【转载】spring-security-oauth2(八) 短信密码登录重构

短信密码登录重构

  • 抽取重复的代码
  • 将一些变量用常量或枚举类管理起来
  • 对一些流程进行抽象封装,方便扩展

比如前面我们的短信和验证码过滤器流程很相似,还有系统置不够清晰糅杂在一起了等。

关于用户名密码登录和短信登录表单提交的url地址,不需要真实存在, 
因为这个是提供这两个特定过滤器框架特定的拦截点。只有提交到指定的拦截点, 
才会进入认证功能服务。

重构

验证码过滤器重构

CaptchaUnionFilter 将短信和图形验证码的过滤器合并成一个 ,将原来的2个过滤器删除


 
 
  1. package com.rui.tiger.auth.core.captcha;
  2. import com.rui.tiger.auth.core.authentication.TigerAuthenticationFailureHandler;
  3. import com.rui.tiger.auth.core.properties.SecurityConstants;
  4. import com.rui.tiger.auth.core.properties.SecurityProperties;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.apache.commons.lang.StringUtils;
  7. import org.springframework.beans.factory.InitializingBean;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.stereotype.Component;
  10. import org.springframework.util.AntPathMatcher;
  11. import org.springframework.web.context.request.ServletWebRequest;
  12. import org.springframework.web.filter.OncePerRequestFilter;
  13. import javax.servlet.FilterChain;
  14. import javax.servlet.ServletException;
  15. import javax.servlet.http.HttpServletRequest;
  16. import javax.servlet.http.HttpServletResponse;
  17. import java.io.IOException;
  18. import java.util.HashMap;
  19. import java.util.Map;
  20. import java.util.Set;
  21. /**
  22. * 重构的验证码拦截器 (图片验证+短信验证)
  23. *
  24. * @author CaiRui
  25. * @date 2018-12-25 8:37
  26. */
  27. @Component( "captchaUnionFilter")
  28. @Slf4j
  29. public class CaptchaUnionFilter extends OncePerRequestFilter implements InitializingBean {
  30. @Autowired
  31. private SecurityProperties securityProperties;
  32. @Autowired
  33. private TigerAuthenticationFailureHandler tigerAuthenticationFailureHandler;
  34. @Autowired
  35. private CaptchaProcessorHolder captchaProcessorHolder;
  36. /**
  37. * 存放所有需要校验验证码的url
  38. * key: 验证码类型
  39. * value: 验证路径
  40. */
  41. private Map<String, CaptchaTypeEnum> urlMap = new HashMap<>();
  42. /**
  43. * 验证请求url与配置的url是否匹配的工具类
  44. */
  45. private AntPathMatcher pathMatcher = new AntPathMatcher();
  46. /**
  47. * bean初始化后调用
  48. *
  49. * @throws ServletException
  50. */
  51. @Override
  52. public void afterPropertiesSet() throws ServletException {
  53. super.afterPropertiesSet();
  54. //短信验证码
  55. urlMap.put(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE, CaptchaTypeEnum.SMS);
  56. addVaildateUrlToUrlMap(securityProperties.getCaptcha().getSms().getInterceptUrl(), CaptchaTypeEnum.SMS);
  57. //图片验证码
  58. urlMap.put(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM, CaptchaTypeEnum.IMAGE);
  59. addVaildateUrlToUrlMap(securityProperties.getCaptcha().getImage().getInterceptImageUrl(), CaptchaTypeEnum.IMAGE);
  60. }
  61. /**
  62. * 验证码拦截核心逻辑
  63. *
  64. * @param request
  65. * @param response
  66. * @param filterChain
  67. * @throws ServletException
  68. * @throws IOException
  69. */
  70. @Override
  71. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
  72. CaptchaTypeEnum captchaTypeEnum = getCaptchaTypeWithRequestUrl(request);
  73. if (captchaTypeEnum != null) {
  74. try {
  75. log.info( "校验请求【" + request.getRequestURI() + "】" + captchaTypeEnum.getDesc() + "验证码");
  76. captchaProcessorHolder.findCaptchaProcessor(captchaTypeEnum)
  77. .validate( new ServletWebRequest(request, response),captchaTypeEnum);
  78. } catch (CaptchaException captchaException) {
  79. log.info( "验证码校验异常", captchaException.getMessage());
  80. tigerAuthenticationFailureHandler.onAuthenticationFailure(request, response, captchaException);
  81. return;
  82. }
  83. //filterChain.doFilter(request, response); 就是null后续都不执行 被坑了很久
  84. }
  85. filterChain.doFilter(request, response);
  86. }
  87. /**
  88. * 根据请求路径返回验证码类型
  89. *
  90. * @param request
  91. * @return
  92. */
  93. private CaptchaTypeEnum getCaptchaTypeWithRequestUrl(HttpServletRequest request) {
  94. String requestUrl = request.getRequestURI(); //返回除去host(域名或者ip)部分的路径
  95. if (!StringUtils.equalsIgnoreCase( "get", request.getMethod())) {
  96. Set<String> urlSet = urlMap.keySet();
  97. for (String url : urlSet) {
  98. if (pathMatcher.match(url, requestUrl)) {
  99. return urlMap.get(url);
  100. }
  101. }
  102. }
  103. return null;
  104. }
  105. /**
  106. * 不同类型拦截路径赋值
  107. *
  108. * @param interceptUrl
  109. * @param captchaTypeEnum
  110. */
  111. private void addVaildateUrlToUrlMap(String interceptUrl, CaptchaTypeEnum captchaTypeEnum) {
  112. if (StringUtils.isNotBlank(interceptUrl)) {
  113. String[] interceptUrlArray = StringUtils.split(interceptUrl, ",");
  114. for (String url : interceptUrlArray) {
  115. urlMap.put(url, captchaTypeEnum);
  116. }
  117. }
  118. }
  119. }

CaptchaProcessorHolder 验证码处理器持有者 根据枚举类型查找验证码处理器  


 
 
  1. package com.rui.tiger.auth.core.captcha;
  2. import com.rui.tiger.auth.core.support.strategy.StrategyContainerImpl;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.springframework.stereotype.Component;
  5. /**
  6. * CaptchaProcessor接口 持有者
  7. *
  8. * @author CaiRui
  9. * @date 2018-12-25 9:02
  10. */
  11. @Component
  12. @Slf4j
  13. public class CaptchaProcessorHolder {
  14. /**
  15. * 获取CaptchaProcessor接口实现类
  16. *
  17. * @param name
  18. * @return
  19. */
  20. CaptchaProcessor findCaptchaProcessor(String name) {
  21. CaptchaTypeEnum captchaTypeEnum = CaptchaTypeEnum.forCode(name);
  22. if (captchaTypeEnum == null) {
  23. log.error( "验证码类型枚举" + name + "不存在");
  24. throw new CaptchaException( "验证码类型枚举类" + name + "不存在");
  25. }
  26. return findCaptchaProcessor(captchaTypeEnum);
  27. }
  28. /**
  29. * 获取CaptchaProcessor 接口实现类
  30. *
  31. * @param captchaTypeEnum
  32. * @return
  33. */
  34. CaptchaProcessor findCaptchaProcessor(CaptchaTypeEnum captchaTypeEnum) {
  35. if (captchaTypeEnum == null) {
  36. throw new CaptchaException( "验证码类型枚举类不存在");
  37. }
  38. CaptchaProcessor captchaProcessor = StrategyContainerImpl.getStrategy(CaptchaProcessor.class, captchaTypeEnum);
  39. if (captchaProcessor == null) {
  40. log.error( "{}处理器不存在", captchaTypeEnum.getDesc());
  41. throw new CaptchaException(captchaTypeEnum.getDesc() + "处理器不存在");
  42. }
  43. log.info( "{}处理器获取", captchaTypeEnum.getDesc());
  44. return captchaProcessor;
  45. }
  46. }
CaptchaProcessor 验证码处理器接口 新增校验逻辑

 
 
  1. package com.rui.tiger.auth.core.captcha;
  2. import com.rui.tiger.auth.core.support.strategy.IStrategy;
  3. import org.springframework.web.context.request.ServletWebRequest;
  4. /**
  5. * 验证码处理器接口
  6. * @author CaiRui
  7. * @Date 2018/12/15 17:53
  8. */
  9. public interface CaptchaProcessor extends IStrategy<CaptchaTypeEnum> {
  10. /**
  11. * 验证码缓存KEY值前缀
  12. */
  13. String CAPTCHA_SESSION_KEY= "captcha_session_key_";
  14. /**
  15. * 创建验证码
  16. * @param request 封装请求和响应
  17. * @throws Exception
  18. */
  19. void create(ServletWebRequest request) throws Exception;
  20. /**
  21. * 校验验证码
  22. * @param servletWebRequest
  23. * @param captchaTypeEnum
  24. */
  25. void validate(ServletWebRequest servletWebRequest, CaptchaTypeEnum captchaTypeEnum) throws CaptchaException ;
  26. }
AbstractCaptchaProcessor 将验证逻辑放到抽象父类中进行统一验证

 
 
  1. package com.rui.tiger.auth.core.captcha;
  2. import org.apache.commons.lang.StringUtils;
  3. import org.springframework.social.connect.web.HttpSessionSessionStrategy;
  4. import org.springframework.social.connect.web.SessionStrategy;
  5. import org.springframework.web.bind.ServletRequestBindingException;
  6. import org.springframework.web.bind.ServletRequestUtils;
  7. import org.springframework.web.context.request.ServletWebRequest;
  8. import java.io.IOException;
  9. /**
  10. * 验证码处理器抽象父类
  11. *
  12. * @author CaiRui
  13. * @Date 2018/12/15 18:21
  14. */
  15. public abstract class AbstractCaptchaProcessor<C extends CaptchaVo> implements CaptchaProcessor {
  16. private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
  17. /**
  18. * 创建验证码
  19. *
  20. * @param request 封装请求和响应
  21. * @throws Exception
  22. */
  23. @Override
  24. public void create(ServletWebRequest request) throws Exception {
  25. //生成
  26. C captcha = generateCaptcha(request);
  27. //保存
  28. save(request, captcha);
  29. //发送
  30. send(request, captcha);
  31. }
  32. /**
  33. * 短信和手机验证码的通用验证
  34. *
  35. * @param request
  36. * @param captchaType 验证码
  37. */
  38. @Override
  39. public void validate(ServletWebRequest request, CaptchaTypeEnum captchaType) throws CaptchaException {
  40. String sessionKey = getSessionKey(captchaType);
  41. C captchaInSession = (C) sessionStrategy.getAttribute(request, sessionKey);
  42. String captchaInRequest;
  43. try {
  44. captchaInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),
  45. captchaType.getParamNameOnValidate());
  46. } catch (ServletRequestBindingException e) {
  47. throw new CaptchaException( "获取验证码的值失败");
  48. }
  49. if (StringUtils.isBlank(captchaInRequest)) {
  50. throw new CaptchaException(captchaType + "验证码的值不能为空");
  51. }
  52. if (captchaInSession == null) {
  53. throw new CaptchaException(captchaType + "验证码不存在");
  54. }
  55. if (captchaInSession.isExpried()) {
  56. sessionStrategy.removeAttribute(request, sessionKey);
  57. throw new CaptchaException(captchaType + "验证码已过期");
  58. }
  59. if (!StringUtils.equals(captchaInSession.getCode(), captchaInRequest)) {
  60. throw new CaptchaException(captchaType + "验证码不匹配");
  61. }
  62. //验证成功清除缓存中的key
  63. sessionStrategy.removeAttribute(request, sessionKey);
  64. }
  65. /**
  66. * 生成验证码
  67. * @param request
  68. * @return
  69. */
  70. protected abstract C generateCaptcha(ServletWebRequest request);
  71. /**
  72. * 发送验证码
  73. * @param request
  74. * @param captcha
  75. */
  76. protected abstract void send(ServletWebRequest request, C captcha) throws IOException, ServletRequestBindingException;
  77. /**
  78. * 保存验证码到session中
  79. * @param request
  80. * @param captcha
  81. */
  82. private void save(ServletWebRequest request, C captcha) {
  83. sessionStrategy.setAttribute(request, CAPTCHA_SESSION_KEY +getCondition().getCode(), captcha);
  84. }
  85. /**
  86. * 获取验证码session key值
  87. *
  88. * @param captchaType
  89. * @return
  90. */
  91. private String getSessionKey(CaptchaTypeEnum captchaType) {
  92. return CAPTCHA_SESSION_KEY + captchaType.getCode();
  93. }
  94. }

配置重构 


AbstractChannelSecurityConfig 密码登录的抽取到core项目中 这个是浏览器和app项目共同的配置项


 
 
  1. package com.rui.tiger.auth.core.config;
  2. import com.rui.tiger.auth.core.properties.SecurityConstants;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  5. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  6. import org.springframework.security.web.authentication.AuthenticationFailureHandler;
  7. import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
  8. /**
  9. * 密码登录的通用安全配置
  10. * @author CaiRui
  11. * @date 2018-12-26 18:11
  12. */
  13. public class AbstractChannelSecurityConfig extends WebSecurityConfigurerAdapter {
  14. @Autowired
  15. private AuthenticationSuccessHandler tigerAuthenticationSuccessHandler;
  16. @Autowired
  17. private AuthenticationFailureHandler tigerAuthenticationFailureHandler;
  18. /**
  19. * 密码登录配置
  20. * @param http
  21. * @throws Exception
  22. */
  23. protected void applyPasswordAuthenticationConfig(HttpSecurity http) throws Exception {
  24. http.formLogin()
  25. .loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
  26. .loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM) //
  27. .successHandler(tigerAuthenticationSuccessHandler)
  28. .failureHandler(tigerAuthenticationFailureHandler);
  29. }
  30. }
CaptchaSecurityConfig 验证码过滤器配置

 
 
  1. package com.rui.tiger.auth.core.config;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
  4. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  5. import org.springframework.security.web.DefaultSecurityFilterChain;
  6. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  7. import org.springframework.stereotype.Component;
  8. import javax.servlet.Filter;
  9. /**
  10. * 验证码过滤器配置
  11. * @author CaiRui
  12. * @date 2018-12-26 18:22
  13. */
  14. @Component( "captchaSecurityConfig")
  15. public class CaptchaSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
  16. /**
  17. * 重构的验证码拦截器 (图片验证+短信验证)
  18. */
  19. @Autowired
  20. private Filter captchaUnionFilter;
  21. /**
  22. * 验证码验证放在密码登录之前
  23. * @param http
  24. * @throws Exception
  25. */
  26. @Override
  27. public void configure(HttpSecurity http) throws Exception {
  28. http.addFilterBefore(captchaUnionFilter, UsernamePasswordAuthenticationFilter.class);
  29. }
  30. }
BrowserSecurityConfig 浏览器配置只保留自己特有的如记住我等

 
 
  1. package com.rui.tiger.auth.browser.config;
  2. import com.rui.tiger.auth.core.config.AbstractChannelSecurityConfig;
  3. import com.rui.tiger.auth.core.config.CaptchaSecurityConfig;
  4. import com.rui.tiger.auth.core.config.SmsAuthenticationSecurityConfig;
  5. import com.rui.tiger.auth.core.properties.SecurityConstants;
  6. import com.rui.tiger.auth.core.properties.SecurityProperties;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.context.annotation.Bean;
  9. import org.springframework.context.annotation.Configuration;
  10. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  11. import org.springframework.security.core.userdetails.UserDetailsService;
  12. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  13. import org.springframework.security.crypto.password.PasswordEncoder;
  14. import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
  15. import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
  16. import javax.sql.DataSource;
  17. /**
  18. * 浏览器security配置类
  19. *
  20. * @author CaiRui
  21. * @date 2018-12-4 8:41
  22. */
  23. @Configuration
  24. public class BrowserSecurityConfig extends AbstractChannelSecurityConfig {
  25. @Autowired
  26. private SecurityProperties securityProperties;
  27. @Autowired
  28. private DataSource dataSource;
  29. @Autowired
  30. private UserDetailsService userDetailsService;
  31. @Autowired
  32. private SmsAuthenticationSecurityConfig smsAuthenticationSecurityConfig; //短信登陆配置
  33. @Autowired
  34. private CaptchaSecurityConfig captchaSecurityConfig; //验证码配置
  35. /**
  36. * 密码加密解密
  37. *
  38. * @return
  39. */
  40. @Bean
  41. public PasswordEncoder passwordEncoder() {
  42. return new BCryptPasswordEncoder();
  43. }
  44. /**
  45. * 记住我持久化数据源
  46. * JdbcTokenRepositoryImpl CREATE_TABLE_SQL 建表语句可以先在数据库中执行
  47. *
  48. * @return
  49. */
  50. @Bean
  51. public PersistentTokenRepository persistentTokenRepository() {
  52. JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
  53. jdbcTokenRepository.setDataSource(dataSource);
  54. //第一次会执行CREATE_TABLE_SQL建表语句 后续会报错 可以关掉
  55. //jdbcTokenRepository.setCreateTableOnStartup(true);
  56. return jdbcTokenRepository;
  57. }
  58. /**
  59. * 核心配置
  60. * @param http
  61. * @throws Exception
  62. */
  63. @Override
  64. protected void configure(HttpSecurity http) throws Exception {
  65. /**
  66. * 表单密码配置
  67. */
  68. applyPasswordAuthenticationConfig(http);
  69. http
  70. .apply(captchaSecurityConfig)
  71. .and()
  72. .apply(smsAuthenticationSecurityConfig)
  73. .and()
  74. .rememberMe()
  75. .tokenRepository(persistentTokenRepository())
  76. .tokenValiditySeconds(securityProperties.getBrowser().getRemberMeSeconds())
  77. .userDetailsService(userDetailsService)
  78. .and()
  79. .authorizeRequests()
  80. .antMatchers(
  81. SecurityConstants.DEFAULT_UNAUTHENTICATION_URL, //权限认证
  82. SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE, //手机
  83. securityProperties.getBrowser().getLoginPage(), //登录页面
  84. SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+ "/*") // /captcha/* 验证码放行
  85. .permitAll()
  86. .anyRequest()
  87. .authenticated()
  88. .and()
  89. .csrf().disable();
  90. }
  91. }

常量字典

 SecurityConstants 权限常量配置类


 
 
  1. package com.rui.tiger.auth.core.properties;
  2. /**
  3. * 权限常量配置类
  4. * @author CaiRui
  5. * @date 2018-12-25 8:52
  6. */
  7. public class SecurityConstants {
  8. /**
  9. * 默认的处理验证码的url前缀
  10. */
  11. public static final String DEFAULT_VALIDATE_CODE_URL_PREFIX = "/captcha";
  12. /**
  13. * 当请求需要身份认证时,默认跳转的url
  14. *
  15. */
  16. public static final String DEFAULT_UNAUTHENTICATION_URL = "/authentication/require";
  17. /**
  18. * 默认的用户名密码登录请求处理url
  19. */
  20. public static final String DEFAULT_LOGIN_PROCESSING_URL_FORM = "/authentication/form";
  21. /**
  22. * 默认的手机验证码登录请求处理url
  23. */
  24. public static final String DEFAULT_LOGIN_PROCESSING_URL_MOBILE = "/authentication/mobile";
  25. /**
  26. * 默认登录页面
  27. *
  28. */
  29. public static final String DEFAULT_LOGIN_PAGE_URL = "/tiger-login.html";
  30. /**
  31. * 验证图片验证码时,http请求中默认的携带图片验证码信息的参数的名称
  32. */
  33. public static final String DEFAULT_PARAMETER_NAME_CODE_IMAGE = "imageCode";
  34. /**
  35. * 验证短信验证码时,http请求中默认的携带短信验证码信息的参数的名称
  36. */
  37. public static final String DEFAULT_PARAMETER_NAME_CODE_SMS = "smsCode";
  38. /**
  39. * 发送短信验证码 或 验证短信验证码时,传递手机号的参数的名称
  40. */
  41. public static final String DEFAULT_PARAMETER_NAME_MOBILE = "mobile";
  42. }

ok 重构核心代码完成 我们来看下测试结果,首先打开登录界面,在没有发送验证码前先随便填个短信验证码试试

输入短信验证码看看

ok 说明我们的配置通过了,其它请自行测试。

总结: 

文章转载至:https://blog.csdn.net/ahcr1026212/article/details/85244082

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值