Как поймать и обработать InvalidGrantException (пользователь отключен)? - PullRequest
0 голосов
/ 15 сентября 2018

Я заметил, что мой ResponseEntityExceptionHandler не работает с исключениями, сгенерированными Spring Security в моем приложении Spring Boot.

Однако мне нужен способ перехвата InvalidGrantException, который, кажется, выдается, когда пользовательучетная запись по-прежнему отключена.

Вариант использования прост: если пользователь в настоящее время отключен, я хочу выдать соответствующую ошибку своему клиенту, чтобы он мог отображать сообщение соответствующим образом.

Сейчас Spring Security отвечает

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

Я видел этот вопрос , но по какой-то причине мой 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();
    }   

}

Ответы [ 2 ]

0 голосов
/ 18 июля 2019

Вы пробовали, если определение @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;
    }
}
0 голосов
/ 16 июля 2019

Создайте пользовательский класс, который расширяет 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 , чтобы объединить все средства разрешения исключений в системе в один преобразователь исключений. Это переопределяет соответствующий компонент, определенный в WebMvcConfigurationSupport класс. Добавьте список exceptionResolvers, например DefaultErrorAttributes и ExceptionHandlerExceptionResolver (это позволяет обрабатывать исключения на основе AOP, включая классы с аннотацией @ControllerAdvice, например, созданный нами пользовательский класс, расширяющий 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>

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...