其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。
在 Spring Security 中,Authority 和 Permission 是两个完全独立的概念,两者并没有必然的联系。他们之间需要通过配置进行关联,可以是自己定义的各种关系。
安全主要分为验证(authentication)和 授权(authorization)两个部分。
验证指的是,建立系统使用者(Principal)的过程。使用者可以是一个用户、设备,和可以在应用程序中执行某种操作的其他系统。
用户认证一般要求用户提供用户名和密码,系统通过校验用户名和密码的正确性来完成认证的通过或拒绝过程。
Spring Security 支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要验证、OpenID 和 LDAP 等
Spring Security 进行验证步骤:
除利用提供的认证外,还可以编写自己的 Filter(过滤器),提供与那些不是基于 Spring Security 的验证系统的操作。
在一个系统中,不同用户具有的权限是不同的。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
它判断某个 Principal 在应用程序中是否允许执行某个操作。在进行授权判断之前,要求其所要使用到的规则必须在验证过程中已经建立好了。
对 Web 资源的保护,最好的办法是使用过滤器。对方法调用的保护,最好的办法是使用 AOP。
Spring Security 在进行用户认证及授权时,也是通过各种 拦截器 和 APO 来控制权限访问的,从而实现安全。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
package com.example.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/security")
public class SecurityController {
@GetMapping("/hello")
public String hello() {
return "Hello Spring Security ~";
}
}
http://localhost:8080/security/hello
application.properties
# Spring Security
spring.security.user.name=root
spring.security.user.password=123456
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 必须要添加这个,否则会报错提示 PasswordEncoder 为 null
@Bean
PasswordEncoder passWord() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 密码加密
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String password = passwordEncoder.encode("123");
// 配置用户名、密码、权限
auth.inMemoryAuthentication().withUser("Lucy").password(password).roles("admin");
}
}
2021-05-30 11:17:31.080 ERROR 36832 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
package com.example.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SelfSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
@Bean
PasswordEncoder passWord() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passWord());
}
}
package com.example.service;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
// 设置权限,这里随便写了一个 role
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User("Mary", new BCryptPasswordEncoder().encode("123"), auths);
}
}
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserModel UM;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserPO user = UM.findByName(username);
if (user == null) {
throw new UsernameNotFoundException("用户名不存在");
}
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User(user.getName(), new BCryptPasswordEncoder().encode(user.getPswd()), auths);
}
}
package com.example.model;
import com.example.jpa.UserPO;
import com.example.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class UserModel {
@Autowired
private UserRepository UR;
public UserPO findByName(String name) {
List<UserPO> userList = UR.findAllByName(name);
System.out.println(userList);
if (userList.size() == 0) {
return null;
}
return userList.get(0);
}
}
package com.example.repository;
import com.example.jpa.UserPO;
import org.springframework.data.repository.Repository;
import java.util.List;
//@Resource
public interface UserRepository extends Repository<UserPO, Long> {
List<UserPO> findAllByName(String name);
}
package com.example.jpa;
import lombok.Data;
import javax.persistence.*;
@Data
@Entity
@Table(name = "user")
public class UserPO {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String pswd;
}
package com.example.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SelfSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
@Bean
PasswordEncoder passWord() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passWord());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义自己编写的登录页面
http.formLogin()
// 登录页面设置
.loginPage("/login.html")
// 登录访问路径
.loginProcessingUrl("/user/login")
// 登录成功之后,跳转路径
.defaultSuccessUrl("/security/index").permitAll()
// 哪些页面需要登录,哪些不需要登录
.and().authorizeRequests()
// 这些访问不需要登录
.antMatchers("/", "/security/hello", "/user/login").permitAll()
// 所有请求都需要认证
.anyRequest().authenticated()
// 关闭CSRF防护
.and().csrf().disable();
}
}
src/main/resources/static/login.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录title>
head>
<body>
<form action="/user/login" method="post">
用户名:<input type="text" name="username">
<br>
密码:<input type="text" name="password">
<br>
<input type="submit" value="login">
form>
body>
html>
package com.example.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/security")
public class SecurityController {
@GetMapping("/hello") // 按上面设置,这个地址可以直接访问
public String hello() {
return "Hello Spring Security ~";
}
@GetMapping("/index") // 按上面设置,这个地址访问先登录,成功后显示
public String index() {
return "登录成功 ~";
}
}
基于 SelfSecurityConfig.java 修改
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义自己编写的登录页面
http.formLogin()
// 登录页面设置
.loginPage("/login.html")
// 登录访问路径
.loginProcessingUrl("/user/login")
// 登录成功之后,跳转路径
.defaultSuccessUrl("/security/index").permitAll()
// 哪些页面需要登录,哪些不需要登录
.and().authorizeRequests()
// 这些访问不需要登录
.antMatchers("/", "/security/hello", "/user/login").permitAll()
// 当前用户具有admins权限才能访问
.antMatchers("/security/admins").hasAuthority("admins")
// 所有请求都需要认证
.anyRequest().authenticated()
// 关闭CSRF防护
.and().csrf().disable();
}
基于 MyUserDetailsService.java 修改
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserPO user = UM.findByName(username);
if (user == null) {
throw new UsernameNotFoundException("用户名不存在");
}
// 用户权限
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("admins");
return new User(user.getName(), // 用户名
new BCryptPasswordEncoder().encode(user.getPswd()), // 用户密码
auths); // 用户权限
}
基于 SelfSecurityConfig.java 修改
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义自己编写的登录页面
http.formLogin()
// 登录页面设置
.loginPage("/login.html")
// 登录访问路径
.loginProcessingUrl("/user/login")
// 登录成功之后,跳转路径
.defaultSuccessUrl("/security/index").permitAll()
// 哪些页面需要登录,哪些不需要登录
.and().authorizeRequests()
// 这些访问不需要登录
.antMatchers("/", "/security/hello", "/user/login").permitAll()
// 当前用户具有 admins 或 manager 权限才能访问
// .antMatchers("/security/admins").hasAuthority("admins")
.antMatchers("/security/admins").hasAnyAuthority("admins", "manager")
// 所有请求都需要认证
.anyRequest().authenticated()
// 关闭CSRF防护
.and().csrf().disable();
}
基于 SelfSecurityConfig.java 修改
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义自己编写的登录页面
http.formLogin()
// 登录页面设置
.loginPage("/login.html")
// 登录访问路径
.loginProcessingUrl("/user/login")
// 登录成功之后,跳转路径
.defaultSuccessUrl("/security/index").permitAll()
// 哪些页面需要登录,哪些不需要登录
.and().authorizeRequests()
// 这些访问不需要登录
.antMatchers("/", "/security/hello", "/user/login").permitAll()
// 当前用户具有 admins 或 manager 权限才能访问
// .antMatchers("/security/admins").hasAuthority("admins")
// .antMatchers("/security/admins").hasAnyAuthority("admins", "manager")
.antMatchers("/security/admins").hasRole("sale")
// 所有请求都需要认证
.anyRequest().authenticated()
// 关闭CSRF防护
.and().csrf().disable();
}
差异看原码:ExpressionUrlAuthorizationConfigurer.java
...
public ExpressionInterceptUrlRegistry hasRole(String role) {
return access(ExpressionUrlAuthorizationConfigurer.hasRole(role));
}
public ExpressionInterceptUrlRegistry hasAuthority(String authority) {
return access(ExpressionUrlAuthorizationConfigurer.hasAuthority(authority));
}
public ExpressionInterceptUrlRegistry hasAnyAuthority(String... authorities) {
return access(ExpressionUrlAuthorizationConfigurer.hasAnyAuthority(authorities));
}
...
private static String hasRole(String role) {
Assert.notNull(role, "role cannot be null");
Assert.isTrue(!role.startsWith("ROLE_"),
() -> "role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'");
return "hasRole('ROLE_" + role + "')";
}
private static String hasAuthority(String authority) {
return "hasAuthority('" + authority + "')";
}
private static String hasAnyAuthority(String... authorities) {
String anyAuthorities = StringUtils.arrayToDelimitedString(authorities, "','");
return "hasAnyAuthority('" + anyAuthorities + "')";
}
...
基于 MyUserDetailsService.java 修改
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserPO user = UM.findByName(username);
if (user == null) {
throw new UsernameNotFoundException("用户名不存在");
}
// 用户权限
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_sale");
return new User(user.getName(), // 用户名
new BCryptPasswordEncoder().encode(user.getPswd()), // 用户密码
auths); // 用户权限
}
略。。。
基于 SelfSecurityConfig.java 修改
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义 403 页面
http.exceptionHandling().accessDeniedPage("/unAuth.html");
// 自定义自己编写的登录页面
http.formLogin()
// 登录页面设置
.loginPage("/login.html")
// 登录访问路径
.loginProcessingUrl("/user/login")
// 登录成功之后,跳转路径
.defaultSuccessUrl("/security/index").permitAll()
// 哪些页面需要登录,哪些不需要登录
.and().authorizeRequests()
// 这些访问不需要登录
.antMatchers("/", "/security/hello", "/user/login").permitAll()
// 当前用户具有 admins 或 manager 权限才能访问
// .antMatchers("/security/admins").hasAuthority("admins")
// .antMatchers("/security/admins").hasAnyAuthority("admins", "manager")
.antMatchers("/security/admins").hasRole("sale")
// 所有请求都需要认证
.anyRequest().authenticated()
// 关闭CSRF防护
.and().csrf().disable();
}
src/main/resources/static/unAuth.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>无权限title>
head>
<body>
<h1>没有访问权限h1>
body>
html>