Spring Security 5.7.* 没有WebSecurityConfigurerAdapter如何配置AuthenticationManager
简述
我用了Spring security 5.7.4这个比较新的版本,而且官方已经标注说明WebSecurityConfigurerAdapter已经过期,那么我就根据官方新的配置方式进行了配置,就在我自定义LoginFilter
这个类去继承UsernamePasswordAuthenticationFilter
且覆盖attemptAuthentication
方法时需要配置AuthenticationManager
我就懵了,旧的方式是继承WebSecurityConfigurerAdapter
且覆盖authenticationManagerBean()
方法去调用父类方法,现在没了WebSecurityConfigurerAdapter
,新的配置方式怎么配呢?然后就在这个坑徘徊了许久,所以现在我把我的解决办法记录一下,以此让更多的伙伴避免这个坑。
WebSecurityConfigurerAdapter过期的官方博客说明新用法传送门
考虑到博客的篇幅,下面我粘贴主要的代码和逻辑。
旧的配置方式
下面代码我配置了接收前端JSON的形式,而且配置了验证码的校验:
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
public LoginFilter(AuthenticationManager authenticationManager){
super(authenticationManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
// 需要是 POST 请求
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
HttpSession session = request.getSession();
// 获得 session 中的 验证码值
String sessionVerifyCode = (String) session.getAttribute("verify_code");
// 判断请求格式是否是 JSON
if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)) {
Map<String, String> loginData = new HashMap<>();
try {
loginData = new ObjectMapper().readValue(request.getInputStream(), Map.class);
} catch (IOException e) {
} finally {
String code = loginData.get("code");
checkVerifyCode(sessionVerifyCode, code);
}
String username = loginData.get(getUsernameParameter());
String password = loginData.get(getPasswordParameter());
if (StringUtils.isEmpty(username)) {
throw new AuthenticationServiceException("用户名不能为空");
}
if (StringUtils.isEmpty(password)) {
throw new AuthenticationServiceException("密码不能为空");
}
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
} else {
checkVerifyCode(sessionVerifyCode, request.getParameter("code"));
return super.attemptAuthentication(request, response);
}
}
private void checkVerifyCode(String sessionVerifyCode, String code) {
if (StringUtils.isEmpty(code)) {
throw new AuthenticationServiceException("验证码不能为空!");
}
if (StringUtils.isEmpty(sessionVerifyCode)) {
throw new AuthenticationServiceException("请重新申请验证码!");
}
if (!sessionVerifyCode.equalsIgnoreCase(code)) {
throw new AuthenticationServiceException("验证码错误!");
}
}
}
下面代码WebSecurityConfigurerAdapter这个类的配置,观察authenticationManagerBean()
这个方法就是调用了父类的方法:
@Override
@Bean
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 用自定义的 LoginFilter 实例代替 UsernamePasswordAuthenticationFilter
http.addFilterBefore(loginFilter(), UsernamePasswordAuthenticationFilter.class);
http.authorizeRequests() //开启配置
// 验证码、登录接口放行
.antMatchers("/verify-code","/sso/login").permitAll()
.anyRequest() //其他请求
.authenticated().and()//验证 表示其他请求需要登录才能访问
.csrf().disable(); // 禁用 csrf 保护
http.exceptionHandling().authenticationEntryPoint(new MyAuthenticationEntryPoint());
}
@Bean
LoginFilter loginFilter() throws Exception {
LoginFilter loginFilter = new LoginFilter();
loginFilter.setFilterProcessesUrl("/sso/login");
loginFilter.setUsernameParameter("username");
loginFilter.setPasswordParameter("password");
loginFilter.setAuthenticationManager(authenticationManagerBean());
loginFilter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
loginFilter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
return loginFilter;
}
上面代码是有WebSecurityConfigurerAdapter这个类的情况,下面看没有WebSecurityConfigurerAdapter这个类是怎么配的。
新的配置方式
新的配置方式,这里有两种方法:
- 配置全局的AuthenticationManager
- 自定义一个Configurer的方式
配置全局的AuthenticationManager的方式:
step 1: 上面粘贴的LoginFilter的代码
这里得留意我在LoginFilter以构造方法来传递给父类,当然你也可以设置为setter的方式,个人爱好。
step 2: 在配置类中配置以下代码
@Bean
SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity
.authorizeRequests();
//不需要保护的资源路径允许访问
for (String url : ignoreUrlsConfig.getUrls()) {
registry.antMatchers(url).permitAll();
}
//允许跨域请求的OPTIONS请求
registry.antMatchers(HttpMethod.OPTIONS)
.permitAll();
// 任何请求需要身份认证
registry.and()
.authorizeRequests()
.anyRequest()
.authenticated()
// 关闭跨站请求防护及不使用session
.and()
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 自定义权限拒绝处理类
.and()
.exceptionHandling()
.accessDeniedHandler(restfulAccessDeniedHandler)
.authenticationEntryPoint(restAuthenticationEntryPoint)
// 自定义权限拦截器JWT过滤器
.and()
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
.addFilterAt(loginFilter(authenticationManager(httpSecurity.getSharedObject(AuthenticationConfiguration.class))), UsernamePasswordAuthenticationFilter.class);
//有动态权限配置时添加动态权限校验过滤器
if(dynamicSecurityService!=null){
registry.and().addFilterBefore(dynamicSecurityFilter, FilterSecurityInterceptor.class);
}
return httpSecurity.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
LoginFilter loginFilter(AuthenticationManager authenticationManager) throws Exception {
LoginFilter loginFilter = new LoginFilter(authenticationManager);
loginFilter.setFilterProcessesUrl("/login");
loginFilter.setUsernameParameter("username");
loginFilter.setPasswordParameter("password");
// loginFilter.setAuthenticationManager();
loginFilter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
loginFilter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
return loginFilter;
}
留意上面.addFilterAt(loginFilter(authenticationManager(httpSecurity.getSharedObject(AuthenticationConfiguration.class))), UsernamePasswordAuthenticationFilter.class);这行代码
自定义一个Configurer的方式
step 1: 跟上面LoginFilter一样的代码
step 2: 新建一个Configurer类,代码如下:
public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
@Override
public void configure(HttpSecurity http) throws Exception {
AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
http.addFilter(loginFilter(authenticationManager));
}
public static MyCustomDsl customDsl() {
return new MyCustomDsl();
}
LoginFilter loginFilter(AuthenticationManager authenticationManager) throws Exception {
LoginFilter loginFilter = new LoginFilter(authenticationManager);
loginFilter.setFilterProcessesUrl("/login");
loginFilter.setUsernameParameter("username");
loginFilter.setPasswordParameter("password");
// loginFilter.setAuthenticationManager(authenticationManager);
loginFilter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
loginFilter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
return loginFilter;
}
}
step 3: 参考上面的配置类的@Bean SecurityFilterChain filterChain(HttpSecurity httpSecurity)
的代码,去掉.addFilterAt(loginFilter(authenticationManager(httpSecurity.getSharedObject(AuthenticationConfiguration.class))), UsernamePasswordAuthenticationFilter.class);
这行代码,新添.and().apply(MyCustomDsl.customDsl())
这行代码,详细如下:
@Bean
SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity
.authorizeRequests();
...
...
// 任何请求需要身份认证
registry.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.apply(MyCustomDsl.customDsl())
...
...
总结
根据自己的实际情况参考以上代码,如果还是解决不了的话,可以参考我下面的参考连接或许给你提供新思路,实在解决不了那就去搜索引擎找了,
推荐google、stackoverflow。最后提一下,下面的参考连接有官方的说明,点进去看看吧!
参考连接:
stackoverflow
spring security官方文档
spring security github issues