重构验证码的存储模式

This commit is contained in:
winLsy
2018-01-20 22:59:47 +08:00
parent bf6ce3c0ab
commit 5d1071d704
8 changed files with 330 additions and 28 deletions
@@ -0,0 +1,56 @@
package com.micro.fast.security.app;
import com.micro.fast.security.core.validate.code.ValidateCodeRepository;
import com.micro.fast.security.core.validate.code.exception.ValidateCodeException;
import com.micro.fast.security.core.validate.code.pojo.ValidateCode;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;
import java.util.concurrent.TimeUnit;
/**
* app模块存储验证码
* @author lsy
*/
@Component
public class AppValidateCodeRepository implements ValidateCodeRepository {
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
@Override
public void save(ServletWebRequest request, ValidateCode code, String validateCodeType) {
redisTemplate.opsForValue().set(buildKey(request,validateCodeType),code,30, TimeUnit.MINUTES);
}
@Override
public ValidateCode get(ServletWebRequest request, String validateCodeType) {
Object o = redisTemplate.opsForValue().get(buildKey(request, validateCodeType));
if (o==null){
return null;
}
return (ValidateCode) o;
}
@Override
public void remove(ServletWebRequest request, String validateCodeType) {
redisTemplate.delete(buildKey(request,validateCodeType));
}
/**
* 构建redis的key
* @param request
* @param validateCodeType
* @return
*/
private String buildKey(ServletWebRequest request,String validateCodeType){
String deviceId = request.getHeader("deviceId");
if (StringUtils.isBlank(deviceId)){
throw new ValidateCodeException("请求头中缺少机器的唯一识别");
}
return "code:"+validateCodeType.toLowerCase()+":"+deviceId;
}
}
@@ -1,13 +1,98 @@
package com.micro.fast.security.app.config.authorization2;
import com.micro.fast.security.app.config.component.MsAuthenticationFailureHandler;
import com.micro.fast.security.app.config.component.MsAuthenticationSuccessHandler;
import com.micro.fast.security.core.authentication.mobile.SmsCodeAuthenticationSecurityConfig;
import com.micro.fast.security.core.master.SecurityConstants;
import com.micro.fast.security.core.master.SecurityProperties;
import com.micro.fast.security.core.validate.code.ValidateCodeRepository;
import com.micro.fast.security.core.validate.code.filter.ValidateCodeFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.social.security.SpringSocialConfigurer;
/**
* OAuth2协议标准服务提供实现配置类
* OAuth2协议标准服务提供实现配置类,资源服务器的配置
* @author lsy
*/
@Configuration
@EnableResourceServer
public class AuthorizationResourceConfig {
public class AuthorizationResourceConfig extends ResourceServerConfigurerAdapter {
//todo 待优化,不唯一,应从启动参数中获取
public static final String DRUID = "/druid/*";
@Autowired
private MsAuthenticationFailureHandler msAuthenticationFailureHandler;
@Autowired
private MsAuthenticationSuccessHandler msAuthenticationSuccessHandler;
@Autowired
private SecurityProperties securityProperties;
/**
* 短信验证码登录的配置
*/
@Autowired
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
@Autowired
private SpringSocialConfigurer springSocialConfigurer;
@Autowired
private ValidateCodeRepository validateCodeRepository;
@Override
public void configure(HttpSecurity http) throws Exception {
//创建自定义的验证码过滤器,填入身份认证失败处理对象
ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
validateCodeFilter.setAuthenticationFailureHandler(msAuthenticationFailureHandler);
validateCodeFilter.setSecurityProperties(securityProperties);
validateCodeFilter.setValidateCodeRepository(validateCodeRepository);
validateCodeFilter.afterPropertiesSet();
http
//添加自定义的验证码处理器,在用户密码校验过滤器之前
.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
//使用表单登录
.formLogin()
//自定义登录页面
.loginPage(SecurityConstants.LOGIN_PAGE)
//配置spring security登录接口的访问地址,用于在自定义的登录页面提交请求
.loginProcessingUrl(SecurityConstants.FORM_LOGIN_PROCESSOR_URI)
//定义身份认证成功后处理对象
.successHandler(msAuthenticationSuccessHandler)
//定义身份认证失败后处理对象
.failureHandler(msAuthenticationFailureHandler)
.and()
.authorizeRequests()//下面是权限配置
//当访问这个页面的时候不进行身份认证,对用户自定义的登录页也不进行权限认证
.antMatchers(SecurityConstants.MOBILE_LOGIN_PROCESSOR_URI
, SecurityConstants.FORM_LOGIN_PROCESSOR_URI
, SecurityConstants.GET_VALIDATE_CODE_URI
, SecurityConstants.LOGIN_PAGE
, DRUID
, securityProperties.getBrowser().getHtml()
//用户注册或绑定的页面
, securityProperties.getBrowser().getSignUpUrl()
, "处理用户的注册绑定请求的url"
)
.permitAll()
//任何请求
.anyRequest()
//都需要身份认证
.authenticated()
.and()
//暂时关闭掉跨站请求攻击防护
.csrf().disable()
//应用短信验证码登录的逻辑
.apply(smsCodeAuthenticationSecurityConfig)
.and()
//应用springSocial的配置,在接收到特殊的请求的时候进行社交登录.例如 /auth/qq 其中/auth是默认的前缀,qq是providerId
.apply(springSocialConfigurer)
;
}
}
@@ -1,12 +1,20 @@
package com.micro.fast.security.app.config.component;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.micro.fast.security.core.browser.constant.LoginType;
import com.micro.fast.security.core.master.SecurityProperties;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
@@ -16,7 +24,8 @@ import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 身份认证成功后的处理类
* 身份认证成功后的处理类,在登录成功后进行授权token的处理
*
* @author lsy
*/
@Component("msAuthenticationSuccessHandler")
@@ -35,28 +44,81 @@ public class MsAuthenticationSuccessHandler extends SavedRequestAwareAuthenticat
@Autowired
private ObjectMapper objectMapper;
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
@Qualifier("defaultAuthorizationServerTokenServices")
private AuthorizationServerTokenServices authorizationServerTokenServices;
/**
* 登录成功后的处理方法
* @param httpServletRequest
* @param httpServletResponse
* @param authentication 用户认证的详细信息
*
* @param request
* @param response
* @param authentication 用户认证的详细信息
* @throws IOException
* @throws ServletException
*/
@Override
public void onAuthenticationSuccess(
HttpServletRequest httpServletRequest
, HttpServletResponse httpServletResponse
HttpServletRequest request
, HttpServletResponse response
, Authentication authentication
) throws IOException, ServletException {
//根据配置参数判断身份认证成功后的返回类型进行响应
if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
log.info("登录成功");
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authentication));
}else{
super.onAuthenticationSuccess(httpServletRequest,httpServletResponse,authentication);
}
) throws IOException, ServletException {
log.info("登录成功,开始授权令牌");
//1.校验clientId和clientSecret是否对应
String header = request.getHeader("Authorization");
if (header == null && !header.startsWith("Basic ")) {
throw new UnapprovedClientAuthenticationException("请求头中client信息不存在");
}
String[] tokens = this.extractAndDecodeHeader(header, request);
assert tokens.length == 2;
String clientId = tokens[0];
String clientSecret = tokens[1];
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
if (clientDetails == null){
throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在:"+clientId);
}else if (!StringUtils.equals(clientDetails.getClientSecret(),clientSecret)){
throw new UnapprovedClientAuthenticationException("clientSecret不匹配:"+clientSecret);
}
//2.自定义流程创建access_Token
TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP,clientId,clientDetails.getScope(),"custom");
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
OAuth2Authentication auth2Authentication = new OAuth2Authentication(oAuth2Request,authentication);
OAuth2AccessToken accessToken = authorizationServerTokenServices.createAccessToken(auth2Authentication);
//3.返回accessToken
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(accessToken));
}
/**
* 对header进行解码
* @param header
* @param request
* @return
* @throws IOException
*/
private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException {
byte[] base64Token = header.substring(6).getBytes("UTF-8");
byte[] decoded;
try {
decoded = Base64.decode(base64Token);
} catch (IllegalArgumentException var7) {
throw new BadCredentialsException("Failed to decode basic authentication token");
}
String token = new String(decoded, this.getCredentialsCharset(request));
int delim = token.indexOf(":");
if (delim == -1) {
throw new BadCredentialsException("Invalid basic authentication token");
} else {
return new String[]{token.substring(0, delim), token.substring(delim + 1)};
}
}
protected String getCredentialsCharset(HttpServletRequest httpRequest) {
return "UTF-8";
}
}
@@ -0,0 +1,42 @@
package com.micro.fast.security.browser;
import com.micro.fast.security.core.validate.code.ValidateCodeRepository;
import com.micro.fast.security.core.validate.code.pojo.ValidateCode;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;
import static com.micro.fast.security.core.validate.code.processor.ValidateCodeProcessor.SESSION_KEY_PREFIX;
/**
* 浏览器存储验证码的策略
* @author lsy
*/
@Component
public class BrowserValidateCodeRepository implements ValidateCodeRepository {
/**
* 操作session的工具类
*/
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
@Override
public void save(ServletWebRequest request, ValidateCode code, String validateCodeType) {
//向session中存储验证码
sessionStrategy.setAttribute(request
,SESSION_KEY_PREFIX+validateCodeType.toUpperCase()
,code);
}
@Override
public ValidateCode get(ServletWebRequest request, String validateCodeType) {
ValidateCode validateCode = (ValidateCode) (sessionStrategy.getAttribute(request, SESSION_KEY_PREFIX + validateCodeType.toUpperCase()));
return null;
}
@Override
public void remove(ServletWebRequest request, String validateCodeType) {
sessionStrategy.removeAttribute(request, SESSION_KEY_PREFIX + validateCodeType.toUpperCase());
}
}
@@ -3,6 +3,7 @@ package com.micro.fast.security.browser.config;
import com.micro.fast.security.core.authentication.mobile.SmsCodeAuthenticationSecurityConfig;
import com.micro.fast.security.core.master.SecurityConstants;
import com.micro.fast.security.core.master.SecurityProperties;
import com.micro.fast.security.core.validate.code.ValidateCodeRepository;
import com.micro.fast.security.core.validate.code.filter.ValidateCodeFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -78,6 +79,8 @@ public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private ValidateCodeRepository validateCodeRepository;
/**
* 短信验证码登录的配置
@@ -100,6 +103,7 @@ public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
validateCodeFilter.setAuthenticationFailureHandler(msAuthenticationFailureHandler);
validateCodeFilter.setSecurityProperties(securityProperties);
validateCodeFilter.setValidateCodeRepository(validateCodeRepository);
validateCodeFilter.afterPropertiesSet();
http
//添加自定义的验证码处理器,在用户密码校验过滤器之前
@@ -0,0 +1,37 @@
package com.micro.fast.security.core.validate.code;
import com.micro.fast.security.core.validate.code.pojo.ValidateCode;
import org.springframework.web.context.request.ServletWebRequest;
/**
* 验证码的存储
* @author lsy
*/
public interface ValidateCodeRepository {
String SMS_CODE = "sms";
String IMAGE_CODE = "image";
/**
* 保存验证码
* @param request
* @param code
* @param validateCodeType
*/
void save(ServletWebRequest request, ValidateCode code,String validateCodeType);
/**
* 获取验证码
* @param request
* @param validateCodeType
* @return
*/
ValidateCode get(ServletWebRequest request,String validateCodeType);
/**
* 删除验证码
* @param request
* @param validateCodeType
*/
void remove(ServletWebRequest request,String validateCodeType);
}
@@ -2,14 +2,17 @@ package com.micro.fast.security.core.validate.code.filter;
import com.micro.fast.security.core.master.SecurityConstants;
import com.micro.fast.security.core.master.SecurityProperties;
import com.micro.fast.security.core.validate.code.ValidateCodeRepository;
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.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;
@@ -32,8 +35,13 @@ public class ValidateCodeFilter extends OncePerRequestFilter implements Initiali
public static final String IMAGE = "IMAGE";
public static final String SMS = "SMS";
private AuthenticationFailureHandler authenticationFailureHandler;
private ValidateCodeRepository validateCodeRepository;
/**
* session操作工具类
*/
@@ -125,7 +133,10 @@ public class ValidateCodeFilter extends OncePerRequestFilter implements Initiali
* @param validateType
*/
private void validate(ServletWebRequest servletWebRequest, String validateType) {
ValidateCode validateCode = (ValidateCode) (sessionStrategy.getAttribute(servletWebRequest, ValidateCodeProcessor.SESSION_KEY_PREFIX + validateType));
//
//获取存储的验证码
ValidateCode validateCode = validateCodeRepository.get(servletWebRequest, validateType.toLowerCase());
String code = servletWebRequest.getParameter(validateType.toLowerCase()+"Code");
//session和用户传入的验证码不能为空
@@ -140,7 +151,7 @@ public class ValidateCodeFilter extends OncePerRequestFilter implements Initiali
throw new ValidateCodeException("验证码已过期");
}
//如果校验通过之后删除验证码
sessionStrategy.removeAttribute(servletWebRequest, ValidateCodeProcessor.SESSION_KEY_PREFIX + validateType);
validateCodeRepository.remove(servletWebRequest,validateType.toLowerCase());
}
public AuthenticationFailureHandler getAuthenticationFailureHandler() {
@@ -166,4 +177,11 @@ public class ValidateCodeFilter extends OncePerRequestFilter implements Initiali
public void setSecurityProperties(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
public ValidateCodeRepository getValidateCodeRepository() {
return validateCodeRepository;
}
public void setValidateCodeRepository(ValidateCodeRepository validateCodeRepository) {
this.validateCodeRepository = validateCodeRepository;
}
}
@@ -1,5 +1,6 @@
package com.micro.fast.security.core.validate.code.processor.impl;
import com.micro.fast.security.core.validate.code.ValidateCodeRepository;
import com.micro.fast.security.core.validate.code.pojo.ValidateCode;
import com.micro.fast.security.core.validate.code.processor.ValidateCodeProcessor;
import com.micro.fast.security.core.validate.code.util.ValidateCodeUtil;
@@ -20,10 +21,7 @@ import java.util.Map;
public abstract class AbstractValidateCodeProcessor <T extends ValidateCode> implements ValidateCodeProcessor {
/**
* 操作session的工具类
*/
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
/**
* 收集系统中所有{@link ValidateCodeUtil}的实现,并将bean的名字作为key
@@ -33,6 +31,9 @@ public abstract class AbstractValidateCodeProcessor <T extends ValidateCode> imp
private static final String VALIDATE_CODE_UTIL_SUFFIX = "CodeUtil";
@Autowired
private ValidateCodeRepository validateCodeRepository;
@Override
public void create(ServletWebRequest servletWebRequest) throws ServletRequestBindingException, IOException {
T generateCode = generateCode(servletWebRequest);
@@ -63,10 +64,7 @@ public abstract class AbstractValidateCodeProcessor <T extends ValidateCode> imp
ValidateCode saveValidateCode = new ValidateCode();
saveValidateCode.setCode(validateCode.getCode());
saveValidateCode.setExpireTime(validateCode.getExpireTime());
//向session中存储验证码
sessionStrategy.setAttribute(servletWebRequest
,SESSION_KEY_PREFIX+getProcessorType(servletWebRequest).toUpperCase()
,saveValidateCode);
validateCodeRepository.save(servletWebRequest,saveValidateCode,getProcessorType(servletWebRequest));
}
/**