該方式可以有多種方法,首先,常用的用戶名密碼登錄所用的filter是UsernamePasswordAuthenticationFilter,token是UsernamePasswordAuthenticationToken,provider是DaoAuthenticationProvider,
- 還有在配置中,以下貌似不能共存
//auth.authenticationProvider(new VerifyCodeAuthenticationProvider());
auth.userDetailsService(authUserDetailService).passwordEncoder(passwordEncoder());
以下是方法:
①
AbstractUserDetailsAuthenticationProvider 是 DaoAuthenticationProvider是父繼承類,我們可以重新繼承AbstractUserDetailsAuthenticationProvider ,加入驗證碼參數(shù),再去authenticate,最后在配置類中配置上就可以了,其實就是一個拓展,
②
我們只要按照登錄驗證的步驟進行重新寫一個自己的過濾器、token類、provider即可,參照UsernamePassword那種即可,但是配置會麻煩一些,與第一種其實蠻相似的,就是花多點功夫,在考慮組件化時,使用可能會好一些。
③
直接定義一個filter,根據(jù)需要,放在UsernamePasswordAuthenticationFilter前面或者后面,
addFilterBefore(new VerifyCodeFilter(), UsernamePasswordAuthenticationFilter.class)
這種是比較清晰的,因為spring security本身就是一個filter鏈,不會干擾到原來的filter的,不會形成耦合,而配置類中還是保持這種就好,不用別的provider了。
auth.userDetailsService(authUserDetailService).passwordEncoder(passwordEncoder());
過濾器代碼:
public class VerifyCodeFilter extends OncePerRequestFilter {
private static final AntPathRequestMatcher ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/haha", HttpMethod.POST.name());
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
RequestMatcher.MatchResult matcher = ANT_PATH_REQUEST_MATCHER.matcher(httpServletRequest);
if (matcher.isMatch()) {
String errMsg = "";
String inputVerifyCode = httpServletRequest.getParameter("verifyCode");
if (StringUtils.isBlank(inputVerifyCode)) {
errMsg = "驗證碼為空";
} else {
Object attrObj = httpServletRequest.getSession().getAttribute("verifyCode");
if (Objects.isNull(attrObj) || attrObj.toString().isEmpty()) {
errMsg = "驗證碼已過期";
} else {
String sessionVerifyCode = attrObj.toString();
if (!inputVerifyCode.equalsIgnoreCase(sessionVerifyCode)) {
errMsg = "驗證碼不正確";
}
}
}
if (StringUtils.isNotBlank(errMsg)) {
//為了回顯錯誤信息,不需要可以刪除,再修改上面的if..else,按情況拋exception
httpServletRequest.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, new VerfifyCodeException(errMsg));
httpServletResponse.sendRedirect("/myLogin?error");
return;
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
④
繼承DaoAuthenticationProvider,其實我們只是要在登陸驗證用戶名密碼的時候順便驗證一下驗證碼是否正確,看過Security登錄流程源碼的同學會發(fā)現(xiàn),用戶密碼的校驗是在DaoAuthenticationProvider類中additionalAuthenticationChecks方法進行的,additionalAuthenticationChecks方法是校驗密碼的,這里貼一下這個方法的源碼:
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {//這里進行密碼校驗
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
因此,我們可以自定義一個AuthenticationProvider來代替 DaoAuthenticationProvider,并重其additionalAuthenticationChecks方法,在重寫的過程中,加入驗證碼的校驗邏輯即可。
創(chuàng)建自定義VerifyCodeAuthenticationProvider類,繼承DaoAuthenticationProvider實現(xiàn)additionalAuthenticationChecks方法,內(nèi)容如下:
public class VerifyCodeAuthenticationProvider extends DaoAuthenticationProvider {
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
//獲取當前請求
HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String code = req.getParameter("code");//從當前請求中拿到code參數(shù)
String verifyCode = (String) req.getSession().getAttribute("verifyCode");//從session中獲取生成的驗證碼字符串
//比較驗證碼是否相同
if (StringUtils.isBlank(code) || StringUtils.isBlank(verifyCode) || !Objects.equals(code, verifyCode)) {
throw new AuthenticationServiceException("驗證碼錯誤!");
}
super.additionalAuthenticationChecks(userDetails, authentication);//調(diào)用父類DaoAuthenticationProvider的方法做密碼的校驗
}
}
接下來就開始配置,讓MyAuthenticationProvider代替DaoAuthenticationProvider,在SecurityConfig中添加以下代碼:
@Bean
VerifyCodeAuthenticationProvider authenticationProvider() {
VerifyCodeAuthenticationProvider authenticationProvider = new VerifyCodeAuthenticationProvider();
authenticationProvider.setPasswordEncoder(passwordEncoder());
authenticationProvider.setUserDetailsService(sysUserDetailService);
return authenticationProvider;
}
@Override
@Bean
protected AuthenticationManager authenticationManager() throws Exception {
return new ProviderManager(Arrays.asList(authenticationProvider()));
}
其中:
authenticationProvider方法提供一個VerifyCodeAuthenticationProvider的實例,創(chuàng)建該實例時,需要提供UserDetailService和PasswordEncoder實例。
重寫authenticationManager方法來提供一個自己的AuthenticationManager,實際上就是ProviderManager,然后加入自定義的VerifyCodeAuthenticationProvider。