SpringSecurity实现短信登录功能

⒈封装短信验证码类

 1 package cn.coreqi.security.validate;
 2 
 3 import java.time.LocalDateTime;
 4 
 5 public class ValidateCode {
 6     private String code;
 7     private LocalDateTime expireTime;   //过期时间
 8 
 9     public ValidateCode(String code, Integer expireIn) {
10         this.code = code;
11         this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
12     }
13 
14     public ValidateCode(String code, LocalDateTime expireTime) {
15         this.code = code;
16         this.expireTime = expireTime;
17     }
18 
19     public boolean isExpried(){
20         return LocalDateTime.now().isAfter(expireTime);
21     }
22     public String getCode() {
23         return code;
24     }
25 
26     public void setCode(String code) {
27         this.code = code;
28     }
29 
30     public LocalDateTime getExpireTime() {
31         return expireTime;
32     }
33 
34     public void setExpireTime(LocalDateTime expireTime) {
35         this.expireTime = expireTime;
36     }
37 
38 }

⒉封装短信验证码接口及实现类

1 package cn.coreqi.security.validate;
2 
3 public interface SmsCodeSender {
4     void send(String mobile,String code);
5 }
1 package cn.coreqi.security.validate;
2 
3 public class DefaultSmsCodeSender implements SmsCodeSender {
4     @Override
5     public void send(String mobile, String code) {
6         System.out.println("向手机"+mobile+"发送短信验证码"+code+"");
7     }
8 }

⒊封装验证码控制器

 1 package cn.coreqi.security.controller;
 2 
 3 import cn.coreqi.security.validate.DefaultSmsCodeSender;
 4 import cn.coreqi.security.validate.SmsCodeSender;
 5 import cn.coreqi.security.validate.ValidateCode;
 6 import org.apache.commons.lang3.RandomStringUtils;
 7 import org.springframework.social.connect.web.HttpSessionSessionStrategy;
 8 import org.springframework.social.connect.web.SessionStrategy;
 9 import org.springframework.web.bind.ServletRequestBindingException;
10 import org.springframework.web.bind.ServletRequestUtils;
11 import org.springframework.web.bind.annotation.GetMapping;
12 import org.springframework.web.bind.annotation.RestController;
13 import org.springframework.web.context.request.ServletWebRequest;
14 
15 import javax.servlet.http.HttpServletRequest;
16 import javax.servlet.http.HttpServletResponse;
17 
18 @RestController
19 public class ValidateSmsController {
20 
21     public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
22     private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
23 
24     @GetMapping("/code/sms")
25     public void createSmsCode(HttpServletRequest request, HttpServletResponse response) throws ServletRequestBindingException {
26         ValidateCode smsCode = new ValidateCode(RandomStringUtils.randomNumeric(4),60);
27         sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,smsCode);
28         String mobile = ServletRequestUtils.getRequiredStringParameter(request,"mobile");
29         SmsCodeSender smsCodeSender = new DefaultSmsCodeSender();
30         smsCodeSender.send(mobile,smsCode.getCode());
31     }
32 }

⒋放行验证码的Rest地址

⒌修改登录表单

 1     <h3>短信登陆</h3>
 2     <form action="/authentication/mobile" method="post">
 3         <table>
 4             <tr>
 5                 <td>手机号码:</td>
 6                 <td><input type="text" name="mobile" value="13800138000"></td>
 7             </tr>
 8             <tr>
 9                 <td>短信验证码:</td>
10                 <td>
11                     <input type="text" name="smsCode">
12                     <a href="/code/sms?mobile=13800138000"/>
13                 </td>
14             </tr>
15             <tr>
16                 <td colspan="2"><button type="submit">登录</button></td>
17             </tr>
18         </table>
19     </form>

⒍封装安全验证流程相关

  1.验证码验证Filter

 1 package cn.coreqi.security.Filter;
 2 
 3 import cn.coreqi.security.controller.ValidateSmsController;
 4 import cn.coreqi.security.validate.ValidateCode;
 5 import cn.coreqi.security.validate.ValidateCodeException;
 6 import org.springframework.security.web.authentication.AuthenticationFailureHandler;
 7 import org.springframework.social.connect.web.HttpSessionSessionStrategy;
 8 import org.springframework.social.connect.web.SessionStrategy;
 9 import org.springframework.util.StringUtils;
10 import org.springframework.web.bind.ServletRequestBindingException;
11 import org.springframework.web.bind.ServletRequestUtils;
12 import org.springframework.web.context.request.ServletWebRequest;
13 import org.springframework.web.filter.OncePerRequestFilter;
14 
15 import javax.servlet.FilterChain;
16 import javax.servlet.ServletException;
17 import javax.servlet.http.HttpServletRequest;
18 import javax.servlet.http.HttpServletResponse;
19 import java.io.IOException;
20 
21 /**
22  *  短信验证码过滤器
23  */
24 public class SmsCodeFilter extends OncePerRequestFilter {
25     private AuthenticationFailureHandler authenticationFailureHandler;
26 
27     private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
28 
29     public AuthenticationFailureHandler getAuthenticationFailureHandler() {
30         return authenticationFailureHandler;
31     }
32 
33     public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
34         this.authenticationFailureHandler = authenticationFailureHandler;
35     }
36 
37     public SessionStrategy getSessionStrategy() {
38         return sessionStrategy;
39     }
40 
41     public void setSessionStrategy(SessionStrategy sessionStrategy) {
42         this.sessionStrategy = sessionStrategy;
43     }
44 
45     @Override
46     protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
47         if (httpServletRequest.equals("/authentication/mobile") && httpServletRequest.getMethod().equals("post")) {
48             try {
49                 validate(new ServletWebRequest(httpServletRequest));
50 
51             }catch (ValidateCodeException e){
52                 authenticationFailureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,e);
53                 return;
54             }
55         }
56         filterChain.doFilter(httpServletRequest,httpServletResponse);   //如果不是登录请求,直接调用后面的过滤器链
57     }
58 
59     private void validate(ServletWebRequest request) throws ServletRequestBindingException {
60         ValidateCode codeInSession = (ValidateCode) sessionStrategy.getAttribute(request, ValidateSmsController.SESSION_KEY);
61         String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),"smsCode");
62         if(!StringUtils.hasText(codeInRequest)){
63             throw new ValidateCodeException("验证码的值不能为空!");
64         }
65         if(codeInSession == null){
66             throw new ValidateCodeException("验证码不存在!");
67         }
68         if(codeInSession.isExpried()){
69             sessionStrategy.removeAttribute(request,ValidateSmsController.SESSION_KEY);
70             throw new ValidateCodeException("验证码已过期!");
71         }
72         if(!codeInSession.getCode().equals(codeInRequest)){
73             throw new ValidateCodeException("验证码不正确!");
74         }
75         sessionStrategy.removeAttribute(request,ValidateSmsController.SESSION_KEY);
76     }
77 }

  2.封装短信登陆Token类

 1 package cn.coreqi.security.Token;
 2 
 3 import org.springframework.security.authentication.AbstractAuthenticationToken;
 4 import org.springframework.security.core.GrantedAuthority;
 5 import org.springframework.security.core.SpringSecurityCoreVersion;
 6 
 7 import java.util.Collection;
 8 
 9 public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
10     private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
11     private final Object principal; //存放认证信息,认证之前存放手机号,认证之后存放登录的用户
12 
13     public SmsCodeAuthenticationToken(String mobile) {
14         super((Collection)null);
15         this.principal = mobile;
16         this.setAuthenticated(false);
17     }
18 
19     public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
20         super(authorities);
21         this.principal = principal;
22         super.setAuthenticated(true);
23     }
24 
25     public Object getCredentials() {
26         return null;
27     }
28 
29     public Object getPrincipal() {
30         return this.principal;
31     }
32 
33     public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
34         if (isAuthenticated) {
35             throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
36         } else {
37             super.setAuthenticated(false);
38         }
39     }
40 
41     public void eraseCredentials() {
42         super.eraseCredentials();
43     }
44 }

  3.短信登陆请求Filter 

 1 package cn.coreqi.security.Filter;
 2 
 3 import cn.coreqi.security.Token.SmsCodeAuthenticationToken;
 4 import org.springframework.security.authentication.AuthenticationServiceException;
 5 import org.springframework.security.core.Authentication;
 6 import org.springframework.security.core.AuthenticationException;
 7 import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
 8 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 9 import org.springframework.util.Assert;
10 
11 import javax.servlet.http.HttpServletRequest;
12 import javax.servlet.http.HttpServletResponse;
13 
14 public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
15     public static final String COREQI_FORM_MOBILE_KEY = "mobile";
16     private String mobileParameter = COREQI_FORM_MOBILE_KEY;    //请求中携带手机号的参数名称
17     private boolean postOnly = true;    //指定当前过滤器是否只处理POST请求
18 
19     public SmsCodeAuthenticationFilter() {
20         super(new AntPathRequestMatcher("/authentication/mobile", "POST")); //指定当前过滤器处理的请求
21     }
22 
23     public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
24         if (this.postOnly && !request.getMethod().equals("POST")) {
25             throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
26         } else {
27             String mobile = this.obtainMobile(request);
28             if (mobile == null) {
29                 mobile = "";
30             }
31             mobile = mobile.trim();
32             SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);
33             this.setDetails(request, authRequest);
34             return this.getAuthenticationManager().authenticate(authRequest);
35         }
36     }
37 
38     /**
39      * 获取手机号码
40      * @param request
41      * @return
42      */
43     protected String obtainMobile(HttpServletRequest request) {
44         return request.getParameter(this.mobileParameter);
45     }
46 
47     /**
48      * 把请求的详情,例如请求ip、SessionId等设置到验证请求中去
49      * @param request
50      * @param authRequest
51      */
52     protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
53         authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
54     }
55 
56     public void setMobileParameter(String mobileParameter) {
57         Assert.hasText(mobileParameter, "Username parameter must not be empty or null");
58         this.mobileParameter = mobileParameter;
59     }
60 
61 
62     public void setPostOnly(boolean postOnly) {
63         this.postOnly = postOnly;
64     }
65 
66     public final String getMobileParameter() {
67         return this.mobileParameter;
68     }
69 
70 }

  4.短信身份认证类

 1 package cn.coreqi.security.Provider;
 2 
 3 import cn.coreqi.security.Token.SmsCodeAuthenticationToken;
 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 public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
12 
13     private UserDetailsService userDetailsService;
14 
15     public UserDetailsService getUserDetailsService() {
16         return userDetailsService;
17     }
18 
19     public void setUserDetailsService(UserDetailsService userDetailsService) {
20         this.userDetailsService = userDetailsService;
21     }
22 
23     /**
24      * 进行身份认证的逻辑
25      * @param authentication    就是我们传入的Token
26      * @return
27      * @throws AuthenticationException
28      */
29     @Override
30     public Authentication authenticate(Authentication authentication) throws AuthenticationException {
31 
32         //利用UserDetailsService获取用户信息,拿到用户信息后重新组装一个已认证的Authentication
33 
34         SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken)authentication;
35         UserDetails user = userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());  //根据手机号码拿到用户信息
36         if(user == null){
37             throw new InternalAuthenticationServiceException("无法获取用户信息");
38         }
39         SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user,user.getAuthorities());
40         authenticationResult.setDetails(authenticationToken.getDetails());
41         return authenticationResult;
42     }
43 
44     /**
45      * AuthenticationManager挑选一个AuthenticationProvider来处理传入进来的Token就是根据supports方法来判断的
46      * @param aClass
47      * @return
48      */
49     @Override
50     public boolean supports(Class<?> aClass) {
51         return SmsCodeAuthenticationToken.class.isAssignableFrom(aClass);   //判断出入进来的是不是SmsCodeAuthenticationToken类型
52     }
53 }

⒎配置

 1 package cn.coreqi.security.config;
 2 
 3 import cn.coreqi.security.Filter.SmsCodeAuthenticationFilter;
 4 import cn.coreqi.security.Provider.SmsCodeAuthenticationProvider;
 5 import org.springframework.beans.factory.annotation.Autowired;
 6 import org.springframework.security.authentication.AuthenticationManager;
 7 import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
 8 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 9 import org.springframework.security.core.userdetails.UserDetailsService;
10 import org.springframework.security.web.DefaultSecurityFilterChain;
11 import org.springframework.security.web.authentication.AuthenticationFailureHandler;
12 import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
13 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
14 import org.springframework.stereotype.Component;
15 
16 @Component
17 public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
18 
19     @Autowired
20     private AuthenticationSuccessHandler coreqiAuthenticationSuccessHandler;
21 
22     @Autowired
23     private AuthenticationFailureHandler coreqiAuthenticationFailureHandler;
24 
25     @Autowired
26     private UserDetailsService userDetailsService;
27 
28     @Override
29     public void configure(HttpSecurity http) throws Exception {
30         SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
31         smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
32         smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(coreqiAuthenticationSuccessHandler);
33         smsCodeAuthenticationFilter.setAuthenticationFailureHandler(coreqiAuthenticationFailureHandler);
34 
35         SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
36         smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);
37 
38         http.authenticationProvider(smsCodeAuthenticationProvider)
39                 .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
40     }
41 }

 

 1 package cn.coreqi.security.config;
 2 
 3 import cn.coreqi.security.Filter.SmsCodeFilter;
 4 import cn.coreqi.security.Filter.ValidateCodeFilter;
 5 import org.springframework.beans.factory.annotation.Autowired;
 6 import org.springframework.context.annotation.Bean;
 7 import org.springframework.context.annotation.Configuration;
 8 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 9 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
10 import org.springframework.security.crypto.password.NoOpPasswordEncoder;
11 import org.springframework.security.crypto.password.PasswordEncoder;
12 import org.springframework.security.web.authentication.AuthenticationFailureHandler;
13 import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
14 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
15 
16 @Configuration
17 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
18 
19     @Autowired
20     private AuthenticationSuccessHandler coreqiAuthenticationSuccessHandler;
21 
22     @Autowired
23     private AuthenticationFailureHandler coreqiAuthenticationFailureHandler;
24 
25     @Autowired
26     private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
27 
28     @Bean
29     public PasswordEncoder passwordEncoder(){
30         return NoOpPasswordEncoder.getInstance();
31     }
32 
33 
34     @Override
35     protected void configure(HttpSecurity http) throws Exception {
36         ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
37         validateCodeFilter.setAuthenticationFailureHandler(coreqiAuthenticationFailureHandler);
38 
39         SmsCodeFilter smsCodeFilter = new SmsCodeFilter();
40 
41 
42         //http.httpBasic()    //httpBasic登录 BasicAuthenticationFilter
43         http.addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class)    //加载用户名密码过滤器的前面
44                 .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)    //加载用户名密码过滤器的前面
45                 .formLogin()    //表单登录 UsernamePasswordAuthenticationFilter
46                     .loginPage("/coreqi-signIn.html")  //指定登录页面
47                     //.loginPage("/authentication/require")
48                     .loginProcessingUrl("/authentication/form") //指定表单提交的地址用于替换UsernamePasswordAuthenticationFilter默认的提交地址
49                     .successHandler(coreqiAuthenticationSuccessHandler) //登录成功以后要用我们自定义的登录成功处理器,不用Spring默认的。
50                     .failureHandler(coreqiAuthenticationFailureHandler) //自己体会把
51                 .and()
52                 .authorizeRequests()    //对授权请求进行配置
53                     .antMatchers("/coreqi-signIn.html","/code/image").permitAll() //指定登录页面不需要身份认证
54                     .anyRequest().authenticated()  //任何请求都需要身份认证
55                     .and().csrf().disable()    //禁用CSRF
56                 .apply(smsCodeAuthenticationSecurityConfig);
57             //FilterSecurityInterceptor 整个SpringSecurity过滤器链的最后一环
58     }
59 }

 

posted @ 2019-03-31 20:11  SpringCore  阅读(4643)  评论(0编辑  收藏  举报