基于SpringSecurity 开发手机验证码登录

html

 

 

 实现短信认证流程

 

 

实现短信验证码接口
1. 生成验证码
2. 存入session
3. 发送给手机验证码

仿写短信认证逻辑

1
2
3
4
5
6
7
8
9
10
11
1. token认证信息,创建token
SmsCodeAuthenticationToken extends AbstractAuthenticationToken
2. 创建    Provider, 根据token类型AuthenticationManager 会调用相应的Provider,获取UserDetailsService user信息等
SmsCodeAuthenticationProvider implements AuthenticationProvider
3. 创建filter 具体拦截什么请求,生成 Token信息
SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter
4. 创建配置类
SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>
实现configure 方法 添加自定义的SmsCodeAuthenticationProvider, 和SmsCodeAuthenticationFilter
5.在项目中引用:
.apply(smsCodeAuthenticationSecurityConfig);

  1.SmsCodeAuthenticationToken.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.imooc.security.core.authentication.mobile;
 
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
 
import java.util.Collection;
 
/**
 * @Title: SmsCodeAuthenticationToken
 * @ProjectName spring-security-main
 * @date 2020/12/211:17
 */
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
 
    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
 
 
    private final Object principal; // 登录之前存手机号, 登录之后存用户信息
    public SmsCodeAuthenticationToken(String mobile) {
        super(null);
        this.principal = mobile;
        setAuthenticated(false);
    }
 
    public SmsCodeAuthenticationToken(Object principal,
                                               Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        super.setAuthenticated(true); // must use super, as we override
    }
 
    // ~ Methods
    // ========================================================================================================
 
    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");
        }
 
        super.setAuthenticated(false);
    }
 
    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
    }
}

  

1
2. 创建    Provider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.imooc.security.core.authentication.mobile;
 
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;
 
/**
 * @Title: SmsCodeAuthenticationProvider
 * @ProjectName spring-security-main
 * @date 2020/12/211:37
 */
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
    private UserDetailsService userDetailsService;
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 1 获取token
        SmsCodeAuthenticationToken smsCodeAuthenticationToken = (SmsCodeAuthenticationToken) authentication;
        // 2. 获取user
        UserDetails user = userDetailsService.loadUserByUsername((String) smsCodeAuthenticationToken.getPrincipal());
        if (user == null) {
            throw new InternalAuthenticationServiceException("无法获取用户信息");
        }
        SmsCodeAuthenticationToken token = new SmsCodeAuthenticationToken(user, user.getAuthorities());
        token.setDetails(smsCodeAuthenticationToken.getDetails());
        return token;
    }
 
    @Override
    public boolean supports(Class<?> authentication) {
        return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
    }
 
    public UserDetailsService getUserDetailsService() {
        return userDetailsService;
    }
 
    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
}

 

1
3. 创建filter 具体拦截什么请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.imooc.security.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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
/**
 * @Title: SmsCodeAuthenticationFilter
 * @ProjectName spring-security-main
 * @date 2020/12/211:24
 */
public class SmsCodeAuthenticationFilter extends
        AbstractAuthenticationProcessingFilter {
    // ~ Static fields/initializers
    // =====================================================================================
 
    public static final String IMOOC_FORM_MOBILE_KEY = "mobile";
 
    private String mobileParameter = IMOOC_FORM_MOBILE_KEY;
    private boolean postOnly = true;
 
    // ~ Constructors
    // ===================================================================================================
 
    public SmsCodeAuthenticationFilter() {
        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();
 
        SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);
 
        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
 
        return this.getAuthenticationManager().authenticate(authRequest);
    }
 
    protected String obtainMobile(HttpServletRequest request) {
        return request.getParameter(mobileParameter);
    }
 
    protected void setDetails(HttpServletRequest request,
                              SmsCodeAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }
 
    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }
}

  

1
4. 创建配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.imooc.security.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;
@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
 
    // 自定义的成功失败处理器
    @Autowired
    private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;
    @Autowired
    private AuthenticationFailureHandler imoocAuthenticationFailureHandler;
     
    @Autowired
    private UserDetailsService userDetailsService;
 
    /**
     * 加入自定义的filter , provider
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
         
        SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
        smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(imoocAuthenticationSuccessHandler);
        smsCodeAuthenticationFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);
         
        SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
        smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);
         
        http.authenticationProvider(smsCodeAuthenticationProvider)
            .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
         
    }
 
}

  

1
5.在项目中引用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package com.imooc.security.browser;
 
import com.imooc.security.core.authentication.mobile.SmsCodeAuthenticationSecurityConfig;
import com.imooc.security.core.properties.SecurityProperties;
import com.imooc.security.core.validate.code.SmsCodeFilter;
import com.imooc.security.core.validate.code.ValidateCodeFilter;
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.password.PasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
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;
 
@Configuration
public class BrowserSecurityConfig  extends WebSecurityConfigurerAdapter {
    @Autowired
    private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;
    @Autowired
    private AuthenticationFailureHandler imoocAuthenctiationFailureHandler;
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new SCryptPasswordEncoder();
    }
 
    @Autowired
    private SecurityProperties securityProperties;
//    PersistentTokenRepository
 
    @Autowired
    private DataSource dataSource;
 
    @Autowired
    private UserDetailsService userDetailsService; // MyUserDetailsService
    /**
     * 记住我功能
     * 1. 创建PersistentTokenRepository
     * 2. 设置过期时间
     * 3. 获取UserDetailsService 用户登录信息
     * 4. 配置rememberMe 生效
     * @return
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource); // 配置的dataSource
//        jdbcTokenRepository.setCreateTableOnStartup(true); // 自动创建存放记住我的表,如果存在会报错
        return jdbcTokenRepository;
    }
 
    @Autowired
    private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
    @Override
    public void configure(HttpSecurity http) throws Exception {
        // 图片验证码过滤器
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        validateCodeFilter.setAuthenticationFailureHandler(imoocAuthenctiationFailureHandler);
        validateCodeFilter.setSecurityProperties(securityProperties);
        validateCodeFilter.afterPropertiesSet();
 
        // 验证码过滤器
        SmsCodeFilter smsCodeFilter = new SmsCodeFilter();
        smsCodeFilter.setAuthenticationFailureHandler(imoocAuthenctiationFailureHandler);
        smsCodeFilter.setSecurityProperties(securityProperties);
        smsCodeFilter.afterPropertiesSet();
 
        // 添加一个图片验证filter, 在UsernamePasswordAuthenticationFilter之前执行
        http
                .addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                // .httpBasic() // 默认方式
                .formLogin() // 设置认证的登录方式 表单方式
                .loginPage("/authentication/require") // 自定义登录页面
                .loginProcessingUrl("/authentication/form") // 自定义表单提交的url, 默认是login
                .successHandler(imoocAuthenticationSuccessHandler) // 不适用默认的认证成功处理器
                .failureHandler(imoocAuthenctiationFailureHandler) // 登录失败处理器
//                .failureForwardUrl("/authentication/require")
//                .failureUrl("/authentication/require")
                .and()
                .rememberMe()
                .tokenRepository(persistentTokenRepository())
                // rememberME 有效期
                .tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())
                .userDetailsService(userDetailsService)
                .and()
                .authorizeRequests() // 需要授权
                // 当匹配到这个页面时,不需要授权
                .antMatchers("/authentication/require", securityProperties.getBrowser().getLoginPage(),
                        "/code/*").permitAll()
                .anyRequest() // 所有请求
                .authenticated()
                .and()  // 关闭csrf
                .csrf()
                .disable()
                .apply(smsCodeAuthenticationSecurityConfig);
    }
}

  

 

controller 

1
2
3
4
5
6
7
8
9
@GetMapping("/code/sms")
public void createSmsCode(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletRequestBindingException {
    ValidateCode smsCode = smsCodeGenerator.generate(new ServletWebRequest(request));
    // 放入session
    sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_SMS_KEY, smsCode);
    // 通过短信服务商发送短信验证码到手机
    String mobile = ServletRequestUtils.getStringParameter(request, "mobile");
    smsCodeSender.send(mobile, smsCode.getCode());
}

  

 

 

posted @   qukaige  阅读(1029)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
点击右上角即可分享
微信分享提示