添加短信登录接口,未测试。文档未编写

This commit is contained in:
winLsy
2017-12-16 22:20:16 +08:00
parent 04c13eee09
commit fa9d9550a3
7 changed files with 338 additions and 19 deletions
@@ -3,8 +3,8 @@
##本项目的参数配置说明
本项目说明中的配置参数是boot-starter-security-browser和boot-starter-security-app
项目的交集
- 用户登录接口url`/user/login`
- 图片验证码获取接口url`/code/image`
- 用户登录接口url`/user/login`
- 图片验证码获取接口url`/code/image` ,在进行图片验证码校验时传递参数为`imageCode`
- `ms.security.code.image.width` 图片验证码宽度,默认值是`67` 可以在请求中使用`width`参数设置
- `ms.security.code.image.height`图片验证码高度,默认值是`23` 可以在请求中使用`height`参数设置
- `ms.security.code.image.length`图片验证码的code长度,默认是`4` 不能在请求中设置
@@ -13,10 +13,12 @@
不能在请求中设置
- 本着增量开发的原则,可以实现`imageCodeUtil`生成验证码的方法,并定义名称为`imageCodeUtil`的bean便可替换原有的图片验证码
的生成逻辑
- 短信验证码获取接口url`/code/sms` 
- 短信验证码获取接口url`/code/sms` 传递参数为`phone`
- `ms.security.code.sms.length` 短信验证码code的长度,默认值是`6`,不能在请求中设置
- `ms.security.code.sms.expireIn` 短信验证码的默认有效时间是`60`秒,不能在请求中设置
- `ms.security.code.sms.url`短信验证码进行耀眼的地址,默认值是`/user/login`,可以是`/user/*`这种格式,多个适用,号分割,不
能在请求中设置
- 同样的,可以实现`validateCode`生成验证码的方法,并定义名称为`smsCodeUtil`的bean便可替换原有的短信验证码的生成逻辑
- 短信登录的接口为`/authentication/mobile`,传递参数为`smsCode`和`mobile`
@@ -0,0 +1,87 @@
package com.micro.fast.security.core.authentication.mobile;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 身份认证过滤器
* @author lsy
*/
public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String MICRO_FAST_MOBILE_KEY = "mobile";
public static final String AUTHENTICATION_MOBILE_URL = "/authentication/mobile";
private String mobileParameter = MICRO_FAST_MOBILE_KEY;
/**
* 是不是只处理post请求
*/
private boolean postOnly = true;
/**
* 对/authentication/mobile路径的post请求进行身份认证
*/
public SmsCodeAuthenticationFilter() {
super(new AntPathRequestMatcher(AUTHENTICATION_MOBILE_URL, "POST"));
}
/**
* 主要的身份认证方法
* @param request
* @param response
* @return
* @throws AuthenticationException
*/
@Override
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 {
//获取请求中的mobile
String mobile = this.obtainMobile(request);
if (mobile == null) {
mobile = "";
}
mobile = mobile.trim();
SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);
this.setDetails(request, authRequest);
//调用AuthenticationManager中的方法
return this.getAuthenticationManager().authenticate(authRequest);
}
}
/**
* 获取手机号的方法
* @param request
* @return
*/
protected String obtainMobile(HttpServletRequest request) {
return request.getParameter(this.mobileParameter);
}
protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
public void setMobileParameter(String mobileParameter) {
Assert.hasText(mobileParameter, "Username parameter must not be empty or null");
this.mobileParameter = mobileParameter;
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getMobileParameter() {
return this.mobileParameter;
}
}
@@ -0,0 +1,52 @@
package com.micro.fast.security.core.authentication.mobile;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* 进行短信登录的身份认证提供类
* @author lsy
*/
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
/**
* 返回经过验证的Authentication
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
SmsCodeAuthenticationToken smsCodeAuthenticationToken = (SmsCodeAuthenticationToken) authentication;
UserDetails userDetails = userDetailsService.loadUserByUsername((String) smsCodeAuthenticationToken.getPrincipal());
if (userDetails == null) {
throw new InternalAuthenticationServiceException("无法获取用户的信息");
}
SmsCodeAuthenticationToken resultSmsCodeAuthenticationToken = new SmsCodeAuthenticationToken(userDetails, userDetails.getAuthorities());
resultSmsCodeAuthenticationToken.setDetails(smsCodeAuthenticationToken.getDetails());
return resultSmsCodeAuthenticationToken;
}
/**
* 这个类是不是支持传入的token的校验
* @param authenticate
* @return
*/
@Override
public boolean supports(Class<?> authenticate) {
return SmsCodeAuthenticationToken.class.isAssignableFrom(authenticate);
}
public UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
}
@@ -0,0 +1,59 @@
package com.micro.fast.security.core.authentication.mobile;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
/**
* 短信验证登陆的身份令牌类
* @author lsy
*/
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 1L;
private final Object principal;
/**
* 在没有进行身份认证的时候存储手机号
* @param mobile 手机号
*/
public SmsCodeAuthenticationToken(String mobile) {
super((Collection)null);
this.principal = mobile;
this.setAuthenticated(false);
}
/**
* @param principal
* @param authorities
*/
public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
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);
}
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}
@@ -1,13 +0,0 @@
package com.micro.fast.security.core.validate.code.constant;
/**
* 验证码校验常量类
* @author lsy
*/
public class ValidateCodeConst {
/**
* 从session中获取验证码校验对象的key
*/
public static final String SESSION_KEY_IMAGE_CODE = "SESSION_KEY_IMAGE_CODE";
}
@@ -0,0 +1,132 @@
package com.micro.fast.security.core.validate.code.filter;
import com.micro.fast.security.core.authentication.mobile.SmsCodeAuthenticationFilter;
import com.micro.fast.security.core.master.SecurityProperties;
import com.micro.fast.security.core.validate.code.exception.ValidateCodeException;
import com.micro.fast.security.core.validate.code.pojo.ValidateCode;
import com.micro.fast.security.core.validate.code.processor.ValidateCodeProcessor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class SmsValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {
private AuthenticationFailureHandler authenticationFailureHandler;
/**
* session操作工具类
*/
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
private SecurityProperties securityProperties;
private Set<String> filterUrl = new HashSet<String>();
private static final String REQUEST_METHOD = "post";
/**
* 初始化filter url
* @throws ServletException
*/
@Override
public void afterPropertiesSet() throws ServletException {
super.afterPropertiesSet();
String url = securityProperties.getCode().getSms().getUrl();
String[] urls = url.split(",");
for (String s : urls) {
filterUrl.add(s);
}
filterUrl.add(SmsCodeAuthenticationFilter.AUTHENTICATION_MOBILE_URL);
}
@Override
protected void doFilterInternal(
HttpServletRequest httpServletRequest
, HttpServletResponse httpServletResponse
, FilterChain filterChain)
throws ServletException, IOException {
//判断是否需要验证码校验
boolean filter = false;
AntPathMatcher antPathMatcher = new AntPathMatcher();
Iterator<String> iterator = filterUrl.iterator();
while (iterator.hasNext()){
if (antPathMatcher.match(iterator.next(),httpServletRequest.getRequestURI())){
filter = true;
break;
}
}
//在登录post请求的时候对验证码进行校验
if (filter && StringUtils.endsWithIgnoreCase(httpServletRequest.getMethod(),REQUEST_METHOD)){
try{
//校验验证码是否正确
validate(new ServletWebRequest(httpServletRequest));
}catch(ValidateCodeException e){
//进行身份认证失败后的处理
authenticationFailureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,e);
//身份认证失败的时候直接return返回给页面信息,不进行后续的
return;
}
}
//执行后续的过滤器
filterChain.doFilter(httpServletRequest,httpServletResponse);
}
/**
* 验证码校验
* @param servletWebRequest
*/
private void validate(ServletWebRequest servletWebRequest) {
ValidateCode validateCode = (ValidateCode)(sessionStrategy.getAttribute(servletWebRequest, ValidateCodeProcessor.SESSION_KEY_PREFIX+"SMS"));
String code = servletWebRequest.getParameter("smsCode");
//session和用户传入的验证码不能为空
if (validateCode==null || StringUtils.isBlank(code)){
throw new ValidateCodeException("未生成验证码对象或用户传入的验证码为空");
}
if (validateCode.getCode()==null
|| !validateCode.getCode().equals(code)){
throw new ValidateCodeException("验证码校验失败");
}
if (validateCode.isExpire()){
throw new ValidateCodeException("验证码已过期");
}
//一次校验完成之后删除验证码
sessionStrategy.removeAttribute(servletWebRequest,ValidateCodeProcessor.SESSION_KEY_PREFIX+"SMS");
}
public AuthenticationFailureHandler getAuthenticationFailureHandler() {
return authenticationFailureHandler;
}
public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
this.authenticationFailureHandler = authenticationFailureHandler;
}
public Set<String> getFilterUrl() {
return filterUrl;
}
public void setFilterUrl(Set<String> filterUrl) {
this.filterUrl = filterUrl;
}
public SecurityProperties getSecurityProperties() {
return securityProperties;
}
public void setSecurityProperties(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
}
@@ -1,9 +1,9 @@
package com.micro.fast.security.core.validate.code.filter;
import com.micro.fast.security.core.master.SecurityProperties;
import com.micro.fast.security.core.validate.code.constant.ValidateCodeConst;
import com.micro.fast.security.core.validate.code.exception.ValidateCodeException;
import com.micro.fast.security.core.validate.code.pojo.ValidateCode;
import com.micro.fast.security.core.validate.code.processor.ValidateCodeProcessor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
@@ -93,7 +93,7 @@ public class ValidateCodeFilter extends OncePerRequestFilter implements Initial
* @param servletWebRequest
*/
private void validate(ServletWebRequest servletWebRequest) {
ValidateCode validateCode = (ValidateCode)(sessionStrategy.getAttribute(servletWebRequest, ValidateCodeConst.SESSION_KEY_IMAGE_CODE));
ValidateCode validateCode = (ValidateCode)(sessionStrategy.getAttribute(servletWebRequest, ValidateCodeProcessor.SESSION_KEY_PREFIX+"IMAGE"));
String code = servletWebRequest.getParameter("imageCode");
//session和用户传入的验证码不能为空
if (validateCode==null || StringUtils.isBlank(code)){
@@ -107,7 +107,7 @@ public class ValidateCodeFilter extends OncePerRequestFilter implements Initial
throw new ValidateCodeException("验证码已过期");
}
//一次校验完成之后删除验证码
sessionStrategy.removeAttribute(servletWebRequest,ValidateCodeConst.SESSION_KEY_IMAGE_CODE);
sessionStrategy.removeAttribute(servletWebRequest,ValidateCodeProcessor.SESSION_KEY_PREFIX+"IMAGE");
}
public AuthenticationFailureHandler getAuthenticationFailureHandler() {