短信验证码登录思路

短信验证码登录

public class ValidateCode {
    private String code;
    //有效期
    private LocalDateTime expireTime;

    public ValidateCode(String code, int expireTime) {
        this.code = code;
        this.expireTime = LocalDateTime.now().plusSeconds(expireTime);
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public LocalDateTime getExpireTime() {
        return expireTime;
    }

    public void setExpireTime(LocalDateTime expireTime) {
        this.expireTime = expireTime;
    }

    public boolean isExpried() {
        return LocalDateTime.now().isAfter(expireTime);
    }

}
/**
 * 短信发送接口
 */
public interface SmsCodeSender {
    void send(String mobile, String code);
}
public class DefaultSmsCodeSender implements SmsCodeSender {
    @Override
    public void send(String mobile, String code) {
        System.out.println("向手机"+mobile+"发送短信验证码"+code);
    }
}
@Component("smsValidateCodeGenerator")
public class SmsCodeGenerator implements ValidateCodeGenerator {
    @Autowired
    private SecurityProperties securityProperties;
    public void setSecurityProperties(SecurityProperties securityProperties) {
        this.securityProperties = securityProperties;
    }
    @Override
    public ValidateCode generate(HttpServletRequest request) {
        String code = RandomStringUtils.randomNumeric(securityProperties.getCode().getSmsCode().getLength());
        return new ValidateCode(code, securityProperties.getCode().getSmsCode().getExpireIn());
    }
}

只有在用户没有实现smsCodeSender时才会使用默认实现

@Configuration
public class ValidateCodeBeanConfig {
    @Autowired
    private SecurityProperties securityProperties;

    @Bean
    @ConditionalOnMissingBean(name = "smsCodeSender")
    public SmsCodeSender smsCodeSender(){
        return new DefaultSmsCodeSender();
    }
}
@RestController
public class ValidateCodeController {
    public static final String SESSION_KEY_SMS = "smscode";

    @Autowired
    @Qualifier("smsValidateCodeGenerator")
    private ValidateCodeGenerator smsValidateCodeGenerator;

    @Autowired
    private SmsCodeSender smsCodeSender;

    @GetMapping("/code/sms")
    public void getsmsCaptcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ValidateCode smsCode = smsValidateCodeGenerator.generate(request);
        request.getSession().setAttribute(SESSION_KEY_SMS,smsCode);
        smsCodeSender.send(ServletRequestUtils.getRequiredStringParameter(request,"mobile"),smsCode.getCode());
    }
}

现在已经有了两种验证方式,接下来我们进行代码重构

用到一个session的操作工具SessionStrategy,需要引入依赖

        <dependency>
            <groupId>org.springframework.social</groupId>
            <artifactId>spring-social-web</artifactId>
            <version>1.1.6.RELEASE</version>
        </dependency>
/**
 * 校验码处理器,封装不同校验码的处理逻辑
 */
public interface ValidateCodeProcessor {
    //验证码放入session时的前缀
    String SESSION_KEY_PREFIX = "SESSION_KEY_FOR_CODE_";
    //创建校验码 ServletWebRequest 已经包含request response
    void create(ServletWebRequest request) throws Exception;
}
public abstract class AbstractValidateCodeProcessor<C extends ValidateCode> implements ValidateCodeProcessor {
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    /**
     * 收集系统中所有的 {@link ValidateCodeGenerator} 接口的实现。
     */
    @Autowired
    private Map<String, ValidateCodeGenerator> validateCodeGenerators;

    @Override
    public void create(ServletWebRequest request) throws Exception {
        C validateCode = generate(request);
        save(request,validateCode);
        send(request,validateCode);
    }

    /**
     * 保存验证码到session
     * @param request
     * @param validateCode
     */
    private void save(ServletWebRequest request, C validateCode){
        sessionStrategy.setAttribute(request,getSessionKey(request),validateCode);

    }

    private String getSessionKey(ServletWebRequest request){
        return SESSION_KEY_PREFIX + getProcessorType(request).toUpperCase();
    }

    /**
     * 发送验证码有子类实现
     * @param request
     * @param validateCode
     * @throws Exception
     */
    protected abstract void send(ServletWebRequest request, C validateCode) throws Exception;

    //生成验证码
    @SuppressWarnings("unchecked")
    private C generate(ServletWebRequest request) {
        String type = getProcessorType(request);
        ValidateCodeGenerator validateCodeGenerator = validateCodeGenerators.get(type + "CodeGenerator");
        return (C) validateCodeGenerator.generate(request);
    }

    //根据请求的url获取校验码的类型
    private String getProcessorType(ServletWebRequest request){
        return StringUtils.substringAfter(request.getRequest().getRequestURI(),"/code/");
    }
}
public class ValidateCode {
    private String code;
    //有效期
    private LocalDateTime expireTime;

    public ValidateCode(String code, int expireTime) {
        this.code = code;
        this.expireTime = LocalDateTime.now().plusSeconds(expireTime);
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public LocalDateTime getExpireTime() {
        return expireTime;
    }

    public void setExpireTime(LocalDateTime expireTime) {
        this.expireTime = expireTime;
    }

    public boolean isExpried() {
        return LocalDateTime.now().isAfter(expireTime);
    }
}
public class ImageCode extends ValidateCode {
    private BufferedImage image;

    public ImageCode(String code, int expireTime,BufferedImage image) {
        super(code,expireTime);
        this.image = image;
    }

    public BufferedImage getImage() {
        return image;
    }

    public void setImage(BufferedImage image) {
        this.image = image;
    }
}
@Component("imageCodeProcessor")
public class ImageCodeProcessor extends AbstractValidateCodeProcessor<ImageCode> {

    @Override
    protected void send(ServletWebRequest request, ImageCode validateCode) throws Exception {
        ServletOutputStream out = request.getResponse().getOutputStream();
        ImageIO.write(validateCode.getImage(),"jpg",out);
    }
}

短信的

@Component("smsCodeProcessor")
public class SmsCodeProcessor extends AbstractValidateCodeProcessor<ValidateCode> {
    @Autowired
    private SmsCodeSender smsCodeSender;
    @Override
    protected void send(ServletWebRequest request, ValidateCode validateCode) throws Exception {
        String mobile = ServletRequestUtils.getRequiredStringParameter(request.getRequest(), "mobile");
        smsCodeSender.send(mobile, validateCode.getCode());
    }
}

默认bean配置

@Configuration
public class ValidateCodeBeanConfig {
    @Autowired
    private SecurityProperties securityProperties;

    @Bean
    @ConditionalOnMissingBean(name = "imageCodeGenerator")
    public ValidateCodeGenerator imageCodeGenerator(){
        ImageCodeGenerator codeGenerator = new ImageCodeGenerator();
        codeGenerator.setSecurityProperties(securityProperties);
        return codeGenerator;
    }

    @Bean
    @ConditionalOnMissingBean(name = "smsCodeSender")
    public SmsCodeSender smsCodeSender(){
        return new DefaultSmsCodeSender();
    }
}

修改controller

@RestController
public class ValidateCodeController {
    @Autowired
    private Map<String, ValidateCodeProcessor> validateCodeProcessors;

    @GetMapping("/code/{type}")
    public void getCaptcha(@PathVariable("type") String type, HttpServletRequest request, HttpServletResponse response) throws Exception {
        validateCodeProcessors.get(type+"CodeProcessor").create(new ServletWebRequest(request,response));
    }
}


/**
 * 参考 {@link UsernamePasswordAuthenticationToken}
 */
public class SmsAuthenticationToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    private final Object principal;

    public SmsAuthenticationToken(Object principal) {
        super(null);
        this.principal = principal;
        setAuthenticated(false);
    }

    public SmsAuthenticationToken(Object principal,
                                               Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        super.setAuthenticated(true); // must use super, as we override
    }

    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();
    }
}
public class SmsAuthenticationProvider implements AuthenticationProvider {
    private UserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        SmsAuthenticationToken smsAuthenticationToken = (SmsAuthenticationToken) authentication;
        UserDetails user = userDetailsService.loadUserByUsername((String) smsAuthenticationToken.getPrincipal());
        if (user == null) {
            throw new InternalAuthenticationServiceException("无法获取用户信息");
        }
        SmsAuthenticationToken authenticationResult = new SmsAuthenticationToken(user, user.getAuthorities());
        authenticationResult.setDetails(smsAuthenticationToken.getDetails());
        return authenticationResult;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return SmsAuthenticationToken.class.isAssignableFrom(authentication);
    }

    public UserDetailsService getUserDetailsService() {
        return userDetailsService;
    }

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
}

/**
 * 参考{@link UsernamePasswordAuthenticationFilter}
 */
public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";

    private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;
    private boolean postOnly = true;

    public SmsAuthenticationFilter() {
        super(new AntPathRequestMatcher("/authentication/mobile", "POST"));
    }

    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();

        SmsAuthenticationToken authRequest = new SmsAuthenticationToken(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,
                              SmsAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

    public void setMobileParameter(String mobileParameter) {
        Assert.hasText(mobileParameter, "mobile parameter must not be empty or null");
        this.mobileParameter = mobileParameter;
    }

    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }

    public final String getMobileParameter() {
        return mobileParameter;
    }
}

@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;
    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;

    @Override
    public void configure(HttpSecurity builder) throws Exception {
        SmsAuthenticationFilter filter = new SmsAuthenticationFilter();
        filter.setAuthenticationManager(builder.getSharedObject(AuthenticationManager.class));
        filter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
        filter.setAuthenticationFailureHandler(authenticationFailureHandler);
        SmsAuthenticationProvider provider = new SmsAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);

        builder.authenticationProvider(provider)
                .addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
    }
}

public class SmsValidateCodeFilter extends OncePerRequestFilter {
    public SmsValidateCodeFilter(AuthenticationFailureHandler flyAuthenticationFailureHandler) {
        this.flyAuthenticationFailureHandler = flyAuthenticationFailureHandler;
    }

    private AuthenticationFailureHandler flyAuthenticationFailureHandler;

    public AuthenticationFailureHandler getFlyAuthenticationFailureHandler() {
        return flyAuthenticationFailureHandler;
    }

    public void setFlyAuthenticationFailureHandler(AuthenticationFailureHandler flyAuthenticationFailureHandler) {
        this.flyAuthenticationFailureHandler = flyAuthenticationFailureHandler;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        if ("/authentication/mobile".equals(httpServletRequest.getRequestURI())&&"post".equalsIgnoreCase(httpServletRequest.getMethod())){
            try {
                validate(httpServletRequest);
            }catch (VerificationCodeException e){
                flyAuthenticationFailureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,e);
                return;
            }
        }
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }

    private void validate(HttpServletRequest request) {
        HttpSession session = request.getSession();
        ValidateCode codeInSession = (ValidateCode) session.getAttribute("SESSION_KEY_FOR_CODE_SMS");
        String smsCode = request.getParameter("smsCode");
        if (StringUtils.isEmpty(smsCode)){
            throw new VerificationCodeException("验证码的值不能为空");
        }
        if (codeInSession==null){
            throw new VerificationCodeException("验证码不存在");
        }
        if (codeInSession.isExpried()){
            session.removeAttribute("SESSION_KEY_FOR_CODE_SMS");
            throw new VerificationCodeException("验证码已过期");
        }
        if (!smsCode.equals(codeInSession.getCode())){
            throw new VerificationCodeException("验证码不匹配");
        }
        session.removeAttribute("SESSION_KEY_FOR_CODE_SMS");
    }
}

修改WebSecurityConfig加入ValidateCodeFilter与smsCodeAuthenticationSecurityConfig


@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private AuthenticationSuccessHandler flyAuthenticationSuccessHandler;
    @Autowired
    private AuthenticationFailureHandler flyAuthenticationFailureHandler;

    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private PasswordEncoder passwordEncoder;
    @Bean
    public PasswordEncoder setPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Autowired
    private DataSource dataSource;

    @Autowired
    private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;

    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
//        tokenRepository.setCreateTableOnStartup(true);
        tokenRepository.setDataSource(dataSource);
        return tokenRepository;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ValidateCodeFilter codeFilter = new ValidateCodeFilter(flyAuthenticationFailureHandler);
        SmsValidateCodeFilter smsValidateCodeFilter = new SmsValidateCodeFilter(flyAuthenticationFailureHandler);
            http
                .addFilterBefore(codeFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(smsValidateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                .formLogin()
                .loginPage("/authentication/request")
                .loginProcessingUrl("/authentication/form")
                .successHandler(flyAuthenticationSuccessHandler)
                .failureHandler(flyAuthenticationFailureHandler)
                .and()
                .rememberMe()
                    .tokenRepository(persistentTokenRepository())
                    .tokenValiditySeconds(securityProperties.getBrowser().getRememberMe())
                    .userDetailsService(userDetails())
                .and()
                .authorizeRequests()
                .antMatchers("/authentication/request",
                        securityProperties.getBrowser().getLoginPage(),
                        "/code/*")
                .permitAll()
                .anyRequest().authenticated()
                .and().csrf().disable()
                    .apply(smsCodeAuthenticationSecurityConfig);
    }
    @Bean
    public UserDetailsService userDetails(){
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("user").password(passwordEncoder.encode("123")).roles("USER").build());
        manager.createUser(User.withUsername("13312345678").password(passwordEncoder.encode("123")).roles("USER").build());
        return manager;
    }
}

原文地址:https://www.cnblogs.com/fly-book/p/12240768.html

时间: 2024-08-29 12:46:12

短信验证码登录思路的相关文章

vue实现短信验证码登录

无论是移动端还是pc端登录或者注册界面都会见到手机验证码登录这个功能,输入手机号,得到验证码,最后先服务器发送请求,保存登录的信息,一个必不可少的功能 思路 1,先判断手机号和验证是否为空, 2,点击发送验证码,得到验证码 3,输入的验证码是否为空和是否正确, 4,最后向服务发送请求 界面展示 1.准备工作 这个会对input进行封装处理 <template> <div class="text_group"> <div class="input_

项目总结手机号+短信验证码登录

首先,需要一个电话号码,目前很多账户都是将账户名设置成手机号,然后点击按钮获取手机验证码. 其次,你需要后台给你手机短信的验证接口,各个公司用的不一样,这个身为前端,不需要你来考虑,你只要让你后台给你写好接口,你直接调用就好了. activity_login.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.andr

手机号+短信验证码登录注意

首先我们 说下成员变量和局部变量 简单介绍下 成员变量 :定义在class里面  方法外边 局部变量: 定义在方法里面 如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,它们对该成员变量是彼此影响的,也就是说一个线程对成员变量的改变会影响到另一个线程. 如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝(即便是同一个对象中的方法的局部变量,也会对每一个线程有一个拷贝),一个线程对该局部变量的改变不会影响到其他线程 如果你定义了两个成员变量分别来存储手机号和验证码

JAVA利用第三方平台发送短信验证码。

前段时间自己做的一个小项目中,涉及到用短信验证码登录.注册的问题,之前没涉及过这一块,看了别人的博客其实也是似懂非懂的,现在就将自己做的利用第三方短信平台来发送验证码这个功能记下来. 本文以注册为例,在SpringMVC+Spring+Mybatis框架的基础上完成该短信验证码功能. 发送短信验证码的原理是:随机生成一个6位数字,将该6位数字保存到session当中,客户端通过sessionid判断对应的session,用户输入的验证码再与session记录的验证码进行比较. 为了防止有广告嫌疑

发送短信验证码和邮箱验证码—Java实现

短信验证码 短信验证码都是调用一些接口来进行短信的发送,短信验证码在登录.注册等操作中使用的最广泛,本文这一节演示如何使用Java制作一个简单的短信验证码登录. 我这里演示使用的是聚合数据的短信接口(并非广告),因为聚合数据的接口调用比较方便和简单,所以首先得先去聚合数据里申请一个短信接口API: 申请时会需要你进行实名认证,如果你不想认证的话跳过认证也是可以的. 申请完短信API进入以下界面后点击"模板": 需要先申请一个短信模板,根据自己的需求定义这个模板内容(定义前先阅读此页面下

短信验证码实现

项目做了登录改密功能需要验证码的功能. 思路:页面点击获取验证码按钮,发送请求到后台,携带用户名作为参数.后台做一个servlet查询该用户的手机号.生成随机验证码.将验证码(+消息)+tel作为参数+其他接口参数拼成url调用第三方服务(云信). 实现: 1.页面js方法 function get_code_time(){ var uname = $("#uname").val(); if(!uname){ $("#users").html("请先输入您

android发送短信验证码并自动获取验证码填充文本框

android注册发送短信验证码并自动获取短信,截取数字验证码填充文本框. 一.接入短信平台 首先需要选择短信平台接入,这里使用的是榛子云短信平台(http://smsow.zhenzikj.com), 两分钟申请测试账号,赠送了100条测试短信. android使用java的jar包即可开发 jar下载: http://smsow.zhenzikj.com/doc/sdk.html API文档:http://smsow.zhenzikj.com/doc/java_sdk_doc.html 使用

性能测试:Jmeter压测过程中的短信验证码读取

问题背景 现如今国内的大部分软件或者网站应用,普遍流行使用短信业务,比如登录.注册以及特定的业务通知等. 对于这些业务,在使用Jmeter进行性能测试的过程中,就会需要自动获取和填入短信验证码,否则性能流程无法进行下去. 由于绝大多数的系统其短信验证码并不会在接口返回中,因此如何获取短信验证码是一个问题. 最简单的做法,是让开发在测试环境将验证码写死,在测试过程中固定使用静态验证码字串. 不过求人不如求己~也是出于尽量贴近真实用户场景的目的,更合适的做法还是通过技术手段动态获取并填写短信验证码.

thinkphp集成系列之短信验证码、订单通知

现在这个短信通知泛滥的年代:应用如果没有个短信注册:你都不敢说你是搞开发的: 这个验证码搞起来是不难的:但是如果刚接触也是有点不知从哪下手的迷茫: 先讲下概念: 要想发送验证码:需要至少三项:appid.key.模板id: appid.和key比如较容易理解:各种第三方平台都会提供的:可能叫法不一样: 模板id就是指的短信的内容:例如[淘宝]验证码是192612,请您在5分钟内输入: 这就是一个模板:我们发短信的时候只能改变192612.和5这两个数字:其他是固定的: 因为政策的问题:模板需要申