前言:
在本人经历过新版和旧版的不同的配置折磨后,决心自己写一篇介绍造福大众,不好地方,欢迎伙指出ovo
本文针对Spring Security 6.0版本的自定义配置进行介绍,其中包含自定义数据源
UserDetailsService、自定义过滤链SecurityFilterChain等内容.
一、自定义过滤链SecurityFilterChain
1.旧版
在旧版的配置中,Security需要我们写一个类去继承他的WebSecurityConfigurerAdapter并把这个配置注入到容器中
java">@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();
}
}
2.新版
在新版的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认证管理器,这样我们在认证的时候,认证管理器就会使用我们自定义的数据源
那么在新旧版本又是如何进行配置的,我们往下看
1.旧版
在旧版的配置中,我们首先去实现一个自定义实体类继承自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 isA***ountNonExpired() {
return true;
}
@Override
@JsonIgnore
public boolean isA***ountNonLocked() {
return true;
}
@Override
@JsonIgnore
public boolean isCredentialsNonExpired() {
return true;
}
@Override
@JsonIgnore
public boolean isEnabled() {
return true;
}
}
然后是实现一个类继承UserDetailsService (注意我代码使用了mp查询数据,非mp正常在UserMapper里面写对应SQL,只要将最终查询到的用户User返回即可)
在这个类中,我们也使用了@***ponent注解将这个数据源交给IOC容器
@***ponent
// "登录数据源"
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 {
// 查询用户
LambdaQueryWrapper<User> queryWrapper_User = new LambdaQueryWrapper<>();
queryWrapper_User.eq(User::getUsername,username);
User user = userMapper.selectOne(queryWrapper_User);
// 工具类判断是否查询user对象是否有效
if (ObjectUtils.isEmpty(user)) {
throw new UsernameNotFoundException("用户不存在");
}
LambdaQueryWrapper<Role> queryWrapper_Role = new LambdaQueryWrapper<>();
queryWrapper_Role.eq(Role::getUid,user.getId());
List<Role> 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();
}
}
2.新版
新版的比较简单,直接定义好数据源,注入就可以了,无需手动到配置类中去将它提交给AuthenticationManager进行管理。
@***ponent
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),新版底层会帮你执行这一步