短信登陆开发
原理

基本原理:SmsAuthenticationFilter接受请求生成SmsAuthenticationToken,然后交给系统的AuthenticationManager进行管理,然后找到SmsAuthenticationProvider,然后再调用UserDetailsService进行短信验证,SmsAuthenticationSecurityConfig进行配置 SmsCaptchaFilter验证码过滤器 在请求之前进行验证验证
短信验证主要是复制参考用户名密码流程,参考前面的源码分析。
代码
SmsAuthenticationToken
- package com.rui.tiger.auth.core.authentication.mobile;
-
- import org.springframework.security.authentication.AbstractAuthenticationToken;
- import org.springframework.security.core.GrantedAuthority;
-
- import java.util.Collection;
-
- /**
- * 手机token
- * 参照:UsernamePasswordAuthenticationToken
- * @author CaiRui
- * @Date 2018/12/15 22:28
- */
- public class SmsAuthenticationToken extends AbstractAuthenticationToken {
- private static final long serialVersionUID = 500L;
- private final Object principal;//用户名
- //private Object credentials; 密码 手机登录验证码在登录前已经验证 考虑手机验证码通用 没有放到这里
-
- /**
- * 没有认证成功
- * @param mobile 手机号
- */
- public SmsAuthenticationToken(Object mobile) {
- super((Collection)null);
- this.principal = mobile;
- this.setAuthenticated(false);
- }
-
- /**
- * 认证成功同时进行权限设置
- * @param principal
- * @param authorities
- */
- public SmsAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
- super(authorities);
- this.principal = principal;
- super.setAuthenticated(true);
- }
-
- public Object getCredentials() {
- return null;
- }
-
- public Object getPrincipal() {
- return this.principal;
- }
-
- 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);
- }
- }
-
- public void eraseCredentials() {
- super.eraseCredentials();
- }
- }
SmsAuthenticationFilter
- package com.rui.tiger.auth.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;
-
- /**
- * 手机登陆过滤器
- * 参照:UsernamePasswordAuthenticationFilter
- * @author CaiRui
- * @Date 2018/12/16 10:39
- */
- public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
-
- // ~ Static fields/initializers
- // =====================================================================================
-
- public static final String TIGER_SECURITY_FORM_MOBILE_KEY = "mobile";
- private String mobileParameter = TIGER_SECURITY_FORM_MOBILE_KEY;
- //public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
- // private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
- private boolean postOnly = true;
-
- // ~ Constructors
- // ===================================================================================================
-
- //TODO /authentication/mobile 这些参数应该配置到字典中待优化
- public SmsAuthenticationFilter() {
- // 拦截该路径,如果是访问该路径,则标识是需要短信登录
- super(new AntPathRequestMatcher("/authentication/mobile", "POST"));
- }
-
- // ~ Methods
- // ========================================================================================================
-
- public Authentication attemptAuthentication(HttpServletRequest request,
- HttpServletResponse response) throws AuthenticationException {
- if (postOnly && !request.getMethod().equals("POST")) {
- throw new AuthenticationServiceException(
- "Authentication method not supported: " + request.getMethod());
- }
-
- String mobile = obtainMobile(request);
- if (mobile == null) {
- mobile = "";
- }
- mobile = mobile.trim();
-
- SmsAuthenticationToken authRequest = new SmsAuthenticationToken(mobile);
-
- // Allow subclasses to set the "details" property
- setDetails(request, authRequest);
-
- return this.getAuthenticationManager().authenticate(authRequest);
- }
-
-
- /**
- * Enables subclasses to override the composition of the username, such as by
- * including additional values and a separator.
- *
- * @param request so that request attributes can be retrieved
- *
- * @return the username that will be presented in the <code>Authentication</code>
- * request token to the <code>AuthenticationManager</code>
- */
- protected String obtainMobile(HttpServletRequest request) {
- return request.getParameter(mobileParameter);
- }
-
- /**
- * Provided so that subclasses may configure what is put into the authentication
- * request's details property.
- *
- * @param request that an authentication request is being created for
- * @param authRequest the authentication request object that should have its details
- * set
- */
- protected void setDetails(HttpServletRequest request,
- SmsAuthenticationToken authRequest) {
- authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
- }
-
- /**
- * Sets the parameter name which will be used to obtain the mobile from the login
- * request.
- *
- * @param mobileParameter the parameter name. Defaults to "mobile".
- */
- public void setMobileParameter(String mobileParameter) {
- Assert.hasText(mobileParameter, "mobile parameter must not be empty or null");
- this.mobileParameter = mobileParameter;
- }
-
- public String getMobileParameter() {
- return mobileParameter;
- }
-
- /**
- * Defines whether only HTTP POST requests will be allowed by this filter. If set to
- * true, and an authentication request is received which is not a POST request, an
- * exception will be raised immediately and authentication will not be attempted. The
- * <tt>unsuccessfulAuthentication()</tt> method will be called as if handling a failed
- * authentication.
- * <p>
- * Defaults to <tt>true</tt> but may be overridden by subclasses.
- */
- public void setPostOnly(boolean postOnly) {
- this.postOnly = postOnly;
- }
-
- }
SmsAuthenticationProvider
- package com.rui.tiger.auth.core.authentication.mobile;
-
- import lombok.Getter;
- import lombok.Setter;
- 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 CaiRui
- * @Date 2018/12/16 10:38
- */
- public class SmsAuthenticationProvider implements AuthenticationProvider {
- @Override
- public Authentication authenticate(Authentication authentication) throws AuthenticationException {
- SmsAuthenticationToken smsCaptchaAuthenticationToken= (SmsAuthenticationToken) authentication;
- UserDetails user=userDetailsService.loadUserByUsername((String) smsCaptchaAuthenticationToken.getPrincipal());
- if(user==null){
- throw new InternalAuthenticationServiceException("无法获取用户信息");
- }
- //认证通过
- SmsAuthenticationToken authenticationTokenResult=new SmsAuthenticationToken(user,user.getAuthorities());
- //将之前未认证的请求放进认证后的Token中
- authenticationTokenResult.setDetails(smsCaptchaAuthenticationToken.getDetails());
- return authenticationTokenResult;
- }
-
- //@Autowired
- @Getter
- @Setter
- private UserDetailsService userDetailsService;//
-
- /**
- * AuthenticationManager 验证该Provider是否支持 认证
- * @param aClass
- * @return
- */
- @Override
- public boolean supports(Class<?> aClass) {
- return aClass.isAssignableFrom(SmsAuthenticationToken.class);
- }
-
- }
SmsAuthenticationSecurityConfig
- package com.rui.tiger.auth.core.authentication.mobile;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.authentication.AuthenticationManager;
- import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
- import org.springframework.security.config.annotation.web.builders.HttpSecurity;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.web.DefaultSecurityFilterChain;
- import org.springframework.security.web.authentication.AuthenticationFailureHandler;
- import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
- import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
- import org.springframework.stereotype.Component;
-
- /**
- * 手机权限配置类
- * @author CaiRui
- * @Date 2018/12/16 13:42
- */
- @Component
- public class SmsAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
-
- @Autowired
- private AuthenticationFailureHandler authenticationFailureHandler;
- @Autowired
- private AuthenticationSuccessHandler authenticationSuccessHandler;
- //实现类怎么确定? 自定义的实现??
- @Autowired
- private UserDetailsService userDetailsService;
-
- @Override
- public void configure(HttpSecurity http) throws Exception {
- SmsAuthenticationFilter filter = new SmsAuthenticationFilter();
- filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
- filter.setAuthenticationFailureHandler(authenticationFailureHandler);
- filter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
-
- SmsAuthenticationProvider smsAuthenticationProvider = new SmsAuthenticationProvider();
- smsAuthenticationProvider.setUserDetailsService(userDetailsService);
-
- http
- // 注册到AuthenticationManager中去
- .authenticationProvider(smsAuthenticationProvider)
- // 添加到 UsernamePasswordAuthenticationFilter 之后
- // 貌似所有的入口都是 UsernamePasswordAuthenticationFilter
- // 然后UsernamePasswordAuthenticationFilter的provider不支持这个地址的请求
- // 所以就会落在我们自己的认证过滤器上。完成接下来的认证
- .addFilterAfter(filter, UsernamePasswordAuthenticationFilter.class);
- }
-
- }
SmsCaptchaFilter
- package com.rui.tiger.auth.core.captcha.sms;
-
- import com.rui.tiger.auth.core.captcha.CaptchaException;
- import com.rui.tiger.auth.core.captcha.CaptchaProcessor;
- import com.rui.tiger.auth.core.captcha.CaptchaVo;
- import com.rui.tiger.auth.core.captcha.ImageCaptchaVo;
- import com.rui.tiger.auth.core.properties.SecurityProperties;
- import lombok.Getter;
- import lombok.Setter;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.commons.lang.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.bind.ServletRequestBindingException;
- import org.springframework.web.bind.ServletRequestUtils;
- 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.Set;
- import java.util.stream.Collectors;
- import java.util.stream.Stream;
-
- /**
- * 手机验证码过滤器
- * OncePerRequestFilter 过滤器只会调用一次
- *
- * @author CaiRui
- * @date 2018-12-10 12:23
- */
- @Setter
- @Getter
- @Slf4j
- public class SmsCaptchaFilter extends OncePerRequestFilter implements InitializingBean {
-
- //一般在配置类中进行注入
-
- private AuthenticationFailureHandler failureHandler;
-
- private SecurityProperties securityProperties;
-
- /**
- * 验证码拦截的路径
- */
- private Set<String> interceptUrlSet = new HashSet<>();
-
- //session工具类
- private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
- //路径匹配工具类
- private AntPathMatcher antPathMatcher = new AntPathMatcher();
-
- /**
- * @throws ServletException
- */
-
- @Override
- public void afterPropertiesSet() throws ServletException {
- super.afterPropertiesSet();
- //其它配置的需要验证码验证的路径
- String configInterceptUrl = securityProperties.getCaptcha().getSms().getInterceptUrl();
- if (StringUtils.isNotBlank(configInterceptUrl)) {
- String[] configInterceptUrlArray = StringUtils.split(configInterceptUrl, ",");
- interceptUrlSet = Stream.of(configInterceptUrlArray).collect(Collectors.toSet());
- }
- //短信登录请求验证
- interceptUrlSet.add("/authentication/mobile");
- }
-
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
-
- log.info("验证码验证请求路径:[{}]", request.getRequestURI());
- boolean action = false;// 默认不放行
- for (String url : interceptUrlSet) {
- if (antPathMatcher.match(url, request.getRequestURI())) {
- action = true;
- }
- }
- if (action) {
- try {
- validate(request);
- } catch (CaptchaException captchaException) {
- //失败调用我们的自定义失败处理器
- failureHandler.onAuthenticationFailure(request, response, captchaException);
- //后续流程终止
- return;
- }
-
- }
- //后续过滤器继续执行
- filterChain.doFilter(request, response);
- }
-
- /**
- * 图片验证码校验
- *
- * @param request
- */
- private void validate(HttpServletRequest request) throws ServletRequestBindingException {
- String smsSessionKey=CaptchaProcessor.CAPTCHA_SESSION_KEY+"sms";
- // 拿到之前存储的imageCode信息
- ServletWebRequest swr = new ServletWebRequest(request);
- CaptchaVo smsCaptchaInSession = (CaptchaVo) sessionStrategy.getAttribute(swr, smsSessionKey);
- String codeInRequest = ServletRequestUtils.getStringParameter(request, "smsCode");
-
- if (StringUtils.isBlank(codeInRequest)) {
- throw new CaptchaException("验证码的值不能为空");
- }
- if (smsCaptchaInSession == null) {
- throw new CaptchaException("验证码不存在");
- }
- if (smsCaptchaInSession.isExpried()) {
- sessionStrategy.removeAttribute(swr, smsSessionKey);
- throw new CaptchaException("验证码已过期");
- }
- if (!StringUtils.equals(smsCaptchaInSession.getCode(), codeInRequest)) {
- throw new CaptchaException("验证码不匹配");
- }
- //验证通过 移除缓存
- sessionStrategy.removeAttribute(swr, smsSessionKey);
- }
- }
BrowserSecurityConfig 浏览器配置同步调整
- package com.rui.tiger.auth.browser.config;
-
- import com.rui.tiger.auth.core.authentication.TigerAuthenticationFailureHandler;
- import com.rui.tiger.auth.core.authentication.TigerAuthenticationSuccessHandler;
- import com.rui.tiger.auth.core.authentication.mobile.SmsAuthenticationSecurityConfig;
- import com.rui.tiger.auth.core.captcha.CaptchaFilter;
- import com.rui.tiger.auth.core.captcha.sms.SmsCaptchaFilter;
- import com.rui.tiger.auth.core.properties.SecurityProperties;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.security.config.annotation.web.builders.HttpSecurity;
- import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
- import org.springframework.security.crypto.password.PasswordEncoder;
- import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
- import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
- import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
-
- import javax.sql.DataSource;
-
- /**
- * 浏览器security配置类
- *
- * @author CaiRui
- * @date 2018-12-4 8:41
- */
- @Configuration
- public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
-
- @Autowired
- private SecurityProperties securityProperties;
- @Autowired
- private TigerAuthenticationFailureHandler tigerAuthenticationFailureHandler;
- @Autowired
- private TigerAuthenticationSuccessHandler tigerAuthenticationSuccessHandler;
- @Autowired
- private DataSource dataSource;
- @Autowired
- private UserDetailsService userDetailsService;
- @Autowired
- private SmsAuthenticationSecurityConfig smsAuthenticationSecurityConfig;//短信登陆配置
-
- /**
- * 密码加密解密
- *
- * @return
- */
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
-
- /**
- * 记住我持久化数据源
- * JdbcTokenRepositoryImpl CREATE_TABLE_SQL 建表语句可以先在数据库中执行
- *
- * @return
- */
- @Bean
- public PersistentTokenRepository persistentTokenRepository() {
- JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
- jdbcTokenRepository.setDataSource(dataSource);
- //第一次会执行CREATE_TABLE_SQL建表语句 后续会报错 可以关掉
- //jdbcTokenRepository.setCreateTableOnStartup(true);
- return jdbcTokenRepository;
- }
-
-
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- //加入图片验证码过滤器
- CaptchaFilter captchaFilter = new CaptchaFilter();
- captchaFilter.setFailureHandler(tigerAuthenticationFailureHandler);
- captchaFilter.setSecurityProperties(securityProperties);
- captchaFilter.afterPropertiesSet();
- //短信验证码的配置
- SmsCaptchaFilter smsCaptchaFilter = new SmsCaptchaFilter();
- smsCaptchaFilter.setFailureHandler(tigerAuthenticationFailureHandler);
- smsCaptchaFilter.setSecurityProperties(securityProperties);
- smsCaptchaFilter.afterPropertiesSet();
-
- //将验证码的过滤器放在登陆的前面
- http.addFilterBefore(smsCaptchaFilter,UsernamePasswordAuthenticationFilter.class)
- .addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class)
- .formLogin()
- .loginPage("/authentication/require")//自定义登录请求
- .loginProcessingUrl("/authentication/form")//自定义登录表单请求
- .successHandler(tigerAuthenticationSuccessHandler)
- .failureHandler(tigerAuthenticationFailureHandler)
- .and()
- //记住我相关配置
- .rememberMe()
- .tokenRepository(persistentTokenRepository())
- .tokenValiditySeconds(securityProperties.getBrowser().getRemberMeSeconds())
- .userDetailsService(userDetailsService)
- .and()
- .authorizeRequests()
- .antMatchers(securityProperties.getBrowser().getLoginPage(),
- "/authentication/require", "/captcha/*")//此路径放行 否则会陷入死循环
- .permitAll()
- .anyRequest()
- .authenticated()
- .and()
- .csrf().disable()//跨域关闭
- //短信登陆配置挂载
- .apply(smsAuthenticationSecurityConfig)
- ;
- }
-
- }
ok 几个核心类都开发完毕下面我们进行测试下
测试
- 登录页面
- 点击发送短信验证码,后台日志查看验证码
- 返回到登录页面,输入验证码进行登陆确认
- 后台复制真正发送的验证码添加
- 提交短信登录
1.登录

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

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

4.返回提示

其它各种情况自行调试验证。
总结
文章转载至:https://blog.youkuaiyun.com/ahcr1026212/article/details/85028726
本文详细介绍了基于Spring Security的短信登录系统开发过程,包括SmsAuthenticationToken、SmsAuthenticationFilter、SmsAuthenticationProvider等核心组件的实现原理及代码示例。
2257

被折叠的 条评论
为什么被折叠?



