zoukankan      html  css  js  c++  java
  • 短信验证码登录思路

    短信验证码登录

    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)
                    .addFilterAfter(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;
        }
    }
    
  • 相关阅读:
    Windows统一平台: 开发小技巧
    How to install more voices to Windows Speech?
    Why does my ListView scroll to the top when navigating backwards?
    中文圣经 for Android
    [ CodeVS冲杯之路 ] P1166
    [ CodeVS冲杯之路 ] P1154
    [ CodeVS冲杯之路 ] P1048
    [ CodeVS冲杯之路 ] P1063
    [ CodeVS冲杯之路 ] P3027
    理解矩阵乘法
  • 原文地址:https://www.cnblogs.com/fly-book/p/12240768.html
Copyright © 2011-2022 走看看