【转载】spring-security-oauth2(七) 自定义短信登陆开发

短信登陆开发

原理

基本原理:SmsAuthenticationFilter接受请求生成SmsAuthenticationToken,然后交给系统的AuthenticationManager进行管理,然后找到SmsAuthenticationProvider,然后再调用UserDetailsService进行短信验证,SmsAuthenticationSecurityConfig进行配置 SmsCaptchaFilter验证码过滤器 在请求之前进行验证验证

短信验证主要是复制参考用户名密码流程,参考前面的源码分析。

代码

SmsAuthenticationToken


 
 
  1. package com.rui.tiger.auth.core.authentication.mobile;
  2. import org.springframework.security.authentication.AbstractAuthenticationToken;
  3. import org.springframework.security.core.GrantedAuthority;
  4. import java.util.Collection;
  5. /**
  6. * 手机token
  7. * 参照:UsernamePasswordAuthenticationToken
  8. * @author CaiRui
  9. * @Date 2018/12/15 22:28
  10. */
  11. public class SmsAuthenticationToken extends AbstractAuthenticationToken {
  12. private static final long serialVersionUID = 500L;
  13. private final Object principal; //用户名
  14. //private Object credentials; 密码 手机登录验证码在登录前已经验证 考虑手机验证码通用 没有放到这里
  15. /**
  16. * 没有认证成功
  17. * @param mobile 手机号
  18. */
  19. public SmsAuthenticationToken(Object mobile) {
  20. super((Collection) null);
  21. this.principal = mobile;
  22. this.setAuthenticated( false);
  23. }
  24. /**
  25. * 认证成功同时进行权限设置
  26. * @param principal
  27. * @param authorities
  28. */
  29. public SmsAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
  30. super(authorities);
  31. this.principal = principal;
  32. super.setAuthenticated( true);
  33. }
  34. public Object getCredentials() {
  35. return null;
  36. }
  37. public Object getPrincipal() {
  38. return this.principal;
  39. }
  40. public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
  41. if(isAuthenticated) {
  42. throw new IllegalArgumentException( "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
  43. } else {
  44. super.setAuthenticated( false);
  45. }
  46. }
  47. public void eraseCredentials() {
  48. super.eraseCredentials();
  49. }
  50. }

SmsAuthenticationFilter


 
 
  1. package com.rui.tiger.auth.core.authentication.mobile;
  2. import org.springframework.security.authentication.AuthenticationServiceException;
  3. import org.springframework.security.core.Authentication;
  4. import org.springframework.security.core.AuthenticationException;
  5. import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
  6. import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
  7. import org.springframework.util.Assert;
  8. import javax.servlet.http.HttpServletRequest;
  9. import javax.servlet.http.HttpServletResponse;
  10. /**
  11. * 手机登陆过滤器
  12. * 参照:UsernamePasswordAuthenticationFilter
  13. * @author CaiRui
  14. * @Date 2018/12/16 10:39
  15. */
  16. public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
  17. // ~ Static fields/initializers
  18. // =====================================================================================
  19. public static final String TIGER_SECURITY_FORM_MOBILE_KEY = "mobile";
  20. private String mobileParameter = TIGER_SECURITY_FORM_MOBILE_KEY;
  21. //public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
  22. // private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
  23. private boolean postOnly = true;
  24. // ~ Constructors
  25. // ===================================================================================================
  26. //TODO /authentication/mobile 这些参数应该配置到字典中待优化
  27. public SmsAuthenticationFilter() {
  28. // 拦截该路径,如果是访问该路径,则标识是需要短信登录
  29. super( new AntPathRequestMatcher( "/authentication/mobile", "POST"));
  30. }
  31. // ~ Methods
  32. // ========================================================================================================
  33. public Authentication attemptAuthentication(HttpServletRequest request,
  34. HttpServletResponse response) throws AuthenticationException {
  35. if (postOnly && !request.getMethod().equals( "POST")) {
  36. throw new AuthenticationServiceException(
  37. "Authentication method not supported: " + request.getMethod());
  38. }
  39. String mobile = obtainMobile(request);
  40. if (mobile == null) {
  41. mobile = "";
  42. }
  43. mobile = mobile.trim();
  44. SmsAuthenticationToken authRequest = new SmsAuthenticationToken(mobile);
  45. // Allow subclasses to set the "details" property
  46. setDetails(request, authRequest);
  47. return this.getAuthenticationManager().authenticate(authRequest);
  48. }
  49. /**
  50. * Enables subclasses to override the composition of the username, such as by
  51. * including additional values and a separator.
  52. *
  53. * @param request so that request attributes can be retrieved
  54. *
  55. * @return the username that will be presented in the <code>Authentication</code>
  56. * request token to the <code>AuthenticationManager</code>
  57. */
  58. protected String obtainMobile(HttpServletRequest request) {
  59. return request.getParameter(mobileParameter);
  60. }
  61. /**
  62. * Provided so that subclasses may configure what is put into the authentication
  63. * request's details property.
  64. *
  65. * @param request that an authentication request is being created for
  66. * @param authRequest the authentication request object that should have its details
  67. * set
  68. */
  69. protected void setDetails(HttpServletRequest request,
  70. SmsAuthenticationToken authRequest) {
  71. authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
  72. }
  73. /**
  74. * Sets the parameter name which will be used to obtain the mobile from the login
  75. * request.
  76. *
  77. * @param mobileParameter the parameter name. Defaults to "mobile".
  78. */
  79. public void setMobileParameter(String mobileParameter) {
  80. Assert.hasText(mobileParameter, "mobile parameter must not be empty or null");
  81. this.mobileParameter = mobileParameter;
  82. }
  83. public String getMobileParameter() {
  84. return mobileParameter;
  85. }
  86. /**
  87. * Defines whether only HTTP POST requests will be allowed by this filter. If set to
  88. * true, and an authentication request is received which is not a POST request, an
  89. * exception will be raised immediately and authentication will not be attempted. The
  90. * <tt>unsuccessfulAuthentication()</tt> method will be called as if handling a failed
  91. * authentication.
  92. * <p>
  93. * Defaults to <tt>true</tt> but may be overridden by subclasses.
  94. */
  95. public void setPostOnly(boolean postOnly) {
  96. this.postOnly = postOnly;
  97. }
  98. }

SmsAuthenticationProvider


 
 
  1. package com.rui.tiger.auth.core.authentication.mobile;
  2. import lombok.Getter;
  3. import lombok.Setter;
  4. import org.springframework.security.authentication.AuthenticationProvider;
  5. import org.springframework.security.authentication.InternalAuthenticationServiceException;
  6. import org.springframework.security.core.Authentication;
  7. import org.springframework.security.core.AuthenticationException;
  8. import org.springframework.security.core.userdetails.UserDetails;
  9. import org.springframework.security.core.userdetails.UserDetailsService;
  10. /**
  11. * @author CaiRui
  12. * @Date 2018/12/16 10:38
  13. */
  14. public class SmsAuthenticationProvider implements AuthenticationProvider {
  15. @Override
  16. public Authentication authenticate(Authentication authentication) throws AuthenticationException {
  17. SmsAuthenticationToken smsCaptchaAuthenticationToken= (SmsAuthenticationToken) authentication;
  18. UserDetails user=userDetailsService.loadUserByUsername((String) smsCaptchaAuthenticationToken.getPrincipal());
  19. if(user== null){
  20. throw new InternalAuthenticationServiceException( "无法获取用户信息");
  21. }
  22. //认证通过
  23. SmsAuthenticationToken authenticationTokenResult= new SmsAuthenticationToken(user,user.getAuthorities());
  24. //将之前未认证的请求放进认证后的Token中
  25. authenticationTokenResult.setDetails(smsCaptchaAuthenticationToken.getDetails());
  26. return authenticationTokenResult;
  27. }
  28. //@Autowired
  29. @Getter
  30. @Setter
  31. private UserDetailsService userDetailsService; //
  32. /**
  33. * AuthenticationManager 验证该Provider是否支持 认证
  34. * @param aClass
  35. * @return
  36. */
  37. @Override
  38. public boolean supports(Class<?> aClass) {
  39. return aClass.isAssignableFrom(SmsAuthenticationToken.class);
  40. }
  41. }
SmsAuthenticationSecurityConfig 

 
 
  1. package com.rui.tiger.auth.core.authentication.mobile;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.security.authentication.AuthenticationManager;
  4. import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
  5. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  6. import org.springframework.security.core.userdetails.UserDetailsService;
  7. import org.springframework.security.web.DefaultSecurityFilterChain;
  8. import org.springframework.security.web.authentication.AuthenticationFailureHandler;
  9. import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
  10. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  11. import org.springframework.stereotype.Component;
  12. /**
  13. * 手机权限配置类
  14. * @author CaiRui
  15. * @Date 2018/12/16 13:42
  16. */
  17. @Component
  18. public class SmsAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
  19. @Autowired
  20. private AuthenticationFailureHandler authenticationFailureHandler;
  21. @Autowired
  22. private AuthenticationSuccessHandler authenticationSuccessHandler;
  23. //实现类怎么确定? 自定义的实现??
  24. @Autowired
  25. private UserDetailsService userDetailsService;
  26. @Override
  27. public void configure(HttpSecurity http) throws Exception {
  28. SmsAuthenticationFilter filter = new SmsAuthenticationFilter();
  29. filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
  30. filter.setAuthenticationFailureHandler(authenticationFailureHandler);
  31. filter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
  32. SmsAuthenticationProvider smsAuthenticationProvider = new SmsAuthenticationProvider();
  33. smsAuthenticationProvider.setUserDetailsService(userDetailsService);
  34. http
  35. // 注册到AuthenticationManager中去
  36. .authenticationProvider(smsAuthenticationProvider)
  37. // 添加到 UsernamePasswordAuthenticationFilter 之后
  38. // 貌似所有的入口都是 UsernamePasswordAuthenticationFilter
  39. // 然后UsernamePasswordAuthenticationFilter的provider不支持这个地址的请求
  40. // 所以就会落在我们自己的认证过滤器上。完成接下来的认证
  41. .addFilterAfter(filter, UsernamePasswordAuthenticationFilter.class);
  42. }
  43. }

SmsCaptchaFilter


 
 
  1. package com.rui.tiger.auth.core.captcha.sms;
  2. import com.rui.tiger.auth.core.captcha.CaptchaException;
  3. import com.rui.tiger.auth.core.captcha.CaptchaProcessor;
  4. import com.rui.tiger.auth.core.captcha.CaptchaVo;
  5. import com.rui.tiger.auth.core.captcha.ImageCaptchaVo;
  6. import com.rui.tiger.auth.core.properties.SecurityProperties;
  7. import lombok.Getter;
  8. import lombok.Setter;
  9. import lombok.extern.slf4j.Slf4j;
  10. import org.apache.commons.lang.StringUtils;
  11. import org.springframework.beans.factory.InitializingBean;
  12. import org.springframework.security.web.authentication.AuthenticationFailureHandler;
  13. import org.springframework.social.connect.web.HttpSessionSessionStrategy;
  14. import org.springframework.social.connect.web.SessionStrategy;
  15. import org.springframework.util.AntPathMatcher;
  16. import org.springframework.web.bind.ServletRequestBindingException;
  17. import org.springframework.web.bind.ServletRequestUtils;
  18. import org.springframework.web.context.request.ServletWebRequest;
  19. import org.springframework.web.filter.OncePerRequestFilter;
  20. import javax.servlet.FilterChain;
  21. import javax.servlet.ServletException;
  22. import javax.servlet.http.HttpServletRequest;
  23. import javax.servlet.http.HttpServletResponse;
  24. import java.io.IOException;
  25. import java.util.HashSet;
  26. import java.util.Set;
  27. import java.util.stream.Collectors;
  28. import java.util.stream.Stream;
  29. /**
  30. * 手机验证码过滤器
  31. * OncePerRequestFilter 过滤器只会调用一次
  32. *
  33. * @author CaiRui
  34. * @date 2018-12-10 12:23
  35. */
  36. @Setter
  37. @Getter
  38. @Slf4j
  39. public class SmsCaptchaFilter extends OncePerRequestFilter implements InitializingBean {
  40. //一般在配置类中进行注入
  41. private AuthenticationFailureHandler failureHandler;
  42. private SecurityProperties securityProperties;
  43. /**
  44. * 验证码拦截的路径
  45. */
  46. private Set<String> interceptUrlSet = new HashSet<>();
  47. //session工具类
  48. private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
  49. //路径匹配工具类
  50. private AntPathMatcher antPathMatcher = new AntPathMatcher();
  51. /**
  52. * @throws ServletException
  53. */
  54. @Override
  55. public void afterPropertiesSet() throws ServletException {
  56. super.afterPropertiesSet();
  57. //其它配置的需要验证码验证的路径
  58. String configInterceptUrl = securityProperties.getCaptcha().getSms().getInterceptUrl();
  59. if (StringUtils.isNotBlank(configInterceptUrl)) {
  60. String[] configInterceptUrlArray = StringUtils.split(configInterceptUrl, ",");
  61. interceptUrlSet = Stream.of(configInterceptUrlArray).collect(Collectors.toSet());
  62. }
  63. //短信登录请求验证
  64. interceptUrlSet.add( "/authentication/mobile");
  65. }
  66. @Override
  67. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
  68. log.info( "验证码验证请求路径:[{}]", request.getRequestURI());
  69. boolean action = false; // 默认不放行
  70. for (String url : interceptUrlSet) {
  71. if (antPathMatcher.match(url, request.getRequestURI())) {
  72. action = true;
  73. }
  74. }
  75. if (action) {
  76. try {
  77. validate(request);
  78. } catch (CaptchaException captchaException) {
  79. //失败调用我们的自定义失败处理器
  80. failureHandler.onAuthenticationFailure(request, response, captchaException);
  81. //后续流程终止
  82. return;
  83. }
  84. }
  85. //后续过滤器继续执行
  86. filterChain.doFilter(request, response);
  87. }
  88. /**
  89. * 图片验证码校验
  90. *
  91. * @param request
  92. */
  93. private void validate(HttpServletRequest request) throws ServletRequestBindingException {
  94. String smsSessionKey=CaptchaProcessor.CAPTCHA_SESSION_KEY+ "sms";
  95. // 拿到之前存储的imageCode信息
  96. ServletWebRequest swr = new ServletWebRequest(request);
  97. CaptchaVo smsCaptchaInSession = (CaptchaVo) sessionStrategy.getAttribute(swr, smsSessionKey);
  98. String codeInRequest = ServletRequestUtils.getStringParameter(request, "smsCode");
  99. if (StringUtils.isBlank(codeInRequest)) {
  100. throw new CaptchaException( "验证码的值不能为空");
  101. }
  102. if (smsCaptchaInSession == null) {
  103. throw new CaptchaException( "验证码不存在");
  104. }
  105. if (smsCaptchaInSession.isExpried()) {
  106. sessionStrategy.removeAttribute(swr, smsSessionKey);
  107. throw new CaptchaException( "验证码已过期");
  108. }
  109. if (!StringUtils.equals(smsCaptchaInSession.getCode(), codeInRequest)) {
  110. throw new CaptchaException( "验证码不匹配");
  111. }
  112. //验证通过 移除缓存
  113. sessionStrategy.removeAttribute(swr, smsSessionKey);
  114. }
  115. }

BrowserSecurityConfig 浏览器配置同步调整


 
 
  1. package com.rui.tiger.auth.browser.config;
  2. import com.rui.tiger.auth.core.authentication.TigerAuthenticationFailureHandler;
  3. import com.rui.tiger.auth.core.authentication.TigerAuthenticationSuccessHandler;
  4. import com.rui.tiger.auth.core.authentication.mobile.SmsAuthenticationSecurityConfig;
  5. import com.rui.tiger.auth.core.captcha.CaptchaFilter;
  6. import com.rui.tiger.auth.core.captcha.sms.SmsCaptchaFilter;
  7. import com.rui.tiger.auth.core.properties.SecurityProperties;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.context.annotation.Bean;
  10. import org.springframework.context.annotation.Configuration;
  11. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  12. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  13. import org.springframework.security.core.userdetails.UserDetailsService;
  14. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  15. import org.springframework.security.crypto.password.PasswordEncoder;
  16. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  17. import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
  18. import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
  19. import javax.sql.DataSource;
  20. /**
  21. * 浏览器security配置类
  22. *
  23. * @author CaiRui
  24. * @date 2018-12-4 8:41
  25. */
  26. @Configuration
  27. public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
  28. @Autowired
  29. private SecurityProperties securityProperties;
  30. @Autowired
  31. private TigerAuthenticationFailureHandler tigerAuthenticationFailureHandler;
  32. @Autowired
  33. private TigerAuthenticationSuccessHandler tigerAuthenticationSuccessHandler;
  34. @Autowired
  35. private DataSource dataSource;
  36. @Autowired
  37. private UserDetailsService userDetailsService;
  38. @Autowired
  39. private SmsAuthenticationSecurityConfig smsAuthenticationSecurityConfig; //短信登陆配置
  40. /**
  41. * 密码加密解密
  42. *
  43. * @return
  44. */
  45. @Bean
  46. public PasswordEncoder passwordEncoder() {
  47. return new BCryptPasswordEncoder();
  48. }
  49. /**
  50. * 记住我持久化数据源
  51. * JdbcTokenRepositoryImpl CREATE_TABLE_SQL 建表语句可以先在数据库中执行
  52. *
  53. * @return
  54. */
  55. @Bean
  56. public PersistentTokenRepository persistentTokenRepository() {
  57. JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
  58. jdbcTokenRepository.setDataSource(dataSource);
  59. //第一次会执行CREATE_TABLE_SQL建表语句 后续会报错 可以关掉
  60. //jdbcTokenRepository.setCreateTableOnStartup(true);
  61. return jdbcTokenRepository;
  62. }
  63. @Override
  64. protected void configure(HttpSecurity http) throws Exception {
  65. //加入图片验证码过滤器
  66. CaptchaFilter captchaFilter = new CaptchaFilter();
  67. captchaFilter.setFailureHandler(tigerAuthenticationFailureHandler);
  68. captchaFilter.setSecurityProperties(securityProperties);
  69. captchaFilter.afterPropertiesSet();
  70. //短信验证码的配置
  71. SmsCaptchaFilter smsCaptchaFilter = new SmsCaptchaFilter();
  72. smsCaptchaFilter.setFailureHandler(tigerAuthenticationFailureHandler);
  73. smsCaptchaFilter.setSecurityProperties(securityProperties);
  74. smsCaptchaFilter.afterPropertiesSet();
  75. //将验证码的过滤器放在登陆的前面
  76. http.addFilterBefore(smsCaptchaFilter,UsernamePasswordAuthenticationFilter.class)
  77. .addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class)
  78. .formLogin()
  79. .loginPage( "/authentication/require") //自定义登录请求
  80. .loginProcessingUrl( "/authentication/form") //自定义登录表单请求
  81. .successHandler(tigerAuthenticationSuccessHandler)
  82. .failureHandler(tigerAuthenticationFailureHandler)
  83. .and()
  84. //记住我相关配置
  85. .rememberMe()
  86. .tokenRepository(persistentTokenRepository())
  87. .tokenValiditySeconds(securityProperties.getBrowser().getRemberMeSeconds())
  88. .userDetailsService(userDetailsService)
  89. .and()
  90. .authorizeRequests()
  91. .antMatchers(securityProperties.getBrowser().getLoginPage(),
  92. "/authentication/require", "/captcha/*") //此路径放行 否则会陷入死循环
  93. .permitAll()
  94. .anyRequest()
  95. .authenticated()
  96. .and()
  97. .csrf().disable() //跨域关闭
  98. //短信登陆配置挂载
  99. .apply(smsAuthenticationSecurityConfig)
  100. ;
  101. }
  102. }

ok 几个核心类都开发完毕下面我们进行测试下

测试

  1. 登录页面
  2. 点击发送短信验证码,后台日志查看验证码       
  3. 返回到登录页面,输入验证码进行登陆确认
  4. 后台复制真正发送的验证码添加
  5. 提交短信登录

1.登录

2.发送验证码 后台日志获取 

3.输入短信验证码 先输入错误的

4.返回提示

其它各种情况自行调试验证。

总结

 

 


 

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值