spring security 使用篇 短信认证
2018-10-15 本文已影响115人
怪诞140819
1.认证基本流程
认证基本流程2.实现手机验证码登录
2.1 开发步骤
对照上面的图我们大概知道分为以下几个步骤
- (1)自定义一个
AuthenticationToken
继承自AbstractAuthenticationToken
可以参考UsernamePasswordAuthenticationFilter
- (2)自定义一个
AuthenticationFilter
继承自AbstractAuthenticationProcessingFilter
可以参考``UsernamePasswordAuthenticationFilter - (3)自定义一个
AuthenticationProvider
实现AuthenticationProvider
,这里定义具体的逻辑
*(4)配置spring security使这些定义生效
2.2 自定义SmsAuthenticationToken
存放认证信息
public class SmsAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 420L;
private Object principal;
public SmsAuthenticationToken(Object principal) {
super((Collection)null);
this.principal = principal;
this.setAuthenticated(false);
}
public SmsAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
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");
} else {
super.setAuthenticated(false);
}
}
}
2.3 自定义SmsAuthenticationFilter
public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String GUAIDAN_MOBILE_KEY = "mobile";
private String mobileParameter = "mobile";
private boolean postOnly = true;
public SmsAuthenticationFilter() {
super(new AntPathRequestMatcher("/authentication/mobile", "POST"));
}
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String mobile = this.obtainMobile(request);
if (mobile == null) {
mobile = "";
}
mobile = mobile.trim();
SmsAuthenticationToken authRequest = new SmsAuthenticationToken(mobile);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
protected String obtainMobile(HttpServletRequest request) {
return request.getParameter(this.mobileParameter);
}
protected void setDetails(HttpServletRequest request, SmsAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public String getMobileParameter() {
return mobileParameter;
}
public void setMobileParameter(String mobileParameter) {
this.mobileParameter = mobileParameter;
}
}
2.4 自定义SmsAuthenticationProvider
定义用户认证逻辑
public class SmsAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
SmsAuthenticationToken smsAuthenticationToken = (SmsAuthenticationToken)(authentication);
UserDetails user = userDetailsService.loadUserByUsername((String) smsAuthenticationToken.getPrincipal());
if(null == user){
throw new InternalAuthenticationServiceException("无法获取用户信息");
}
SmsAuthenticationToken tokenResult = new SmsAuthenticationToken(smsAuthenticationToken.getPrincipal(),user.getAuthorities());
tokenResult.setDetails(smsAuthenticationToken.getDetails());
return tokenResult;
}
@Override
public boolean supports(Class<?> authentication) {
return SmsAuthenticationToken.class.isAssignableFrom(authentication);
}
public UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
}
2.5 自定义SmsCodeValidateFilter验证手机验证码
public class SmsCodeValidateFilter extends OncePerRequestFilter implements InitializingBean {
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
private AuthenticationFailureHandler authenticationFailureHandler;
private SecurityProperties securityProperties;
private Set<String> urls = new HashSet<>();
private AntPathMatcher pathMatcher = new AntPathMatcher();
public SmsCodeValidateFilter(AuthenticationFailureHandler authenticationFailureHandler,SecurityProperties securityProperties) throws ServletException {
this.authenticationFailureHandler = authenticationFailureHandler;
this.securityProperties = securityProperties;
}
@Override
public void afterPropertiesSet() throws ServletException{
super.afterPropertiesSet();
String[] configUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getValidate().getSms().getUrls(),",");
if(null==configUrls){
configUrls = new String[0];
}
for (String configUrl:configUrls){
urls.add(configUrl);
}
urls.add("/authentication/mobile");
}
@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(request);
}catch (ValidateImageCodeException e){
authenticationFailureHandler.onAuthenticationFailure(request,response,e);
return;
}
}
filterChain.doFilter(request,response);
}
private void validate(HttpServletRequest request) {
String imageCode = request.getParameter("smsCode");
ServletWebRequest servletWebRequest = new ServletWebRequest(request);
ValidateCode sessionImageCode = (ValidateCode) sessionStrategy.getAttribute(servletWebRequest, AbstractValidateGeneratorProcess.VALIDATE_CODE_KEY+"sms");
if(StringUtils.isBlank(imageCode)){
throw new ValidateImageCodeException("验证码为空");
}
if(null==sessionImageCode){
throw new ValidateImageCodeException("验证码不存在");
}
if(LocalDateTime.now().isAfter(sessionImageCode.getExpiredTime())){
sessionStrategy.removeAttribute(servletWebRequest, AbstractValidateGeneratorProcess.VALIDATE_CODE_KEY+"sms");
throw new ValidateImageCodeException("验证码已过期");
}
if(!StringUtils.equalsIgnoreCase(imageCode,sessionImageCode.getCode())){
throw new ValidateImageCodeException("验证码不匹配");
}
sessionStrategy.removeAttribute(servletWebRequest, AbstractValidateGeneratorProcess.VALIDATE_CODE_KEY+"sms");
}
}
2.6 添加配置到spring security
@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private AuthenticationSuccessHandler customAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler customAuthenticationFailureHandler;
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(HttpSecurity http) throws Exception {
SmsAuthenticationFilter smsAuthenticationFilter = new SmsAuthenticationFilter();
smsAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
smsAuthenticationFilter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler);
smsAuthenticationFilter.setAuthenticationFailureHandler(customAuthenticationFailureHandler);
SmsAuthenticationProvider provider = new SmsAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
http.authenticationProvider(provider)
.addFilterAfter(smsAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}