spring securiry登録には認証コードフィルタを追加します.

13418 ワード

暇なので、半月ほどスプリントsecurityを見ました.ログイン時に、より多くのユーザデータを検証する方法を考えています.例えば、認証コードです.
spring security公式文書の第10章で、spring security検証の手順を述べた.
spring securityではデフォルトの検証方法はすべてProvider Managerを呼び出すことによって、authentication Providersを輪講して一つ一つ検証して、検証の目的を達成します.しかし、デフォルトの検証以外の検証はサポートされていません.そこで次のような考えができました.
私達もFilterをカスタマイズして、彼をspring securityの中のfilterChinに追加して、spring secutiryの検証構造によって検証の構造を拡張します.
筆者は以下のコードを、spring securityのUsernamePassword Authentication Filterを参照して実現した.
  • 具体的な手順は以下の通りです.
  • は、認証コード認証データ
  • を保存するための認証コードtokenクラスをカスタマイズする.
    public class CodeAuthenticationToken extends AbstractAuthenticationToken{
    
        /**
         *    
         */
        private Object captcha;
    
        /**
         *        
         * true:  
         * false:  
         */
        private boolean flag;
    
        public CodeAuthenticationToken() {
            super(null);
        }
    
        public CodeAuthenticationToken(Object captcha) {
            super(null);
            this.captcha = captcha;
            this.flag = false;
            setAuthenticated(false);
        }
    
        public CodeAuthenticationToken(Collection extends GrantedAuthority> authorities, Object captcha) {
            super(authorities);
            this.captcha = captcha;
            this.flag = false;
            super.setAuthenticated(true); // must use super, as we override
        }
    
        @Override
        public Object getCredentials() {
            return null;
        }
    
        @Override
        public Object getPrincipal() {
            return null;
        }
    
        public Object getCaptcha() {
            return captcha;
        }
    
        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);
        }
    
    
        public boolean isFlag() {
            return flag;
        }
    
        public void setFlag(boolean flag) {
            this.flag = flag;
        }
    }
    
  • 認証失敗異常クラスをカスタマイズします.このクラスはAuthentication Exceptionクラス
  • を継承しなければなりません.
    public class CodeAuthenticationException extends AuthenticationException {
    
        public CodeAuthenticationException(String msg, Throwable t) {
            super(msg, t);
        }
    
        public CodeAuthenticationException(String msg) {
            super(msg);
        }
    }
    
  • は、検証コード検証器
  • を提供する.
    public class CodeAuthenticationProvider implements AuthenticationProvider {
    
        /** Logger available to subclasses */
        protected final Log logger = LogFactory.getLog(getClass());
    
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            logger.debug("custom captcha authentication");
    
            Assert.isInstanceOf(CodeAuthenticationToken.class, authentication,"    ");
    
            CodeAuthenticationToken custCaptchaToken = (CodeAuthenticationToken) authentication;
            String captcha = custCaptchaToken.getCaptcha().toString();
    
            if(captcha.equals("")){
                logger.debug("     ");
                throw new CodeAuthenticationException("     !");
            }
    
            //       ,        
            if(!captcha.equals("1000")){
                logger.debug("     ");
                throw new CodeAuthenticationException("     !");
            }
    
            //        
            custCaptchaToken.setFlag(true);
            return custCaptchaToken;
        }
    
        @Override
        public boolean supports(Class> authentication) {
            return (CodeAuthenticationToken.class.isAssignableFrom(authentication));
        }
    }
    
  • カスタム認証マネージャはここでは省略できます.ここではspring securityの構成によって実現されるので、省略されていません.
  • public class CodeAuthenticationManager implements AuthenticationManager {
    
        /**
         *            
         */
        private AuthenticationProvider provider;
    
        public CodeAuthenticationManager(AuthenticationProvider provider) {
            Assert.notNull(provider, "provider cannot be null");
            this.provider = provider;
        }
    
        public Authentication authenticate(Authentication authentication)
                throws AuthenticationException {
            return this.provider.authenticate(authentication);
        }
    }
    
  • 私たち自身のfilter
  • public class CodeFilter extends GenericFilterBean {
    
        /** Logger available to subclasses */
        protected final Log logger = LogFactory.getLog(getClass());
    
        //       
        private static final String CODE_ANT_URL = "/login";
        private static final String SPRING_SECURITY_FORM_CAPTCHA_KEY = "code";
    
        private String captchaParameter = SPRING_SECURITY_FORM_CAPTCHA_KEY;
    
        private boolean postOnly = true;
    
        //      
        private RequestMatcher requiresAuthenticationRequestMatcher;
    
        private RememberMeServices rememberMeServices = new NullRememberMeServices();
        private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler(CODE_ANT_URL); //           
    
        public CodeFilter() {
            this.requiresAuthenticationRequestMatcher = new AntPathRequestMatcher(CODE_ANT_URL, "POST");
        }
    
        public CodeFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
            Assert.notNull(requiresAuthenticationRequestMatcher,"requiresAuthenticationRequestMatcher cannot be null");
            this.requiresAuthenticationRequestMatcher = requiresAuthenticationRequestMatcher;
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            //          ,        
            if (!requiresAuthentication(request, response)) {
                filterChain.doFilter(request, response);
                return;
            }
    
            if (logger.isDebugEnabled()) {
                logger.debug("Request is to process authentication");
            }
    
            Authentication authResult;
            try {
                authResult = this.attemptAuthentication(request, response);
                if (authResult == null) {
                    logger.error("Authentication is null!");
                    // return immediately as subclass has indicated that it hasn't completed
                    // authentication
                    return;
                }
    
            } catch (InternalAuthenticationServiceException failed) {
                logger.error("An internal error occurred while trying to authenticate the user.",failed);
                return;
            } catch (AuthenticationException failed) {
                logger.error("Authentication failed.", failed);
                //Authentication failed
                unsuccessfulAuthentication(request, response, failed);
                return;
            }
    
            //    ,       
            filterChain.doFilter(request, response);
        }
    
        private Authentication attemptAuthentication(HttpServletRequest request,
                                                    HttpServletResponse response) throws AuthenticationException {
            if (postOnly && !request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException(
                        "Authentication method not supported: " + request.getMethod());
            }
            //     
            String captcha = request.getParameter(captchaParameter);
    
            if (captcha == null) {
                captcha = "";
            }
    
            captcha = captcha.trim();
    
            CodeAuthenticationToken authRequest = new CodeAuthenticationToken(captcha);
    
            //         , provider    
            CodeAuthenticationManager manager = new CodeAuthenticationManager(new CodeAuthenticationProvider());
            return manager.authenticate(authRequest);
        }
    
        /**
         *            
         *
         * @param request
         * @param response
         * @return
         */
        protected boolean requiresAuthentication(HttpServletRequest request,
                                                 HttpServletResponse response) {
            return requiresAuthenticationRequestMatcher.matches(request);
        }
    
        /**
         *          
         *    {@link org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter}
         * Default behaviour for unsuccessful authentication.
         * 
      *
    1. Clears the {@link SecurityContextHolder}
    2. *
    3. Stores the exception in the session (if it exists or * allowSesssionCreation is set to true)
    4. *
    5. Informs the configured RememberMeServices of the failed login
    6. *
    7. Delegates additional behaviour to the {@link AuthenticationFailureHandler}.
    8. *
    */ protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { SecurityContextHolder.clearContext(); if (logger.isDebugEnabled()) { logger.debug("Authentication request failed: " + failed.toString(), failed); logger.debug("Updated SecurityContextHolder to contain null Authentication"); logger.debug("Delegating to authentication failure handler " + failureHandler); } rememberMeServices.loginFail(request, response); failureHandler.onAuthenticationFailure(request, response, failed); } }
    以上は自分で実現する必要があります.以下の筆者はspring bootでspring securityの中のdemoを統合して、検証コード登録を実現します.
    ステップは以下の通りです
  • 上記の5つのクラスをプロジェクトに追加します.
  • WebSecurityConfigを変更する
  • @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            http.addFilterBefore(getFilter(), UsernamePasswordAuthenticationFilter.class) //             
                .authorizeRequests()
                    .antMatchers("/", "/home").permitAll()
                    .anyRequest().authenticated()
                    .and()
                .formLogin()
                    .loginPage("/login")
                    .permitAll()
                    .and()
                .logout()
                    .permitAll();
        }
    
    
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            //         
            auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
        }
    
    
        //            
        @Bean
        public CodeFilter getFilter(){
            return new CodeFilter();
        }
    }
    
  • ログインビューコントローラのコメントを修正します.「login」.
  • @Configuration
    public class MvcConfig extends WebMvcConfigurerAdapter {
        
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
            registry.addViewController("/home").setViewName("home");
            registry.addViewController("/").setViewName("home");
            registry.addViewController("/hello").setViewName("hello");
            //registry.addViewController("/login").setViewName("login");
        }
    
    }
    
  • 新規登録ビューコントローラ
  • @Controller
    @RequestMapping("/")
    public class LoginController {
    
        //          
        private static final String LAST_EXCEPTION_KEY = "SPRING_SECURITY_LAST_EXCEPTION";
    
        @RequestMapping("/login")
        public String login(HttpServletRequest request, HttpSession session){
            //spring security        session 。
            AuthenticationException authenticationException = (AuthenticationException) session.getAttribute(LAST_EXCEPTION_KEY);
    
            //                    
           if(authenticationException != null && authenticationException instanceof CodeAuthenticationException){
                CodeAuthenticationException c = (CodeAuthenticationException) authenticationException;
                //         ,  request        。       
                request.setAttribute("codeError",true);
            }
            return "login";
        }
    }
    
  • 最後にログインインターフェースに認証コード入力ボックス
  • を追加します.
    
    
        
            Spring Security Example 
        
        
            
    Invalid username and password.
    You have been logged out.
    !
    本論文の内容はspring boot 1.5.10とspring security 4.2.4に基づいて実現されるspring security登録検証コードを追加し、既存の検証メカニズムの場合、検証コード検証メカニズムを追加した.
    テストデータのユーザー名は:userパスワード:password検証コード:1000例は比較的簡単で、検証コードは直接に書き込みました.興味のある人は自分で直してもいいです.
    この例のソースコード:https://gitee.com/longguiyunjosh/spring-security-demo/tree/master
    足りないところを補充してください.もし転載するなら、出典を明記してください.