Spring security(四)-spring boot+spring securityメール認証+redis統合

60431 ワード

現在主流の登録方式は主に3種類ある:アカウントパスワード登録、メール認証コード登録と第三者授権登録、前節Spring security(3)—認証過程はspring securityアカウントパスワード方式登録を分析したが、今spring securityメール方式認証登録を分析する. Spring securityメール方式、IP検証などの類似モードの登録方式の検証は、アカウントパスワード方式の登録手順に基づいて書き換えることができ、主に以下の手順で展開される.
カスタムFilter:カスタムAuthenticationカスタムAuthenticationProviderカスタムUserDetailsService SecurityConfig構成
  • カスタムフィルタ:
    コンストラクタを構築し、コンストラクタで構成要求パスおよび要求方式のフィルタカスタムattemptAuthentication()認証ステップ2ステップで認証するにはAuthenticationProviderによる最終的な認証が必要であり、認証filterではAuthenticationProviderをfilterに設定する必要があり、AuthenticationProviderを管理するのはAuthenticationManagerである.そのため、フィルタfilterを作成するときにAuthenticationManagerを設定する必要があります.この手順の詳細は5.1 SecurityConfig構成手順です.
    第2ステップにおいてattemptAuthentication()認証方法は主に以下のステップを行う.post要求認証;   2).requestは携帯電話番号と検証コードの取得を要求した.   3).カスタムAuthenticationオブジェクトで携帯電話番号と検証コードをカプセル化します.   4).AuthenticationManagerの使用authenticate()メソッドを検証します.カスタムfilter実装コード:
    public class SmsAuthenticationfilter extends AbstractAuthenticationProcessingFilter {
        private boolean postOnly = true;
    
        public SmsAuthenticationfilter() {
          super(new AntPathRequestMatcher(SecurityConstants.APP_MOBILE_LOGIN_URL, "POST"));
       }
    
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
            if (postOnly && !request.getMethod().equals("POST")) {
                  throw new AuthenticationServiceException(
                       "Authentication method not supported: " + request.getMethod());
          }
            Assert.hasText(SecurityConstants.MOBILE_NUMBER_PARAMETER, "mobile parameter must not be empty or null");
         
             String mobile = request.getParameter(SecurityConstants.MOBILE_NUMBER_PARAMETER);
            String smsCode = request.ge+tParameter(SecurityConstants.MOBILE_VERIFY_CODE_PARAMETER);
            if (mobile == null) {
                mobile="";
            }
            if(smsCode == null){
                smsCode="";
            }
            mobile = mobile.trim();
            smsCode = smsCode.trim();
            SmsAuthenticationToken authRequest = new SmsAuthenticationToken(mobile,smsCode);
    
            // Allow subclasses to set the "details" property
            setDetails(request, authRequest);
        
            return this.getAuthenticationManager().authenticate(authRequest);
        }protected void setDetails(HttpServletRequest request,
                                  SmsAuthenticationToken authRequest) {
            authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
        }
    
        public void setPostOnly(boolean postOnly) {
            this.postOnly = postOnly;
        }
    
    }
    
  • Authentication:filterおよびその後の認証にはカスタムAuthenticationオブジェクトを使用する必要があります.カスタムAuthenticationオブジェクトはU s e r m e P a s s s s s w o r d AuthenticationTokenに基づいてシミュレーションされ、AbstractAuthenticationToken抽象クラスを実現できます.カスタムSmsAuthenticationToken:
  • public class SmsAuthenticationToken extends AbstractAuthenticationToken {
    
        private final Object principal;
        private Object credentials;
    
        public SmsAuthenticationToken(Object principal,Object credentials ) {
            super(null);
            this.principal = principal;
            this.credentials=credentials;
            setAuthenticated(false);
        }
    
        public SmsAuthenticationToken(Object principal, Object credentials,Collection<? extends GrantedAuthority> authorities) {
            super(null);
            this.principal = principal;
            this.credentials=credentials;
            setAuthenticated(true);
        }
    
        @Override
        public Object getCredentials() {
            return this.credentials=credentials;
        }
    
        @Override
        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();
    
        }
    }
    

    3.AuthenticationProviderA b s t r a c t U s e r D e tailsAuthenticationProviderに従って、AuthenticationProviderおよびMessageSourceAwareインタフェースをシミュレートすることができます.認証ロジックは実装を定義できます.カスタムAuthenticationProvider:
    public class SmsAuthenticationProvide implements AuthenticationProvider, MessageSourceAware {
      private UserDetailsService userDetailsService;
      private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    
        @Override
        public void setMessageSource(MessageSource messageSource) {
            this.messages = new MessageSourceAccessor(messageSource);
        }
    
        @Override
        public Authentication authenticate(Authentication authentication) {
            Assert.isInstanceOf(SmsAuthenticationToken.class, authentication,
                    messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.onlySupports",
                            "Only UsernamePasswordAuthenticationToken is supported"));
            SmsAuthenticationToken authenticationToken = (SmsAuthenticationToken) authentication;
            //        SecurityContext  UserDetailsService    
            SecurityContext context = SecurityContextHolder.getContext();
            context.setAuthentication(authenticationToken);
            String mobile = (String) authenticationToken.getPrincipal();
            if (mobile == null) {
                throw new InternalAuthenticationServiceException("can't obtain user info ");
            }
            mobile = mobile.trim();
            //            
            UserDetails user = userDetailsService.loadUserByUsername(mobile);
            if (user == null) {
                throw new InternalAuthenticationServiceException("can't obtain user info ");
            }
            SmsAuthenticationToken smsAuthenticationToken = new SmsAuthenticationToken(user, user.getAuthorities());
            return smsAuthenticationToken;
        }
    
        @Override
        public boolean supports(Class<?> authentication) {
            return (SmsAuthenticationToken.class.isAssignableFrom(authentication));
        }
    
        public void setUserDetailsService(UserDetailsService userDetailsService) {
            this.userDetailsService = userDetailsService;
        }
    
        public UserDetailsService getUserDetailsService() {
            return userDetailsService;
        }
    }
    
  • UserDetailsService AuthenticationProviderの最終認証ポリシーエントリでは、認証方式の実現ロジックはUserDetailsServiceである.独自のプロジェクトに基づいて認証ロジックをカスタマイズできます.カスタムUserDetailsServices:
  • public class SmsUserDetailsService implements UserDetailsService {
        @Autowired
        private RedisUtil redisUtil;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            // SecurityContext         (    、   )
            SecurityContext context = SecurityContextHolder.getContext();
            SmsAuthenticationToken authentication = (SmsAuthenticationToken) context.getAuthentication();
            if(!additionalAuthenticationChecks(username,authentication)){
                return null;
            }
            //               ,     
            return new User("admin", "123456", Arrays.asList(new SimpleGrantedAuthority("admin")));
        }
    
        public boolean additionalAuthenticationChecks(String mobile, SmsAuthenticationToken smsAuthenticationToken) {
            //  redis        value   
            String smsCode = redisUtil.get(mobile).toString();
            //          
            String credentials = (String) smsAuthenticationToken.getCredentials();
            if(StringUtils.isEmpty(credentials)){
                return false;
            }
            if (credentials.equalsIgnoreCase(smsCode)) {
                return true;
            }
            return false;
        }
    }
    

    5.SecurityConfig 5.1カスタムSmsショートメッセージ検証コンポーネント構成SecurityConfig カスタムコンポーネント構成SecurityConfigでは、A b s t r a c t u t h e n t i c a tionFilterConfigurer(サブクラスFormLoginConfigurer)に従ってSmsAuthenticationSecurityConfigを模写することができ、主に以下の構成を行う.
    フィルタリングリンクにフィルタを追加し、フィルタ処理を実行するために、デフォルトのAuthenticationManager(定義可能)をカスタムfilterフィルタに設定します.カスタムのUserDetailsServiceをカスタムのAuthenticationProvideに設定します.(一般的にU s e r a m e P a sswordAuthenticationFilterの前に加算される)
    SmsAuthenticationSecurityConfigを構成するには、次の手順に従います.
    
    ```java
     @Component
     public class SmsAuthenticationSecurityConfig extends SecurityConfigurerAdapter {
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            //         SmsAuthenticationfilter,
            SmsAuthenticationfilter smsAuthenticationfilter = new SmsAuthenticationfilter();
            smsAuthenticationfilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
            smsAuthenticationfilter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler());
            smsAuthenticationfilter.setAuthenticationFailureHandler(customAuthenticationFailureHandler());
            //         SmsAuthenticationProvide
            SmsAuthenticationProvide smsAuthenticationProvide=new SmsAuthenticationProvide();
            smsAuthenticationProvide.setUserDetailsService(userDetailsService);
            http.authenticationProvider(smsAuthenticationProvide);
            //            
            http.addFilterAfter(smsAuthenticationfilter, UsernamePasswordAuthenticationFilter.class);
        }
    
        @Bean
        public CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler() {
            return new CustomAuthenticationSuccessHandler();
        }
        
        @Bean
        public CustomAuthenticationFailureHandler customAuthenticationFailureHandler() {
            return new CustomAuthenticationFailureHandler();
        }
    }
    
    
    5.2 SecurityConfig   
      SecurityConfig          Spring Security( )--WebSecurityConfigurer    filter      。
    SecurityConfig   :
    
    
    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Autowired
        private SmsAuthenticationSecurityConfig smsAuthenticationSecurityConfig;
        @Autowired
        private CustomAuthenticationSuccessHandler authenticationSuccessHandler;
        @Autowired
        private CustomAuthenticationFailureHandler authenticationFailureHandler;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.headers().frameOptions().disable().and()
                    .formLogin()
                    .loginPage(SecurityConstants.APP_FORM_LOGIN_PAGE)
                    //  form      URL
                    .loginProcessingUrl(SecurityConstants.APP_FORM_LOGIN_URL)
                    .successHandler(authenticationSuccessHandler)
                    .failureHandler(authenticationFailureHandler)
                    .and()
                    //  smsAuthenticationSecurityConfig
                    .apply(smsAuthenticationSecurityConfig)
                    .and()
                    //    URL
                    .authorizeRequests()
                    .antMatchers(SecurityConstants.APP_MOBILE_VERIFY_CODE_URL,
                                 SecurityConstants.APP_USER_REGISTER_URL)
                    .permitAll()
                    .and()
                    .csrf().disable();
        }
        @Bean
        public ObjectMapper objectMapper(){
            return new ObjectMapper();
        }
    } 
    
    
    6.  
    6.1 redis
    RedisUtil   :
    
    
    @Component
    public class RedisUtil {
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
        /**
         *       
         *
         * @param key  
         * @return  
         */
        public Object get(String key) {
            return key == null ? null : redisTemplate.opsForValue().get(key);
        }
    
        /**
         *       
         *
         * @param key    
         * @param value  
         * @return true   false  
         */
        public boolean set(String key, Object value) {
            try {
                redisTemplate.opsForValue().set(key, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         *            
         *
         * @param key    
         * @param value  
         * @param time    ( ) time   0   time    0       
         * @return true   false   
         */
        public boolean set(String key, Object value, long time) {
            try {
                if (time > 0) {
                    redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
                } else {
                    set(key, value);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
     }
    
    
    redisConfig   :
    
    
    @Configuration
    public class RedisConfig {
    @Autowired
    private RedisProperties properties;
    @Bean
    @SuppressWarnings("all")
    @ConditionalOnClass(RedisConnectionFactory.class)
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
            RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
            template.setConnectionFactory(factory);
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
            ObjectMapper om = new ObjectMapper();
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            jackson2JsonRedisSerializer.setObjectMapper(om);
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
            // key  String      
            template.setKeySerializer(stringRedisSerializer);
            // hash key   String      
            template.setHashKeySerializer(stringRedisSerializer);
            // value       jackson
            template.setValueSerializer(jackson2JsonRedisSerializer);
            // hash value       jackson
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            template.afterPropertiesSet();
            return template;
        }
        @Bean
        @Qualifier("redisConnectionFactory")
        public RedisConnectionFactory redisConnectionFactory(){
            RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
            redisConfig.setHostName(properties.getHost());
            redisConfig.setPort(properties.getPort());
            redisConfig.setPassword(RedisPassword.of(properties.getPassword()));
            redisConfig.setDatabase(properties.getDatabase());
            //redis       
            JedisClientConfiguration.JedisClientConfigurationBuilder builder = JedisClientConfiguration.builder();
            if (this.properties.getTimeout() != null) {
                Duration timeout = this.properties.getTimeout();
                builder.readTimeout(timeout).connectTimeout(timeout);
            }
            RedisProperties.Pool pool = this.properties.getJedis().getPool();
            if (pool != null) {
                builder.usePooling().poolConfig(this.jedisPoolConfig(pool));
            }
            JedisClientConfiguration jedisClientConfiguration = builder.build();
            //         JedisConnectionFactory
            return new JedisConnectionFactory(redisConfig,jedisClientConfiguration);
    
        }
        private JedisPoolConfig jedisPoolConfig(RedisProperties.Pool pool) {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(pool.getMaxActive());
            config.setMaxIdle(pool.getMaxIdle());
            config.setMinIdle(pool.getMinIdle());
            if (pool.getMaxWait() != null) {
                config.setMaxWaitMillis(pool.getMaxWait().toMillis());
            }
            return config;
        }
    }