博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring Security 自定义短信验证码登录
阅读量:4185 次
发布时间:2019-05-26

本文共 9774 字,大约阅读时间需要 32 分钟。

短信验证码登录的思路,需要通过验证码过滤器,过滤验证码是否正确。次过程和图形验证码校验逻辑完全一样。 之后,需要通过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 2021/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;    }    @Override    public ValidateCode generate(HttpServletRequest request) {        String smsCode = RandomUtil.randomNumbers(smsProperties.getLength());        return new ValidateCode(smsCode, smsProperties.getExpireTime());    }}

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

@Configurationpublic class VlidateCodeConfig {    @Autowired    private 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 {    @Override    public 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
authorities, Object principal) { super(authorities); this.principal = principal; super.setAuthenticated(true); } @Override public Object getCredentials() { return null; } @Override public Object getPrincipal() { return this.principal; } @Override public 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;    @Override    public 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;    }    @Override    public 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 2021/5/24 17:35 */@Componentpublic class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter
{ @Autowired private LoginFailureHandler loginFailureHandler; @Autowired private LoginSuccessHandler loginSuccessHandler; @Autowired private UserDetailsService userDetailsService; @Override public 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     */    @Autowired    private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;    /**     *  为减少代码重复开发,多个应用使用同一个认证中心,每个应用需要自己指定登录页面。     *  这里需要将 loginpage 指向一个controlelr地址。     *  如果是html页面,就跳转到指定的登录页。     *  如果不是html页面,就提示401 没有认证信息。     *  如果有应用有指定的就使用自己的。如果没指定就使用本认证模块默认的登录页。     * @param http     * @throws Exception     */    @Override    protected 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();    }

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

转载地址:http://ydfoi.baihongyu.com/

你可能感兴趣的文章
Sqoop是什么
查看>>
宽带是多少
查看>>
Hadoop和Spark的异同
查看>>
avro 是什么
查看>>
什么叫容灾
查看>>
tower.im、Worktile、钉钉有什么不同
查看>>
OLAP、OLTP的介绍和比较
查看>>
Hadoop ,storm,spark 的特点
查看>>
MapReduce Tez Storm Spark四个框架的异同
查看>>
kudu存储引擎
查看>>
PHP语法1
查看>>
Linux如何查看端口状态
查看>>
Guava cache 缓存
查看>>
UUID.randomUUID()是什么
查看>>
TimeUnit是什么
查看>>
2017年大数据的变化趋势
查看>>
作业、任务、进程、线程的区别
查看>>
laypage分页
查看>>
ojdbc14.jar 与ojdbc6.jar的区别
查看>>
如何区分Oracle的数据库,实例,服务名,SID
查看>>