1. 程式人生 > 其它 >Spring Security 5.7.* 沒有WebSecurityConfigurerAdapter如何配置AuthenticationManager

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