1:原理图
SmsCodeAuthenticationFilter接受请求生成SmsCodeAuthenticationToken,然后交给系统的AuthenticationManager进行管理,然后找到SmsCodeAuthenticationProvider,然后再调用UserDetailsService进行短信验证。验证码的验证是在请求之前进行验证码过滤验证的。
在这里主要的是SmsCodeAuthenticationToken,SmsCodeAuthenticationFilter,SmsCodeAuthenticationProvider。
2:SmsCodeAuthenticationToken参照UsernamePasswordAuthenticationToken
/**
* SmsCodeAuthenticationToken
* @author HZK
* @date 2018年7月28日 下午10:49:06
*/
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
// ~ Instance fields
private final Object principal;//存放认证信息。
// ~ Constructors
/**
* 认证之前存放手机号
*/
public SmsCodeAuthenticationToken(Object mobile) {
super(null);
this.principal = mobile;
setAuthenticated(false);
}
/**
* 认证成功存放用户
*/
public SmsCodeAuthenticationToken(Object principal,Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true); // must use super, as we override
}
// ~ Methods
public Object getPrincipal() {
return this.principal;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
@Override
public Object getCredentials() {
// TODO Auto-generated method stub
return null;
}
}
3:SmsCodeAuthenticationFilter参照UsernamePasswordAuthenticationFilter
/**
* 短信登录的过滤器
* @author HZK
* @date 2018年7月28日 下午10:22:39
*/
public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
//请求中携带的参数名
public static final String IMOOC_FORM_MOBILE_KEY = "mobile";
private String mobileParameter = IMOOC_FORM_MOBILE_KEY;// 请求中携带参数的名字是什么
private boolean postOnly = true;//是否仅处理post请求
// ~ Constructors
//处理的短信登录的请求是什么
public SmsCodeAuthenticationFilter() {
super(new AntPathRequestMatcher("/authentication/mobile", "POST"));
}
// ~ Methods
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String mobile = obtainMobile(request);
if (mobile == null) {
mobile = "";
}
mobile = mobile.trim();
SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);
// 将请求的信息设置在Token中
setDetails(request, authRequest);
//拿着Token调用AuthenticationManager
return this.getAuthenticationManager().authenticate(authRequest);
}
/**
* 获取请求参数中的手机号
* @param request
* @return
*/
protected String obtainMobile(HttpServletRequest request) {
return request.getParameter(mobileParameter);
}
/**
* 将请求的信息设置在Token中
* @param request
* @param authRequest
*/
protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
public void setMobileParameter(String mobileParameter) {
Assert.hasText(mobileParameter, "Mobile parameter must not be empty or null");
this.mobileParameter = mobileParameter;
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getMobileParameter() {
return mobileParameter;
}
}
4:SmsCodeAuthenticationProvider
/**
* SmsCodeAuthenticationProvider
* @author HZK
* @date 2018年7月28日 下午11:47:14
*/
public class SmsCodeAuthenticationProvider implements AuthenticationProvider{
private UserDetailsService userDetailsService;
/**
* 身份认证的逻辑
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
SmsCodeAuthenticationToken smsCodeAuthenticationToken = (SmsCodeAuthenticationToken)authentication;
UserDetails user = userDetailsService.loadUserByUsername((String) smsCodeAuthenticationToken.getPrincipal());
if(user == null){
throw new InternalAuthenticationServiceException("无法获取用户信息");
}
/**
* 1:用户认证信息
* 2:用户权限
*/
SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user, user.getAuthorities());
//将之前未认证的请求放进认证后的Token中
authenticationResult.setDetails(smsCodeAuthenticationToken);
return authenticationResult;
}
/**
* AuthenticationManager带着Token调用Provider
* 判断传进来的Token最终调用的是哪个Provider
*/
@Override
public boolean supports(Class<?> authentication) {
return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
}
public UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
}
5:SmsCodeFilter复制ValidateCodeFilte进行修改,后面会重构
public class SmsCodeFilter extends OncePerRequestFilter implements InitializingBean {
private AuthenticationFailureHandler authenticationFailureHandler;
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
private Set<String> urls = new HashSet<>();
private SecurityProperties securityProperties;
private AntPathMatcher pathMatcher = new AntPathMatcher();
@Override
public void afterPropertiesSet() throws ServletException {
super.afterPropertiesSet();
String[] configUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getCode().getSms().getUrl(), ",");
if(ArrayUtils.isNotEmpty(configUrls)){
for (String configUrl : configUrls) {
urls.add(configUrl);
}
}
urls.add("/authentication/mobile");
}
/* (non-Javadoc)
* @see org.springframework.web.filter.OncePerRequestFilter#doFilterInternal(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.FilterChain)
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
boolean action = false;
for (String url : urls) {
if(pathMatcher.match(url, request.getRequestURI())){
action = true;
}
}
if(action) {
try {
validate(new ServletWebRequest(request));
} catch (ValidateCodeException e) {
authenticationFailureHandler.onAuthenticationFailure(request, response, e);
return;
}
}
filterChain.doFilter(request, response);
}
private void validate(ServletWebRequest request) throws ServletRequestBindingException {
ValidateCode codeInSession = (ValidateCode) sessionStrategy.getAttribute(request,
ValidateCodeProcessor.SESSION_KEY_PREFIX+"SMS");
//请求中的短信验证码的值
String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "smsCode");
if (StringUtils.isBlank(codeInRequest)) {
throw new ValidateCodeException("验证码的值不能为空");
}
if(codeInSession == null){
throw new ValidateCodeException("验证码不存在");
}
if(codeInSession.isExpried()){
sessionStrategy.removeAttribute(request, ValidateCodeProcessor.SESSION_KEY_PREFIX+"SMS");
throw new ValidateCodeException("验证码已过期");
}
if(!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
throw new ValidateCodeException("验证码不匹配");
}
sessionStrategy.removeAttribute(request, ValidateCodeProcessor.SESSION_KEY_PREFIX+"SMS");
}
public AuthenticationFailureHandler getAuthenticationFailureHandler() {
return authenticationFailureHandler;
}
public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
this.authenticationFailureHandler = authenticationFailureHandler;
}
public SessionStrategy getSessionStrategy() {
return sessionStrategy;
}
public void setSessionStrategy(SessionStrategy sessionStrategy) {
this.sessionStrategy = sessionStrategy;
}
public Set<String> getUrls() {
return urls;
}
public void setUrls(Set<String> urls) {
this.urls = urls;
}
public SecurityProperties getSecurityProperties() {
return securityProperties;
}
public void setSecurityProperties(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
}
6:下面是将短信验证码和短信登录的相关类进行的配置类
/**
* 将前面的三个类穿在一起
* 在浏览器也要在app中使用
* @author HZK
* @date 2018年7月28日 下午11:33:07
*/
@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler imoocAuthenticationFailureHandler;
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(HttpSecurity http) throws Exception {
SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
//AuthenticationManager
smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
//AuthenticationSuccessHandler
smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(imoocAuthenticationSuccessHandler);
//AuthenticationFailureHandler
smsCodeAuthenticationFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);
SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);
//smsCodeAuthenticationProvider设置在AuthenticationManager管理的集合上面
http.authenticationProvider(smsCodeAuthenticationProvider)
.addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
/**
* 安全配置类
* @author zhailiang
*
*/
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties;
@Autowired
private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler imoocAuthenctiationFailureHandler;
@Autowired
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//验证码的配置
ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
validateCodeFilter.setAuthenticationFailureHandler(imoocAuthenctiationFailureHandler);
validateCodeFilter.setSecurityProperties(securityProperties);
validateCodeFilter.afterPropertiesSet();
//短信验证码的配置
SmsCodeFilter smsCodeFilter = new SmsCodeFilter();
smsCodeFilter.setAuthenticationFailureHandler(imoocAuthenctiationFailureHandler);
smsCodeFilter.setSecurityProperties(securityProperties);
smsCodeFilter.afterPropertiesSet();
//将验证码的过滤器放在登录验证过滤器之前
http.addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin()
.loginPage("/authentication/require")
.loginProcessingUrl("/authentication/form")
.successHandler(imoocAuthenticationSuccessHandler)//登录成功的处理
.failureHandler(imoocAuthenctiationFailureHandler)//登录失败的处理
.and()
.authorizeRequests()
.antMatchers("/authentication/require",
securityProperties.getBrowser().getLoginPage(),
"/code/image").permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable()
//将前面创建的配置类添加在安全配置类后面
.apply(smsCodeAuthenticationSecurityConfig);
}
}