diff --git a/boot-starter/boot-starter-security/boot-starter-security-app/src/main/java/com/micro/fast/security/app/AppValidateCodeRepository.java b/boot-starter/boot-starter-security/boot-starter-security-app/src/main/java/com/micro/fast/security/app/AppValidateCodeRepository.java new file mode 100644 index 0000000..3d72ae0 --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-app/src/main/java/com/micro/fast/security/app/AppValidateCodeRepository.java @@ -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 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; + } +} diff --git a/boot-starter/boot-starter-security/boot-starter-security-app/src/main/java/com/micro/fast/security/app/config/authorization2/AuthorizationResourceConfig.java b/boot-starter/boot-starter-security/boot-starter-security-app/src/main/java/com/micro/fast/security/app/config/authorization2/AuthorizationResourceConfig.java index 790a66b..a1657b2 100644 --- a/boot-starter/boot-starter-security/boot-starter-security-app/src/main/java/com/micro/fast/security/app/config/authorization2/AuthorizationResourceConfig.java +++ b/boot-starter/boot-starter-security/boot-starter-security-app/src/main/java/com/micro/fast/security/app/config/authorization2/AuthorizationResourceConfig.java @@ -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) + ; + } } diff --git a/boot-starter/boot-starter-security/boot-starter-security-app/src/main/java/com/micro/fast/security/app/config/component/MsAuthenticationSuccessHandler.java b/boot-starter/boot-starter-security/boot-starter-security-app/src/main/java/com/micro/fast/security/app/config/component/MsAuthenticationSuccessHandler.java index 0d53e14..98416f2 100644 --- a/boot-starter/boot-starter-security/boot-starter-security-app/src/main/java/com/micro/fast/security/app/config/component/MsAuthenticationSuccessHandler.java +++ b/boot-starter/boot-starter-security/boot-starter-security-app/src/main/java/com/micro/fast/security/app/config/component/MsAuthenticationSuccessHandler.java @@ -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"; + } } diff --git a/boot-starter/boot-starter-security/boot-starter-security-browser/src/main/java/com/micro/fast/security/browser/BrowserValidateCodeRepository.java b/boot-starter/boot-starter-security/boot-starter-security-browser/src/main/java/com/micro/fast/security/browser/BrowserValidateCodeRepository.java new file mode 100644 index 0000000..2cc8488 --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-browser/src/main/java/com/micro/fast/security/browser/BrowserValidateCodeRepository.java @@ -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()); + } +} diff --git a/boot-starter/boot-starter-security/boot-starter-security-browser/src/main/java/com/micro/fast/security/browser/config/BrowserSecurityConfig.java b/boot-starter/boot-starter-security/boot-starter-security-browser/src/main/java/com/micro/fast/security/browser/config/BrowserSecurityConfig.java index 7582724..dbbc701 100644 --- a/boot-starter/boot-starter-security/boot-starter-security-browser/src/main/java/com/micro/fast/security/browser/config/BrowserSecurityConfig.java +++ b/boot-starter/boot-starter-security/boot-starter-security-browser/src/main/java/com/micro/fast/security/browser/config/BrowserSecurityConfig.java @@ -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 //添加自定义的验证码处理器,在用户密码校验过滤器之前 diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/ValidateCodeRepository.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/ValidateCodeRepository.java new file mode 100644 index 0000000..20ff1bc --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/ValidateCodeRepository.java @@ -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); +} 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 981cc4d..32af7de 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 @@ -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; + } } diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/processor/impl/AbstractValidateCodeProcessor.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/processor/impl/AbstractValidateCodeProcessor.java index 479e1ed..dbf0de9 100644 --- a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/processor/impl/AbstractValidateCodeProcessor.java +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/validate/code/processor/impl/AbstractValidateCodeProcessor.java @@ -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 implements ValidateCodeProcessor { - /** - * 操作session的工具类 - */ - private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); + /** * 收集系统中所有{@link ValidateCodeUtil}的实现,并将bean的名字作为key @@ -33,6 +31,9 @@ public abstract class AbstractValidateCodeProcessor 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 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)); } /**