一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分,这两点也是 Spring Security 重要核心功能。
- 用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录。
- 用户授权指的是:验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。
**
常用的安全管理框架:
- Shiro
- Spring Security
Spring Security 本质上是一个过滤器链:
- FilterSecurityInterceptor:方法级的权限过滤器,基本位于过滤器链的最底层
- ExceptionTranslationFilter:异常过滤器,用来处理在认证授权过程中抛出的异常
- UsernamePasswordAuthenticationFilter:对 /login 的请求进行拦截,校验表单中的用户名、密码
过滤器自动加载原理:使用 SpringSecurity 配置过滤器:DelegatingFilterProxy
如何自定义登录验证逻辑:
- 创建类继承 UsernamePasswordAuthenticationFilter,重写三个方法
- 创建类实现 UserDetailsService,编写查询数据过程,返回 User 对象(该User对象由框框架提供)
PasswordEncoder 接口:对密码进行加密
- BCryptPasswordEncoder:Spring Security官方推荐的密码解析器,平时使用较多
- BCryptPasswordEncoder 是对 bycrypt 强散列方法的具体实现,是基于 Hash 算法实现的单项加密,可以通过 strength 控制加密强度,默认 10
设置登录的用户名和密码:三种方式
1、通过配置文件 application.properties
spring.security.user.name=
spring.security.user.password=
2、通过配置类:继承 WebSecurityConfigurerAdapter 类,重写 configure 方法
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String password = encoder.encode("password");
auth.inMemoryAuthentication().withUser("username").password(password).roles("admin");
}
// 配置密码解码器
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
3、自定义实现类:
- 创建配置类,设置使用哪个 userDetailsService 实现类
编写实现类,返回 User 对象,User 对象有密码和操作权限 ```java / 创建配置类 / @Configuration public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired private UserDetailsService userDetailsService;
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
} }
/ 实现 UserDetailsService /
@Service(“userDetailsService”)
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 权限列表
List
从数据库中获取登录数据:
- 引入依赖:mybatis-plus-starter、mysql、lombok
- 使用 mybatis-plus:接口直接继承 BaseMapper<T>
- QueryWrapper<User> 条件构造器
- 启动类上 @MapperScan
自定义设置登录页面:在配置类中进行配置
```java
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() // 自定义登录页面
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") //登录访问路径
.defaultSuccessUrl("/index.html").permitAll() //登录成功跳转路径
.and().authorizeRequests()
.antMatchers("/user/login").permitAll() //设置那些路径无需登录,可以直接访问
.anyRequest().authenticated() //其他页面必须认证
.and().csrf().disable(); //关闭csrf防护
}
}
基于角色或权限进行访问控制:
前提:在配置类中返回 User 对象时,必须设置访问权限
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
1、hasAuthority(),当前主体有指定的权限,返回 true,否则 false
.and().authorizeRequests()
.antMatchers("/user/login").permitAll() //设置那些路径无需登录,可以直接访问
.antMatchers("/admin").hasAuthority("admin") // 设置该页面只有admin用户才能访问
.anyRequest().authenticated() //其他页面必须认证
没有访问权限:type = Forbidden,status = 403
2、hasAnyAuthority():如果当前的主体有任何提供的角色(给定一个逗号分隔的字符串列表)的话,返回 true
.antMatchers("/admin").hasAnyAuthority("admin,manager")
3、hasRole():如果用户具备给定角色就允许访问,否则出现 403;如果当前主体具有指定的角色,则返回 true
4、hasAnyRole():表示用户具备任何一个条件都可以访问
区别:在设置角色权限时,前必须加 ROLE 前缀,而在进行匹配时,则必须省略 ROLE 前缀
自定义 403 无权限访问页面:
- 修改访问配置类:http.exceptionHandling().accessDeniedPage(“/unauth”);
- 添加对应的控制器:@GetMapping(“/unauth”)
@Secured:判断是否具有某角色
- 这里匹配的字符串需要添加前缀 “ROLE_”
- 该注解用在控制器方法上,用于标注该方法的访问权限:@Secured({“ROLE_normal”,”ROLE_admin”})
注解使用:
- 使用注解必须现在启动类上开启注解功能,@EnableGlobalMethodSecurity(securedEnabled = true)
- 在 Controller 的方法上添加相应注解:@Secured({“ROLE_normal”,”ROLE_admin”})
- userDetailsService 设置用户角色:AuthorityUtils.commaSeparatedStringToAuthorityList(“admin , ROLE_sale”);
@PreAuthorize:注解适合进入方法前的权限验证,@PreAuthorize 可以将登录用户的roles/permissions 参数传到方法中 @PreAuthorize(“hasAnyAuthority(‘menu : system’)”),方法之前的校验
- 开启注解功能:@EnableGlobalMethodSecurity(securedEnabled=true , prePostEnabled=true)
- 在 Controller 的方法上添加相应注解:@PreAuthorize(“hasAnyAuthority(‘admins’)”)
@PostAuthorize:在方法执行后再进行权限验证,适合验证带有返回值的权限 @PostAuthorize(“hasAnyAuthority(‘menu : system’)”),方法之后的校验
- 开启注解功能:@EnableGlobalMethodSecurity(securedEnabled=true , prePostEnabled=true)
@PostFilter:权限验证之后对数据进行过滤,留下用户名是 admin1 的数据,表达式中的 filterObject 引用的是方法返回值 List 中的某一个元素,方法返回数据进行过滤
@PreFilter:进入控制器之前对数据进行过滤,传入方法数据进行过滤