代码之家  ›  专栏  ›  技术社区  ›  Stefan Falk

如何捕获和处理InvalidGrantException(用户已禁用)?

  •  3
  • Stefan Falk  · 技术社区  · 6 年前

    我注意到我的 ResponseEntityExceptionHandler 无法处理Spring启动应用程序中Spring Security引发的异常。

    InvalidGrantException

    用例很简单:如果用户当前被禁用,我想向我的客户机s.t抛出一个适当的错误。它可以相应地显示一条消息。

    {
      error: "invalid_grant", 
      error_description: "User is disabled"
    }
    

    我看到 this question 但出于某种原因,我的 AuthFailureHandler 未被调用:

    @Configuration
    @EnableResourceServer
    public class ResourceServer extends ResourceServerConfigurerAdapter {
    
        @Component
        public class AuthFailureHandler implements AuthenticationEntryPoint {
    
            @Override
            public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
    
                // Never reached ..
                System.out.println("Hello World!");
            }
        }
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.exceptionHandling()                        
                .authenticationEntryPoint(customAuthEntryPoint());;
        }
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.authenticationEntryPoint(customAuthEntryPoint());
        }
    
        @Bean
        public AuthenticationEntryPoint customAuthEntryPoint(){
            return new AuthFailureHandler();
        }
    
    }
    

    知道我错过了什么吗?


    配置代码

    这是我的 OAuth2Configuration 其中还包含 ResourceServerConfiguraerAdapter

    @Configuration
    @EnableAuthorizationServer
    @EnableResourceServer
    public class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {
    
        private final TokenStore tokenStore;
    
        private final JwtAccessTokenConverter accessTokenConverter;
    
        private final AuthenticationManager authenticationManager;
    
        @Autowired
        public OAuth2Configuration(TokenStore tokenStore, JwtAccessTokenConverter accessTokenConverter, AuthenticationManager authenticationManager) {
            this.tokenStore = tokenStore;
            this.accessTokenConverter = accessTokenConverter;
            this.authenticationManager = authenticationManager;
        }
    
        @Value("${security.jwt.client-id}")
        private String clientId;
    
        @Value("${security.jwt.client-secret}")
        private String clientSecret;
    
        @Value("${security.jwt.scope-read}")
        private String scopeRead;
    
        @Value("${security.jwt.scope-write}")
        private String scopeWrite;
    
        @Value("${security.jwt.resource-ids}")
        private String resourceIds;
    
        private final static String WEBHOOK_ENDPOINTS = "/r/api/*/webhooks/**";
    
        private final static String PUBLIC_ENDPOINTS = "/r/api/*/public/**";
    
        @Override
        public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
            configurer
                    .inMemory()
                    .withClient(clientId)
                    .secret(clientSecret)
                    .scopes(scopeRead, scopeWrite)
                    .resourceIds(resourceIds)
                    .accessTokenValiditySeconds(60*60*24*7 * 2) // Access tokens last two weeks
                    .refreshTokenValiditySeconds(60*60*24*7 * 12) // Refresh tokens last 12 weeks
                    //.accessTokenValiditySeconds(5)
                    //.refreshTokenValiditySeconds(10)
                    .authorizedGrantTypes("password", "refresh_token"); //, "client_credentials");
        }
    
    
        /**
         * Since there are currently multiply clients, we map the OAuth2 endpoints under /api
         * to avoid conflicts with @{@link ForwardController}
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    
            TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
            enhancerChain.setTokenEnhancers(Collections.singletonList(accessTokenConverter));
    
            endpoints.tokenStore(tokenStore)
    
                    // Create path mappings to avoid conflicts with forwarding controller
    
                    .pathMapping("/oauth/authorize", "/api/v1/oauth/authorize")
                    .pathMapping("/oauth/check_token", "/api/v1/oauth/check_token")
                    .pathMapping("/oauth/confirm_access", "/api/v1/oauth/confirm_access")
                    .pathMapping("/oauth/error", "/api/v1/oauth/error")
                    .pathMapping("/oauth/token", "/api/v1/oauth/token")
    
                    .accessTokenConverter(accessTokenConverter)
                    .tokenEnhancer(enhancerChain)
                    .reuseRefreshTokens(false)
                    .authenticationManager(authenticationManager);
        }
    
        /**
         * Forwarding controller.
         *
         * This controller manages forwarding in particular for the static web clients. Since there are multiple
         * clients, this controller will map <i>any</i> GET request to the root /* to one of the clients.
         *
         * If no match was found, the default redirect goes to /web/index.html
         *
         */
        @Controller
        public class ForwardController {
    
            @RequestMapping(value = "/sitemap.xml", method = RequestMethod.GET)
            public String redirectSitemapXml(HttpServletRequest request) {
                return "forward:/a/web/assets/sitemap.xml";
            }
    
            @RequestMapping(value = "/robots.txt", method = RequestMethod.GET)
            public String redirectRobotTxt(HttpServletRequest request) {
                return "forward:/a/web/assets/robots.txt";
            }
    
            @RequestMapping(value = "/*", method = RequestMethod.GET)
            public String redirectRoot(HttpServletRequest request) {
                return "forward:/a/web/index.html";
            }
    
            @RequestMapping(value = "/a/**/{path:[^.]*}", method = RequestMethod.GET)
            public String redirectClients(HttpServletRequest request) {
    
                String requestURI = request.getRequestURI();
    
                if (requestURI.startsWith("/a/admin/")) {
                    return "forward:/a/admin/index.html";
                }
    
                if (requestURI.startsWith("/a/swagger/")) {
                    return "forward:/a/swagger/swagger-ui.html#/";
                }
    
                return "forward:/a/web/index.html";
            }
    
        }
    
        @Configuration
        @EnableResourceServer
        public class ResourceServer extends ResourceServerConfigurerAdapter {
    
            @Override
            public void configure(HttpSecurity http) throws Exception {
    
                // @formatter:off
                http
                        .requiresChannel()
                            /* Require HTTPS evereywhere*/
                            .antMatchers("/**")
                                .requiresSecure()
                        .and()
                            .exceptionHandling()
                        .and()
                            /* Permit all requests towards the public api as well as webhook endpoints. */
                            .authorizeRequests()
                                .antMatchers(PUBLIC_ENDPOINTS, WEBHOOK_ENDPOINTS)
                                .permitAll()
                            /* Required for ForwardController */
                            .antMatchers(HttpMethod.GET, "/*")
                                .permitAll()
                            .antMatchers("/r/api/**")
                                .authenticated();
                // @formatter:on
            }
    
            @Override
            public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
                resources
                        .resourceId(resourceIds)
                        .authenticationEntryPoint(customAuthEntryPoint());
            }
    
            @Component
            public class AuthFailureHandler implements AuthenticationEntryPoint {
    
                @Override
                public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
                    System.out.println("Hello");
                    // FIXME We need to return HTTP 401 (s.t. the client knows what's going on) but for some reason it's not working as intended!
                    throw authException;
                }
            }
    
            @Bean
            public AuthenticationEntryPoint customAuthEntryPoint(){
                return new AuthFailureHandler();
            }
    
        }
    
    }
    

    另外,这里是 WebSecurityConfigurerAdapter 虽然我不认为这在这里起作用:

    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Value("${security.signing-key}")
        private String signingKey;
    
        private final UserDetailsService userDetailsService;
    
        private final DataSource dataSource;
    
        @Autowired
        public WebSecurityConfig(@Qualifier("appUserDetailsService") UserDetailsService userDetailsService, DataSource dataSource) {
            this.userDetailsService = userDetailsService;
            this.dataSource = dataSource;
        }
    
        @Bean
        @Override
        protected AuthenticationManager authenticationManager() throws Exception {
            return super.authenticationManager();
        }
    
        @Override
        public void configure(WebSecurity web) throws Exception {
    
        }
    
        @Bean
        public JwtAccessTokenConverter accessTokenConverter() {
            JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
            converter.setSigningKey(signingKey);
            return converter;
        }
    
        /**
         * Using {@link JwtTokenStore} for JWT access tokens.
         * @return
         */
        @Bean
        public TokenStore tokenStore() {
            return new JdbcTokenStore(dataSource);
        }
    
        /**
         * Provide {@link DefaultTokenServices} using the {@link JwtTokenStore}.
         * @return
         */
        @Bean
        public DefaultTokenServices tokenServices() {
            DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
            defaultTokenServices.setTokenStore(tokenStore());
            defaultTokenServices.setSupportRefreshToken(true);
            return defaultTokenServices;
        }
    
        /**
         * We provide the AuthenticationManagerBuilder using our {@link UserDetailsService} and the {@link BCryptPasswordEncoder}.
         * @param auth
         * @throws Exception
         */
        @Autowired
        public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
            auth
                    .userDetailsService(this.userDetailsService)
                    .passwordEncoder(passwordEncoder())
                    .and()
                    .authenticationProvider(daoAuthenticationProvider());
        }
    
        /**
         * Using {@link BCryptPasswordEncoder} for user-password encryption.
         * @return
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        /**
         * Provide {@link DaoAuthenticationProvider} for password encoding and set the {@link UserDetailsService}.
         * @return
         */
        @Bean
        public DaoAuthenticationProvider daoAuthenticationProvider() {
            DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
            daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
            daoAuthenticationProvider.setUserDetailsService(this.userDetailsService);
            return daoAuthenticationProvider;
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.requiresChannel()
                    .requestMatchers(r -> r.getHeader("X-Forwarded-Proto") != null)
                    .requiresSecure();
        }   
    
    }
    
    0 回复  |  直到 5 年前
        1
  •  0
  •   Vijay Nandwana    5 年前

    创建扩展的自定义类 ResponseEntityExceptionHandler @ControllerAdvice OAuth2Exception (基类) InvalidGrantException

    @ExceptionHandler({OAuth2Exception.class})
    public ResponseEntity<Object> handleOAuth2Exception(OAuth2Exception exception, WebRequest request) {
        LOGGER.debug("OAuth failed on request processing", exception);
        return this.handleExceptionInternal(exception, ErrorOutputDto.create(exception.getOAuth2ErrorCode(), exception.getMessage()), new HttpHeaders(), HttpStatus.valueOf(exception.getHttpErrorCode()), request);
    }
    

    使用 HandlerExceptionResolverComposite 将系统中的所有异常解析程序合成为一个异常解析程序。这将覆盖中定义的相应bean WebMvcConfigurationSupport 班级。添加列表 exceptionResolvers 喜欢 DefaultErrorAttributes ExceptionHandlerExceptionResolver ResponseEntityExceptionHandler .

    <bean id="handlerExceptionResolver" class="org.springframework.web.servlet.handler.HandlerExceptionResolverComposite">
    <property name="exceptionResolvers">
        <list>
            <bean class="org.springframework.boot.web.servlet.error.DefaultErrorAttributes"/>
    
            <bean class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver">
                <property name="messageConverters">
                    <list>
                        <ref bean="jackson2HttpMessageConverter" />
                    </list>
                </property>
            </bean>
        </list>
    </property>
    

        2
  •  0
  •   ValerioMC    5 年前

    您是否尝试过定义 @ControllerAdvice 指定您的 InvalidGrantException 作品

    @ControllerAdvice
    @ResponseBody
    public class GlobalExceptionHandler {
    
        @ExceptionHandler(InvalidGrantException.class)
        public ResponseEntity<CustomErrorMessageTO> handleInvalidGrant(
                InvalidGrantException invalidGrantException) {
    
            CustomErrorMessageTO customErrorMessageTO = new CustomErrorMessageTO("Not granted or whatsoever");
    
            return new ResponseEntity<>(customErrorMessageTO, HttpStatus.UNAUTHORIZED);
        }
    }
    
    class CustomErrorMessageTO {
    
        private String message;
    
        public CustomErrorMessageTO(String message) {
            this.message = message;
        }
    
        public String getMessage() {
            return message;
        }
    }