基于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()); } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .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 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义