在本人经历过新版和旧版的不同的配置折磨后,决心自己写一篇介绍造福大众,不好地方,欢迎伙指出ovo 本文针对Spring Security 6.0版本的自定义配置进行介绍,其中包含自定义数据源 UserDetailsService、自定义过滤链SecurityFilterChain等内容.
在旧版的配置中,Security需要我们写一个类去继承他的WebSecurityConfigurerAdapter并把这个配置注入到容器中
@Configuration public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter{ ..... }
在继承这个类后,我们可以在WebSecurityConfigurer里面去重写WebSecurityConfigurationAdapter类里面的一些方法来实现自定义过滤链等操作
实现自定义过滤链需要重写configure(HttpSecurity http)方法
@Override protected void configure(HttpSecurity http) throws Exception { http .mvcMatchers("/login.html") .permitAll() .formLogin() ...... .csrf() .disable(); }
这个方法的参数http采用的是链式设计,我们直接 ‘.’就可以进行设置。
特别的在旧版的配置中,我们需要手动将AuthenticationManager类暴露在全局中,使我们能在其他功能实现上可以拿到这个认证管理器
// 暴露认证管理器给全局 @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }
旧版整体代码
@Configuration // Security自定义配置器" public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .mvcMatchers("/login.html") .permitAll() .formLogin() ...... .csrf() .disable(); } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
在新版的Spring Security里面,如果我们继续继承WebSecurityConfigurationAdapter类的话,会出现如下弃用提示
public class WebSecurityConfigurer extends
WebSecurityConfigurerAdapter
在新版中我们只需要在自定义的配置类头顶添加一个 @EnableWebSecurity 注解即可将你写的这个注入到IOC容器中
该注解源码如下:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, HttpSecurityConfiguration.class }) @EnableGlobalAuthentication @Configuration public @interface EnableWebSecurity { /** * Controls debugging support for Spring Security. Default is false. * @return if true, enables debug support with Spring Security */ boolean debug() default false; }
从源码中我们可以看到内部以及实现了@Configuration注解,所有不需要我们手动注入IOC容器
在这个注解内部,作者也对新版的Spring Security配置SecurityFilterChain进行了特别说明
其中我们目前只需要注意securityFilterChain(HttpSecurity http)这个方法,这个方法就是我们配置自定义过滤链需要实现的方法
以下是实现伪代码
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests((auth) -> { auth .mvcMatchers("/user/login") .permitAll() .anyRequest() .authenticated(); }) .formLogin() ...... .csrf() .disable() // 构建过滤链并返回 return http.build(); }
新版的实现方法不再和旧版一样在配置类里面重写方法,而是构建了一个过滤链对象并通过@Bean注解注入到IOC容器中
新版整体代码 (注意:新版AuthenticationManager认证管理器默认全局)
@EnableWebSecurity // Security自定义配置器 public class WebSecurityConfiger { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests((auth) -> { auth .mvcMatchers("/user/login") .permitAll() .anyRequest() .authenticated(); }) .formLogin() ...... .csrf() .disable() // 构建过滤链并返回 return http.build(); } }
在介绍如何配置前,我们先来看看底层是如何进行数据源认证的,下面是流程图:
在整个认证流程中,我们在提交用户表单数据的时候,会先被AbstractAuthenticationProcessingFilte捕获到如何被封装为UsernamePasswordAuthenticationToken 的token 对象,然后将整个对象交给AuthenticationManager认证管理器去实现认证,而这个时候就会去数据源UserDetailService类中去进行对应的认证方式,认证成功返回UserDetails对象,否则返回null,所以我们这个时候目的就比较明确了,为实现自定义数据源,我们就需要去重新实现一个类继承自UserDetailsSerive类,然后将这个类交给AuthenticationManager认证管理器,这样我们在认证的时候,认证管理器就会使用我们自定义的数据源
那么在新旧版本又是如何进行配置的,我们往下看
在旧版的配置中,我们首先去实现一个自定义实体类继承自UserDetails(注意:必须把这个类需要重写一些方法,这些方法都是给认证管理器去做一些工作的,这个User类还可以拥有一些其他的字段属性,比如说username,password…建议基于数据库中的User表来设置)
@Data //@ApiModel(value = "User对象", description = "用户对象") public class User implements UserDetails { private String username; private String password; ..... @Override @JsonIgnore public Collection extends GrantedAuthority> getAuthorities() { // 创建角色集合 ......... return grantedRoles; } @Override @JsonIgnore public boolean isAccountNonExpired() { return true; } @Override @JsonIgnore public boolean isAccountNonLocked() { return true; } @Override @JsonIgnore public boolean isCredentialsNonExpired() { return true; } @Override @JsonIgnore public boolean isEnabled() { return true; } }
然后是实现一个类继承UserDetailsService (注意我代码使用了mp查询数据,非mp正常在UserMapper里面写对应SQL,只要将最终查询到的用户User返回即可)
在这个类中,我们也使用了@Component注解将这个数据源交给IOC容器
@Component // "登录数据源" public class LoginUserDetailService implements UserDetailsService { // 数据库查询User信息 private final UserMapper userMapper; private final RoleMapper RoleMapper @Autowired public LoginUserDetailService(UserMapper userMapper, RoleMapper roleMapper) { this.userMapper = userMapper; this.roleMapper = roleMapper; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 查询用户 LambdaQueryWrapperqueryWrapper_User = new LambdaQueryWrapper<>(); queryWrapper_User.eq(User::getUsername,username); User user = userMapper.selectOne(queryWrapper_User); // 工具类判断是否查询user对象是否有效 if (ObjectUtils.isEmpty(user)) { throw new UsernameNotFoundException("用户不存在"); } LambdaQueryWrapper queryWrapper_Role = new LambdaQueryWrapper<>(); queryWrapper_Role.eq(Role::getUid,user.getId()); List roles = roleMapper.selectList(queryWrapper_Role); user.setRoles(roles); // User类继承了UserDetails,返回时会进行向上转型,所以我们将正常查询到的user数据装进User对象返回即可 return user; } }
现在到最后一步了,回到我们的WebSecurityConfigurationAdapter实现类里面,我们使用@Autowired注入我们刚刚写的LoginUserDetailService类使用 configure(AuthenticationManagerBuilder auth) 方法将这个实例交给AuthenticationManager,下面是具体代码
@Configuration public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { @Autowired private LoginUserDetailService loginUserDetailService @Override protected void configure(HttpSecurity http) throws Exception { http .mvcMatchers("/login.html") .permitAll() .formLogin() ...... .csrf() .disable(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(loginUserDetailService); } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
新版的比较简单,直接定义好数据源,注入就可以了,无需手动到配置类中去将它提交给AuthenticationManager进行管理。
@Component public class LoginUserDetailService implements UserDetailsService { private final UserMapper userMapper; @Autowired public LoginUserDetailService(UserMapper userMapper, RoleMapper roleMapper) { this.userMapper = userMapper; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 数据查询 userMapper.方法 return user; } }
相比旧版,跳过了手动auth.userDetailsService(loginUserDetailService),新版底层会帮你执行这一步
上一篇:MySQL中的字符替换