最近在复习spring security,随手写下这个文章,以方便自己记忆。本文将粗略介绍如何实现复杂验证逻 辑,以及介绍一部分源码。适合小白、中白看,需要之前对spring security有那么一丢丢的认识。我们将实现以下场景:
1)使用用户名、密码、手机号、暗号进行登陆,其中用户名、密码、手机号是用户注册时填写的,暗号是论坛的暗号“宫保鸡丁”。若登陆失败达到3次,该用户名对应的用户将锁定。
先说说我们的思路:
首先一个很自然的想法,就是我们提供一个过滤器对手机号、暗号进行验证,该过滤器在原生的用户名密码认证逻辑之前,只有这两个认证都通过后,认证流程才完成。
但是本文没有采用这种方式,因为这个过滤器filter对用户名手机号进行验证,这个验证逻辑就怕我们写得不够缜密(我还得去看原生的源码DaoAuthenticationProvider的authenticate方法,来写出缜密的认证流程!!),而且白白读多了一次数据库(读手机号),何不利用后面原生的用户名密码认证逻辑??——方案如下:暗号利用filter验证(因为不涉及用户信息),用户名、密码、手机号验证通过spring的认证框架AuthenticationProvider。
接下来我们开动改造啦!!
卖资源啦,本文对应工程请在https://download.csdn.net/download/xiaxuepiaopiao/12022396下载!
1)拓展原生的WebAuthenticationDetails,让认证的元素增加telephone参数。
该类提供了获取用户登录时携带的额外信息的功能,默认实现WebAuthenticationDetails提供了remoteAddress与sessionId信息。开发者可以通过Authentication的getDetails()获取WebAuthenticationDetails。我们编写自定义类CustomWebAuthenticationDetails继承自WebAuthenticationDetails,添加我们关心的数据。
public class WebAuthenticationDetails implements Serializable {
private static final long serialVersionUID = 510L;
private final String remoteAddress;
private final String sessionId;
...
}
我们提供如下CustomWebAuthenticationDetails
@Getter
public class CustomWebAuthenticationDetails extends WebAuthenticationDetails {
private final String telephone;
public CustomWebAuthenticationDetails(HttpServletRequest request) {
super(request);
telephone = request.getParameter("telephone");
}
}
telephone = request.getParameter(“telephone”)可以获得提交的表单中的telephone参数
2)提供自定义AuthenticationDetailsSource,该组件使用1)提供的WebAuthenticationDetails
@Component
public class CustomAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {
@Override
public WebAuthenticationDetails buildDetails(HttpServletRequest httpServletRequest) {
return new CustomWebAuthenticationDetails(httpServletRequest);
}
}
该AuthenticationDetailsSource在spring security配置WebSecurityConfig中被调用,表示使用该组件:
@EnableWebSecurity
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
@Autowired
private CustomAuthenticationDetailsSource customAuthenticationDetailsSource;
@Override
protected void configure(HttpSecurity http) throws Exception {
String[] permitUris = {
"/css/**", "/js/**", "/login_result", "/anyone/**",
"/login", "/userlogin**", "/UserLoginRegistry/**", "/index", "/static/**",
"/api/conf","/registry","/indexA","/images/**","/manage**", "/login_code"};
http.csrf().disable();
http
// 添加暗号校验filter,在默认校验器之前
.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(permitUris).permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/userlogin") // 表示拦截到的路劲为/userlogin时,进入登录认证环节
.successHandler(authenticationSuccessHandler) // 认证成功后逻辑
.failureHandler(authenticationFailureHandler) // 认证失败后逻辑
.authenticationDetailsSource(customAuthenticationDetailsSource); // 提供认证额外参数的能力
}
}
3)定制AuthenticationProvider,拓展用户名密码验证模式为用户名密码手机号验证模式。
默认实现为DaoAuthenticationProvider,该类拓展了抽象类AbstractUserDetailsAuthenticationProvider。抽象类核心验证逻辑为public Authentication authenticate(Authentication authentication)。阅读源码,可知验证过程分三个阶段:preAuthenticationChecks、additionalAuthenticationChecks和postAuthenticationChec