600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > Spring Security 自定义短信验证码登录

Spring Security 自定义短信验证码登录

时间:2020-10-21 02:53:55

相关推荐

Spring Security 自定义短信验证码登录

短信验证码登录的思路,需要通过验证码过滤器,过滤验证码是否正确。次过程和图形验证码校验逻辑完全一样。 之后,需要通过Spring Security 认真的一套逻辑,来去数据库查询用户信息,进行 认证信息Authentication的封装。

此处案例的Provider认证校验类,只是从数据库查询信息,然后进行封装。实际开发中可能需求不同,按需求进行更改。

发送验证码功能

1、定义验证码实体类

@Datapublic class ValidateCode {/*** 验证码*/private String code;/*** 过期时间*/private LocalDateTime expireTime;public ValidateCode(String code, int expireTime) {this.code = code;this.expireTime = LocalDateTime.now().plusSeconds(expireTime);}public ValidateCode(String code, LocalDateTime expireTime) {this.code = code;this.expireTime = expireTime;}/*** 判断验证码是否过期* @return*/public boolean isExpire() {return LocalDateTime.now().isAfter(expireTime);}}

2、定义生成验证码的接口

/*** @Author L.jg* @Title 抽象接口,让客户端可配置接口* @Date /5/24 11:42*/public interface ValidateCodeGenerate {ValidateCode generate(HttpServletRequest request);}

3、 实现生成短信验证码

public class SmsCodeGenerate implements ValidateCodeGenerate {private SmsProperties smsProperties;public SmsCodeGenerate(SmsProperties smsProperties) {this.smsProperties = smsProperties;}@Overridepublic ValidateCode generate(HttpServletRequest request) {String smsCode = RandomUtil.randomNumbers(smsProperties.getLength());return new ValidateCode(smsCode, smsProperties.getExpireTime());}}

4、将生成短信验证码的类加入Bean容器

@Configurationpublic class VlidateCodeConfig {@Autowiredprivate SecurityProperties securityProperties;@Bean@ConditionalOnMissingBean(name = "smsCodeGenerate")public ValidateCodeGenerate smsCodeGenerate() {SmsCodeGenerate smsCodeGenerate = new SmsCodeGenerate(securityProperties.getValidateCode().getSms());return smsCodeGenerate;}}

5、发送验证码接口

public interface SmsCodeSender {/*** 发送验证码** @param mobile 手机号* @param code 验证码*/void send(String mobile, String code);}

6、模拟发送验证码实现类

@Componentpublic class DefaultSmsCodeSender implements SmsCodeSender {@Overridepublic void send(String mobile, String code) {System.out.println("手机号:" + mobile + "短信验证码:" + code);}}

7、 发送验证码接口

@GetMapping("/sms/code")public void createSmsCode(HttpServletRequest request, HttpServletResponse response) throws ServletRequestBindingException {// 生成imageCode// ImageCode imageCode = createImageCode(request);ValidateCode smsCode = smsCodeGenerate.generate(request);// 将imageCode 保存在session中sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY_IMAGE_CODE, smsCode);String mobile = ServletRequestUtils.getRequiredStringParameter(request, "mobile");smsCodeSender.send(mobile, smsCode.getCode());}

手机验证码登录

以Spring Security的form表单为例,是先通过UsernamePasswordAuthenticationFilter来获取用户的登录信息。

然后将登录信息封装为UsernamePasswordAuthenticationToken

将封装的 Authentication信息交给AuthenticationManager管理。

根据Authentication的类型,调用对应的Provider来处理认证逻辑。

这里参考UsernamepasswordAuthenticationFilterUsernamePasswordAuthenticationTokenDaoAuthenticationProvider实现自己的各个类。

1、自定义 SmsCodeAuthenticationToken

public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;// 手机号private final Object principal;public SmsCodeAuthenticationToken(Object principal) {super(null);this.principal = principal;setAuthenticated(false);}public SmsCodeAuthenticationToken(Collection<? extends GrantedAuthority> authorities, Object principal) {super(authorities);this.principal = principal;super.setAuthenticated(true);}@Overridepublic Object getCredentials() {return null;}@Overridepublic Object getPrincipal() {return this.principal;}@Overridepublic void setAuthenticated(boolean authenticated) {if (authenticated) {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();}}

2、自定义 SmsCodeAuthenticationFilter

public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {private String mobileParameter = "mobile";private boolean postOnly = true;public SmsCodeAuthenticationFilter() {super(new AntPathRequestMatcher("/authentication/mobile", "POST"));}public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {if (this.postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());} else {String mobile = this.obtainMobile(request);if (mobile == null) {mobile = "";}mobile = mobile.trim();SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);this.setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}}protected String obtainMobile(HttpServletRequest request) {return request.getParameter(this.mobileParameter);}protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));}public void setPostOnly(boolean postOnly) {this.postOnly = postOnly;}}

3、自定义 SmsCodeAuthenticationProvider

public class SmsCodeAuthenticationProvider implements AuthenticationProvider {private UserDetailsService userDetailsService;@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {String username = authentication.getPrincipal().toString();UserDetails userDetails = userDetailsService.loadUserByUsername(authentication.getPrincipal().toString());if (userDetails == null) {throw new InternalAuthenticationServiceException("无法通过手机号获取用户信息");}SmsCodeAuthenticationToken smsCodeAuthenticationToken = new SmsCodeAuthenticationToken( userDetails.getAuthorities(),userDetails);smsCodeAuthenticationToken.setDetails(authentication.getDetails());return smsCodeAuthenticationToken;}@Overridepublic boolean supports(Class<?> aClass) {return SmsCodeAuthenticationToken.class.isAssignableFrom(aClass);}public UserDetailsService getUserDetailsService() {return userDetailsService;}public void setUserDetailsService(UserDetailsService userDetailsService) {this.userDetailsService = userDetailsService;}}

4、SmsCodeAuthenticationSecurityConfig 配置类

将自定义的实现逻辑,配置到 Security 里

/*** @Author L.jg* @Title app和浏览器都需要使用,短信验证配置* @Date /5/24 17:35*/@Componentpublic class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {@Autowiredprivate LoginFailureHandler loginFailureHandler;@Autowiredprivate LoginSuccessHandler loginSuccessHandler;@Autowiredprivate UserDetailsService userDetailsService;@Overridepublic void configure(HttpSecurity builder) throws Exception {SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();smsCodeAuthenticationFilter.setAuthenticationManager(builder.getSharedObject(AuthenticationManager.class));smsCodeAuthenticationFilter.setAuthenticationFailureHandler(loginFailureHandler);smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(loginSuccessHandler);SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);builder.authenticationProvider(smsCodeAuthenticationProvider).addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);}}

5、 应用配置

/*** 短信自定义登录config*/@Autowiredprivate SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;/*** 为减少代码重复开发,多个应用使用同一个认证中心,每个应用需要自己指定登录页面。* 这里需要将 loginpage 指向一个controlelr地址。* 如果是html页面,就跳转到指定的登录页。* 如果不是html页面,就提示401 没有认证信息。* 如果有应用有指定的就使用自己的。如果没指定就使用本认证模块默认的登录页。* @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();validateCodeFilter.setAuthenticationFailureHandler(loginFailureHandler);validateCodeFilter.setSecurityProperties(securityProperties);validateCodeFilter.afterPropertiesSet();// 配置过滤器的位置http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);http.apply(smsCodeAuthenticationSecurityConfig);http.formLogin().loginPage("/authentication/require").loginProcessingUrl("/authentication/form").successHandler(loginSuccessHandler).failureHandler(loginFailureHandler)//.successForwardUrl("/index").defaultSuccessUrl("/index").and().authorizeRequests().antMatchers("/sms/code","/code/image","/authentication/require",securityProperties.getBrowser().getLoginpage()).permitAll().anyRequest().authenticated().and().csrf().disable();}

这里缺少了,登录验证码校验功能,可以参考 图像验证码校验功能,只要在 添加一个过滤器,自定义校验即可。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。