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