Мне было интересно, если бы я мог настроить следующую ошибку авторизации:

{
  "error": "unauthorized",
  "error_description": "Full authentication is required to access this resource"
}

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

{
 "timestamp":1445441285803,
 "status":401,
 "error":"Unauthorized",
 "message":"Bad credentials",
 "path":"/oauth/token"
}

Возможно ли это?

Благодарю.

10
pakkk 31 Авг 2017 в 18:37

4 ответа

Лучший ответ

Я понял :)

https://stackoverflow.com/a/37132751/2520689

Мне нужно создать новый класс, который реализует «AuthenticationEntryPoint» следующим образом:

public class AuthExceptionEntryPoint implements AuthenticationEntryPoint
{
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg2) throws IOException, ServletException
    {
        final Map<String, Object> mapBodyException = new HashMap<>() ;

        mapBodyException.put("error"    , "Error from AuthenticationEntryPoint") ;
        mapBodyException.put("message"  , "Message from AuthenticationEntryPoint") ;
        mapBodyException.put("exception", "My stack trace exception") ;
        mapBodyException.put("path"     , request.getServletPath()) ;
        mapBodyException.put("timestamp", (new Date()).getTime()) ;

        response.setContentType("application/json") ;
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED) ;

        final ObjectMapper mapper = new ObjectMapper() ;
        mapper.writeValue(response.getOutputStream(), mapBodyException) ;
    }
}

И добавьте его в мою реализацию ResourceServerConfigurerAdapter:

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter
{   
    @Override
    public void configure(HttpSecurity http) throws Exception
    {
        http.exceptionHandling().authenticationEntryPoint(new AuthExceptionEntryPoint()) ;

    }
}

Вы можете найти мой проект GitHub, который реализует все, что вам нужно:

https://github.com/pakkk/custom-spring-security

6
pakkk 30 Окт 2017 в 08:05

Я думаю, что вы можете использовать @ControllerAdvice, чтобы перехватить исключение неавторизованным, а затем отформатировать ответ как ожидаемое и вернуть его. Что-то вроде этого:

@ResponseBody
@ExceptionHandler(CustomException.class)
@ResponseStatus(value=HttpStatus.UNAUTHORIZED, reason="Exception message")
public JsonResponse unAuthorised(HttpServletRequest request, Exception ex) {
    return new JsonResponse("ERROR", 401, "Unauthorised Request");
}

Надеюсь, это поможет.

-1
Kenny Tai Huynh 31 Авг 2017 в 17:15

Короткая история: https://github.com/melardev/JavaSpringBootOAuth2Pwitru / а>

После прочтения ответа @pakkk я не был согласен, поэтому я решил попробовать свои собственные идеи, которые тоже не сработали, поэтому я решил взглянуть на сам исходный код Spring Security, что происходит так:Существует фильтр, который вызывается очень рано, OAuth2AuthenticationProcessingFilter.Этот фильтр пытается извлечь JWT из заголовка, если выдается исключение, он вызываетего аутентификацияEntryPoint.commence () (здесь был @pakk)Я попытался добавить фильтр, чтобы проверить, вызывается ли он, когда Jwt недействителен или присутствует, и этого не произошло, поэтому добавление пользовательского фильтра для изменения ответа не будет работать.Затем я посмотрел, где настроен OAuth2AuthenticationProcessingFilter, и обнаружил, что он настроен на ResourceServerSecurityConfigurer :: configure (HttpSecurity http).С учетом сказанного, давайте посмотрим, как мы можем подключиться к процессу.Оказывается, это очень просто, поскольку вы будете расширять класс ResourceServerConfigurerAdapter в своем приложении Resource Server:

@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
// ....
}

Вы идете вперед и переопределяете:

@Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        super.configure(resources);
}

Как видите, да! у вас есть доступ к ResourceServerSecurityConfigurer, и что теперь? ну давайте заменим точку входа по умолчанию на нашу:

@Autowired
    private AuthenticationEntryPoint oauthEntryPoint;
@Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        super.configure(resources);

        resources.authenticationEntryPoint(oauthEntryPoint);
    }

Для полного исходного кода с примером посмотрите на: https://github.com/melardev/JavaSpringBootOAuth2JwtCrudPagination.git

Без этих шагов, по крайней мере, для меня это не сработало бы, ответ, предоставленный @pakkk, не работает для меня, я проверил на отладчике, и по умолчанию используемая точка входа не наша, даже используя:

http.and().exceptionHandling().authenticationEntryPoint(oauthEntryPoint)

Это было первое, что я протестировал, чтобы заставить его работать, нужно изменить точку входа непосредственно из класса ResourceServerSecurityConfigurer.

И это моя точка входа: обратите внимание, я отправляю объект ErrorResponse, который является моим собственным классом, поэтому у меня есть полный контроль над ответом:

@Component
public class OAuthEntryPoint implements AuthenticationEntryPoint {

    @Autowired
    ObjectMapper mapper;

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        ServletServerHttpResponse res = new ServletServerHttpResponse(httpServletResponse);
        res.setStatusCode(HttpStatus.FORBIDDEN);
        res.getServletResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        res.getBody().write(mapper.writeValueAsString(new ErrorResponse("You must authenticated")).getBytes());
    }
}
1
Melardev 24 Июн 2019 в 14:34

Принятый ответ не работает для меня с использованием Oauth2. После некоторого исследования переводчик исключений работает.

По сути, вам нужно создать WebResponseExceptionTranslator и зарегистрировать его в качестве транслятора исключений.

Сначала создайте бин WebResponseExceptionTranslator:

@Slf4j
@Configuration
public class Oauth2ExceptionTranslatorConfiguration {

    @Bean
    public WebResponseExceptionTranslator oauth2ResponseExceptionTranslator() {
        return new DefaultWebResponseExceptionTranslator() {

            @Override
            public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {

                ResponseEntity<OAuth2Exception> responseEntity = super.translate(e);
                OAuth2Exception body = responseEntity.getBody();
                HttpStatus statusCode = responseEntity.getStatusCode();

                body.addAdditionalInformation("timestamp", dateTimeFormat.format(clock.instant()))
                body.addAdditionalInformation("status", body.getHttpErrorCode().toString())
                body.addAdditionalInformation("message", body.getMessage())
                body.addAdditionalInformation("code", body.getOAuth2ErrorCode().toUpperCase())

                HttpHeaders headers = new HttpHeaders();
                headers.setAll(responseEntity.getHeaders().toSingleValueMap());
                // do something with header or response
                return new ResponseEntity<>(body, headers, statusCode);
            }
        };
    }

}

Теперь вам нужно изменить конфигурацию Oauth2, чтобы зарегистрировать компонент WebResponseExceptionTranslator:

@Slf4j
@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private ClientDetailsServiceBuilder builder;

    @Autowired
    private WebResponseExceptionTranslator oauth2ResponseExceptionTranslator;

    @Autowired
    private UserDetailsService userDetailsService;


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) {
        clients.setBuilder(builder);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();

        tokenEnhancerChain.setTokenEnhancers(
                Arrays.asList(tokenEnhancer(), accessTokenConverter()));

        endpoints.tokenStore(tokenStore())
                .tokenEnhancer(tokenEnhancerChain)
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)
                .exceptionTranslator(oauth2ResponseExceptionTranslator);

    }

}

Окончательный результат будет:

{
    "error": "unauthorized",
    "error_description": "Full authentication is required to access this resource",
    "code": "UNAUTHORIZED",
    "message": "Full authentication is required to access this resource",
    "status": "401",
    "timestamp": "2018-06-28T23:55:28.86Z"
}

Как видите, я не удаляю error и error_description из исходного тела OAuth2Exception. Я рекомендую сохранить их, потому что эти два поля соответствуют спецификации OAuth2. См. RFC и Определения API OAuth2 для получения дополнительных сведений.

Вы также можете настроить результат: переопределить error или error_description (просто вызывая addAdditionalInformation), идентифицировать конкретное исключение с помощью instance of для возврата другого результата json и т. Д. Но Есть и ограничения: если вы хотите определить какое-то поле как integer, я не думаю, что это возможно, потому что метод addAdditionalInformation принимает только String в качестве типа.

5
Dherik 24 Апр 2019 в 16:41