From fa9d9550a31eadcb3591598df8e941308df5deb7 Mon Sep 17 00:00:00 2001 From: winLsy <18332763730@163.com> Date: Sat, 16 Dec 2017 22:20:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=9F=AD=E4=BF=A1=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E6=8E=A5=E5=8F=A3=EF=BC=8C=E6=9C=AA=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E3=80=82=E6=96=87=E6=A1=A3=E6=9C=AA=E7=BC=96=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../boot-starter-security-core/README.md | 8 +- .../mobile/SmsCodeAuthenticationFilter.java | 87 ++++++++++++ .../mobile/SmsCodeAuthenticationProvider.java | 52 +++++++ .../mobile/SmsCodeAuthenticationToken.java | 59 ++++++++ .../code/constant/ValidateCodeConst.java | 13 -- .../code/filter/SmsValidateCodeFilter.java | 132 ++++++++++++++++++ .../code/filter/ValidateCodeFilter.java | 6 +- 7 files changed, 338 insertions(+), 19 deletions(-) create mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/authentication/mobile/SmsCodeAuthenticationFilter.java create mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/authentication/mobile/SmsCodeAuthenticationProvider.java create mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/authentication/mobile/SmsCodeAuthenticationToken.java delete mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/constant/ValidateCodeConst.java create mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/filter/SmsValidateCodeFilter.java diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/README.md b/boot-starter/boot-starter-security/boot-starter-security-core/README.md index 805620a..7491ed6 100644 --- a/boot-starter/boot-starter-security/boot-starter-security-core/README.md +++ b/boot-starter/boot-starter-security/boot-starter-security-core/README.md @@ -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` + diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/authentication/mobile/SmsCodeAuthenticationFilter.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/authentication/mobile/SmsCodeAuthenticationFilter.java new file mode 100644 index 0000000..454fca6 --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/authentication/mobile/SmsCodeAuthenticationFilter.java @@ -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; + } + +} diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/authentication/mobile/SmsCodeAuthenticationProvider.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/authentication/mobile/SmsCodeAuthenticationProvider.java new file mode 100644 index 0000000..5e7333b --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/authentication/mobile/SmsCodeAuthenticationProvider.java @@ -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; + } +} diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/authentication/mobile/SmsCodeAuthenticationToken.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/authentication/mobile/SmsCodeAuthenticationToken.java new file mode 100644 index 0000000..b77ce59 --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/authentication/mobile/SmsCodeAuthenticationToken.java @@ -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 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(); + } +} diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/constant/ValidateCodeConst.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/constant/ValidateCodeConst.java deleted file mode 100644 index 37f136c..0000000 --- a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/constant/ValidateCodeConst.java +++ /dev/null @@ -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"; - -} diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/filter/SmsValidateCodeFilter.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/filter/SmsValidateCodeFilter.java new file mode 100644 index 0000000..dc478bf --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/filter/SmsValidateCodeFilter.java @@ -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 filterUrl = new HashSet(); + + 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 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 getFilterUrl() { + return filterUrl; + } + + public void setFilterUrl(Set filterUrl) { + this.filterUrl = filterUrl; + } + + public SecurityProperties getSecurityProperties() { + return securityProperties; + } + + public void setSecurityProperties(SecurityProperties securityProperties) { + this.securityProperties = securityProperties; + } +} diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/filter/ValidateCodeFilter.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/filter/ValidateCodeFilter.java index 5721af7..39eeb04 100644 --- a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/filter/ValidateCodeFilter.java +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/filter/ValidateCodeFilter.java @@ -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() {