Fork me on GitHub

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

posted @ 2022-11-29 14:43  tiger_yam  阅读(1500)  评论(0编辑  收藏  举报