spring boot RestController get HttpServletResponse content - spring-boot

I use spring boot build project, RestController return string data.
I want get response content in Filter.
But cant get, please help me.
controller:
#RestController
#RequestMapping(value = "/service/example")
public class ExampleController {
#RequestMapping(value = "/get/test", method = RequestMethod.POST)
public String message(#RequestBody String data) {
return "test";
}
#RequestMapping(value = "/get/test1", method = RequestMethod.POST)
public void message(HttpServletRequest request, HttpServletResponse response) throws IOException {
PrintWriter writer = response.getWriter();
writer.write("dfsfd");
writer.flush();
}
}
filter:
#WebFilter(filterName="myFilter",urlPatterns="/service/*")
public class MyFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
MyHttpServletResponseWrapper responseWrapper = new MyHttpServletResponseWrapper(response);
filterChain.doFilter(request, responseWrapper);
String responseContent = responseWrapper.getContent();
System.out.println("response="+responseContent);
}
}
MyHttpServletResponseWrapper :
public class MyHttpServletResponseWrapper extends HttpServletResponseWrapper {
private PrintWriter cachedWriter;
private CharArrayWriter bufferedWriter;
/**
* Constructs a response adaptor wrapping the given response.
*
* #param response The response to be wrapped
* #throws IllegalArgumentException if the response is null
*/
public MyHttpServletResponseWrapper(HttpServletResponse response) {
super(response);
bufferedWriter = new CharArrayWriter();
cachedWriter = new PrintWriter(bufferedWriter);
}
#Override
public PrintWriter getWriter() throws IOException {
return cachedWriter;
}
/**
* 获取原始HTML
*
* #return
*/
public String getContent() {
byte[] bytes = bufferedWriter.toString().getBytes();
try {
return new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
return "";
}
}
}
post to /service/example/get/test cant get content.
but post to /service/example/get/test1 can get content.
why?
My project has many rest like /service/example/get/test, I dont want to change each one.
how to get response content in filter, please help, Thanks!!!

I created one simple spring boot project, in this project you can control which url you want to filter:
Rest service class (3 services, we will filter 2 only)
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.filter.GenericFilterBean;
#SpringBootApplication
#RestController
#RequestMapping(value = "/service/example")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#RequestMapping(value = "/get/test", method = RequestMethod.POST)
public String message(#RequestBody String data) {
return "test";
}
#RequestMapping(value = "/get/test1", method = RequestMethod.POST)
public void message(HttpServletRequest request, HttpServletResponse response) throws IOException {
PrintWriter writer = response.getWriter();
writer.write("dfsfd");
writer.flush();
}
#RequestMapping(value = "/api", method = RequestMethod.POST)
public void messages(HttpServletRequest request, HttpServletResponse response) throws IOException {
PrintWriter writer = response.getWriter();
writer.write("dfsfd");
writer.flush();
}
#Bean
public FilterRegistrationBean someFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(myFilter());
registration.addUrlPatterns("/service/example/get/*");
registration.setOrder(1);
return registration;
}
#Bean(name = "someFilter")
public GenericFilterBean myFilter() {
return new MyFilter();
}
}
MyFilter class:
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.web.filter.GenericFilterBean;
public class MyFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
System.out.println("Filter called");
filterChain.doFilter(servletRequest, servletResponse);
}
}
try to call 3 services:
http://localhost:8080/service/example/get/test
http://localhost:8080/service/example/get/test1
http://localhost:8080/service/example/api
and check the printed log.
you can control the url patter using this line
registration.addUrlPatterns("/service/example/get/*");
I hope this sample help you, thanks

Related

How to get out of static context in spring boot

Both methods, "createToken" and "getAuthentication" return errors when being used in class "JWTAuthenticationFilter" and "JWTAuthorizationFilter". The errors are Non-static method 'getAuthentication(java.lang.String)' cannot be referenced from a static context
The problem is that I don't have a single static field in all my code, so I don't understand the error. I also can't just declare the methods as static because I need to use the variable "jwt_secret" and in my previous post someone explained why I shouldn't make it static:
"Spring will not inject (autowire) into static fields.; that wouldn't make any sense even if it could. Spring beans are instances of classes, but static fields are not associated to any one instance. There are some ugly workarounds but better would be to eliminate the use of static fields."
TokenUtils
package com.XX.ZZ.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.stereotype.Component;
import java.util.*;
#Component
public class TokenUtils {
#Autowired
#Value("${custom.data.jwt.secret}")
private final String jwt_secret;
public TokenUtils(String jwtSecret) {
jwt_secret = jwtSecret;
}
public String createToken(String name, String email){
long expirationTime = 86400 * 1000;
Date expirationDate = new Date(System.currentTimeMillis() + expirationTime);
Map<String, Object> extra = new HashMap<>();
extra.put("name", name);
return Jwts.builder()
.setSubject(email)
.setExpiration(expirationDate)
.addClaims(extra)
.signWith(Keys.hmacShaKeyFor(jwt_secret.getBytes()))
.compact();
}
public UsernamePasswordAuthenticationToken getAuthentication(String token){
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(jwt_secret.getBytes())
.build()
.parseClaimsJws(token)
.getBody();
String email = claims.getSubject();
return new UsernamePasswordAuthenticationToken(email, null, Collections.emptyList());
} catch (JwtException e){
return null;
}
}
}
JWTAuthorizationFilter, error in UsernamePasswordAuthenticationToken usernamePAT = TokenUtils.getAuthentication(token)
package com.XX.ZZ.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
#Component
public class JWTAuthorizationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String bearerToken = request.getHeader("Authorization");
if(bearerToken != null && bearerToken.startsWith("Bearer ")){
String token = bearerToken.replace("Bearer ", "");
UsernamePasswordAuthenticationToken usernamePAT = TokenUtils.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(usernamePAT);
}
filterChain.doFilter(request, response);
}
}
JwtAuthenticationFilter, error in String token = TokenUtils.createToken
package com.XX.ZZ.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.io.IOException;
import java.util.Collections;
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
AuthCredentials authCredentials = new AuthCredentials();
try {
authCredentials = new ObjectMapper().readValue(request.getReader(), AuthCredentials.class);
} catch (IOException e){ }
UsernamePasswordAuthenticationToken usernamePAT = new UsernamePasswordAuthenticationToken(
authCredentials.getEmail(),
authCredentials.getPassword(),
Collections.emptyList()
);
return getAuthenticationManager().authenticate(usernamePAT);
}
#Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
UserDetailsImpl userDetails = (UserDetailsImpl) authResult.getPrincipal();
String token = TokenUtils.createToken(userDetails.getName(), userDetails.getUsername());
response.addHeader("Authorization", "Bearer " + token);
response.getWriter().flush();
super.successfulAuthentication(request, response, chain, authResult);
}
}
By calling it as TokenUtils.getAuthentication(token) you are literally calling it as as if it is a static. You need an instance in JWTAuthenticationFilter:
TokenUtils tokenUtilsInstance = new TokenUtils("my secret");
tokenUtilsInstance.getAuthentication(token)
You probably need to drop the constructor TokenUtils(String jwtSecret). That will allow the following in JWTAuthenticationFilter. Much better Spring practice.
#Autowired
TokenUtils tokenUtilsInstance;

set content range in spring

I have many resting api in my spring boot application.
#GetMapping("lc/all")
List<LC> getAll()
{
return lcRepository.findAll();
}
Mainly they are sending list.
Now for some reason, I have to receive the length of the response. Changing for each and every method would be tedious.
How can I set Content-Range for each method automatically.
I have modified the CORS:
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/**")
.allowedOrigins("http://localhost:5000")
.exposedHeaders("Content-Range");
}
};
}
First you need a filter that writes only the requested range part as a response when range header is present.
package com.example.contentrange;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT;
import static javax.servlet.http.HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE;
import java.io.IOException;
import java.util.Arrays;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpRange;
import org.springframework.web.util.ContentCachingResponseWrapper;
#WebFilter("/*")
public class AddResponseHeaderFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
var rangeHeader = httpServletRequest.getHeader("Range");
// if there is no range request in the header than do the "normal" filtering
if (rangeHeader == null) {
chain.doFilter(request, response);
return;
}
HttpRange range = HttpRange.parseRanges(rangeHeader).get(0);
ContentCachingResponseWrapper responseWrapper =
new ContentCachingResponseWrapper((HttpServletResponse) response);
try {
chain.doFilter(request, responseWrapper);
} finally {
byte[] copy = responseWrapper.getContentAsByteArray();
int size = responseWrapper.getContentSize();
int lower = (int) range.getRangeStart(size);
int upper = (int) range.getRangeEnd(size);
if (lower <= size) {
responseWrapper.setStatus(SC_PARTIAL_CONTENT);
byte[] subArray = Arrays.copyOfRange(copy, lower, upper + 1);
String newContent = new String(subArray, UTF_8);
responseWrapper.reset();
responseWrapper.setHeader(
"Content-Range", String.format("bytes %d-%d/%d", lower, upper, size));
responseWrapper.setContentLength(newContent.length());
responseWrapper.getWriter().write(newContent);
responseWrapper.getWriter().flush();
responseWrapper.flushBuffer();
responseWrapper.copyBodyToResponse();
} else {
responseWrapper.setStatus(SC_REQUESTED_RANGE_NOT_SATISFIABLE);
}
}
}
}
Second you need to add org.springframework.boot.web.servlet.ServletComponentScan annotation to your application class.
#ServletComponentScan
#SpringBootApplication
public class ContentRangeApplication {
public static void main(String[] args) {
SpringApplication.run(ContentRangeApplication.class, args);
}
}
For more info on Range: https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests

Spring Security Oauth - Custom format for OAuth2Exceptions

The error format of spring security oauth conforms with the OAuth spec and looks like this.
{
"error":"insufficient_scope",
"error_description":"Insufficient scope for this resource",
"scope":"do.something"
}
Especially on a resource server I find it a bit strange to get a different error format for authentication issues. So I would like to change the way this exception is rendered.
The documentation says
Error handling in an Authorization Server uses standard Spring MVC
features, namely #ExceptionHandler methods
So I tried something like this to customize the format of the error:
#ControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE)
public class MyErrorHandler {
#ExceptionHandler(value = {InsufficientScopeException.class})
ResponseEntity<MyErrorRepresentation> handle(RuntimeException ex, HttpServletRequest request) {
return errorResponse(HttpStatus.FORBIDDEN,
MyErrorRepresentation.builder()
.errorId("insufficient.scope")
.build(),
request);
}
}
But this does not work.
Looking at the code, all the error rendering seems to be done in DefaultWebResponseExceptionTranslator#handleOAuth2Exception. But implementing a custom WebResponseExceptionTranslator would not allow changing the format.
Any hints?
First of all,some knowledge for Spring Security OAuth2.
OAuth2 has two main parts
AuthorizationServer : /oauth/token, get token
ResourceServer : url resource priviledge management
Spring Security add filter to the filter chains of server container, so the exception of Spring Security will not reach #ControllerAdvice
Then, custom OAuth2Exceptions should consider for AuthorizationServer and ResourceServer.
This is configuration
#Configuration
#EnableAuthorizationServer
public class OAuthSecurityConfig extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//for custom
endpoints.exceptionTranslator(new MyWebResponseExceptionTranslator());
}
}
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
// format message
resources.authenticationEntryPoint(new MyAuthenticationEntryPoint());
resources.accessDeniedHandler(new MyAccessDeniedHandler());
}
}
MyWebResponseExceptionTranslator is translate the exception to ourOAuthException and we custom ourOAuthException serializer by jackson, which way is same by default the OAuth2 use.
#JsonSerialize(using = OAuth2ExceptionJackson1Serializer.class)
public class OAuth2Exception extends RuntimeException {
other custom handle class stuff
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
/**
* #author qianggetaba
* #date 2019/6/21
*/
public class MyWebResponseExceptionTranslator implements WebResponseExceptionTranslator {
#Override
public ResponseEntity<OAuth2Exception> translate(Exception exception) throws Exception {
if (exception instanceof OAuth2Exception) {
OAuth2Exception oAuth2Exception = (OAuth2Exception) exception;
return ResponseEntity
.status(oAuth2Exception.getHttpErrorCode())
.body(new CustomOauthException(oAuth2Exception.getMessage()));
}else if(exception instanceof AuthenticationException){
AuthenticationException authenticationException = (AuthenticationException) exception;
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body(new CustomOauthException(authenticationException.getMessage()));
}
return ResponseEntity
.status(HttpStatus.OK)
.body(new CustomOauthException(exception.getMessage()));
}
}
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
/**
* #author qianggetaba
* #date 2019/6/21
*/
#JsonSerialize(using = CustomOauthExceptionSerializer.class)
public class CustomOauthException extends OAuth2Exception {
public CustomOauthException(String msg) {
super(msg);
}
}
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
/**
* #author qianggetaba
* #date 2019/6/21
*/
public class CustomOauthExceptionSerializer extends StdSerializer<CustomOauthException> {
public CustomOauthExceptionSerializer() {
super(CustomOauthException.class);
}
#Override
public void serialize(CustomOauthException value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
jsonGenerator.writeNumberField("code4444", value.getHttpErrorCode());
jsonGenerator.writeBooleanField("status", false);
jsonGenerator.writeObjectField("data", null);
jsonGenerator.writeObjectField("errors", Arrays.asList(value.getOAuth2ErrorCode(),value.getMessage()));
if (value.getAdditionalInformation()!=null) {
for (Map.Entry<String, String> entry : value.getAdditionalInformation().entrySet()) {
String key = entry.getKey();
String add = entry.getValue();
jsonGenerator.writeStringField(key, add);
}
}
jsonGenerator.writeEndObject();
}
}
for custom ResourceServer exception
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* #author qianggetaba
* #date 2019/6/21
*/
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException)
throws ServletException {
Map map = new HashMap();
map.put("errorentry", "401");
map.put("message", authException.getMessage());
map.put("path", request.getServletPath());
map.put("timestamp", String.valueOf(new Date().getTime()));
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
try {
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), map);
} catch (Exception e) {
throw new ServletException();
}
}
}
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* #author qianggetaba
* #date 2019/6/21
*/
public class MyAccessDeniedHandler implements AccessDeniedHandler{
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
Map map = new HashMap();
map.put("errorauth", "400");
map.put("message", accessDeniedException.getMessage());
map.put("path", request.getServletPath());
map.put("timestamp", String.valueOf(new Date().getTime()));
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
try {
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), map);
} catch (Exception e) {
throw new ServletException();
}
}
}
I found a similar question with answers that really helped my solving this - Handle spring security authentication exceptions with #ExceptionHandler
But my question is specifically about spring-security-oauth2 - so I think it is still worth stating the answer specific to spring-security-oauth2. My solution was picked from different answers to the question mentioned above.
My samples work for spring-security-oauth2 2.0.13
So the solution for me to achieve a different custom error structure for oauth2 errors on resource server resources was to register a custom OAuth2AuthenticationEntryPoint and OAuth2AccessDeniedHandler that I register using a ResourceServerConfigurerAdapter. It is worth mentioning that this is only changing the format for ResourceServer endpoints - and not the AuthorizationServer endpoints like the TokenEndpoint.
class MyCustomOauthErrorConversionConfigurerAdapter extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer configurer) throws Exception {
configurer.authenticationEntryPoint(new MyCustomOauthErrorOAuth2AuthenticationEntryPoint());
configurer.accessDeniedHandler(new MyCustomOauthErrorOAuth2AccessDeniedHandler());
}
}
I could not reuse the functionality in OAuth2AuthenticationEntryPoint and OAuth2AccessDeniedHandler because the relevant methods translate the exception and flush it in the same method. So I needed to copy some code:
public class MyCustomOauthErrorOAuth2AccessDeniedHandler extends OAuth2AccessDeniedHandler {
private final MyCustomOauthErrorOAuth2SecurityExceptionHandler oAuth2SecurityExceptionHandler = new MyCustomOauthErrorOAuth2SecurityExceptionHandler();
/**
* Does exactly what OAuth2AccessDeniedHandler does only that the body is transformed to {#link MyCustomOauthError} before rendering the exception
*/
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, org.springframework.security.access.AccessDeniedException authException)
throws IOException, ServletException {
oAuth2SecurityExceptionHandler.handle(request, response, authException, this::enhanceResponse);
}
}
public class ExceptionMessageOAuth2AuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint {
private final MyCustomOauthErrorOAuth2SecurityExceptionHandler oAuth2SecurityExceptionHandler = new MyCustomOauthErrorOAuth2SecurityExceptionHandler();
/**
* Does exactly what OAuth2AuthenticationEntryPoint does only that the body is transformed to {#link MyCustomOauthError} before rendering the exception
*/
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
oAuth2SecurityExceptionHandler.handle(request, response, authException, this::enhanceResponse);
}
}
#RequiredArgsConstructor
public class MyCustomOauthErrorOAuth2SecurityExceptionHandler {
private final WebResponseExceptionTranslator exceptionTranslator = new DefaultWebResponseExceptionTranslator();
private final OAuth2ExceptionRenderer exceptionRenderer = new DefaultOAuth2ExceptionRenderer();
private final HandlerExceptionResolver handlerExceptionResolver = new DefaultHandlerExceptionResolver();
/**
* This is basically what {#link org.springframework.security.oauth2.provider.error.AbstractOAuth2SecurityExceptionHandler#doHandle(HttpServletRequest, HttpServletResponse, Exception)} does.
*/
public void handle(HttpServletRequest request, HttpServletResponse response, RuntimeException authException,
BiFunction<ResponseEntity<OAuth2Exception>, Exception, ResponseEntity<OAuth2Exception>> oauthExceptionEnhancer)
throws IOException, ServletException {
try {
ResponseEntity<OAuth2Exception> defaultErrorResponse = exceptionTranslator.translate(authException);
defaultErrorResponse = oauthExceptionEnhancer.apply(defaultErrorResponse, authException);
//this is the actual translation of the error
final MyCustomOauthError customErrorPayload =
MyCustomOauthError.builder()
.errorId(defaultErrorResponse.getBody().getOAuth2ErrorCode())
.message(defaultErrorResponse.getBody().getMessage())
.details(defaultErrorResponse.getBody().getAdditionalInformation() == null ? emptyMap() : defaultErrorResponse.getBody().getAdditionalInformation())
.build();
final ResponseEntity<MyCustomOauthError> responseEntity = new ResponseEntity<>(customErrorPayload, defaultErrorResponse.getHeaders(), defaultErrorResponse.getStatusCode());
exceptionRenderer.handleHttpEntityResponse(responseEntity, new ServletWebRequest(request, response));
response.flushBuffer();
} catch (ServletException e) {
// Re-use some of the default Spring dispatcher behaviour - the exception came from the filter chain and
// not from an MVC handler so it won't be caught by the dispatcher (even if there is one)
if (handlerExceptionResolver.resolveException(request, response, this, e) == null) {
throw e;
}
} catch (IOException | RuntimeException e) {
throw e;
} catch (Exception e) {
// Wrap other Exceptions. These are not expected to happen
throw new RuntimeException(e);
}
}
}
Spring Boot version: 2.2.5
You really don't have to write that much code. All you need to do create a custom AuthenticationEntryPoint by extending OAuth2AuthenticationEntryPoint, override enhanceResponse method of it and register it via Resource Server configuration.
First part:
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer config) {
config.authenticationEntryPoint(new CustomOauth2AuthenticationEntryPoint());
}
}
Second part:
public class CustomOauth2AuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint {
#Override
protected ResponseEntity<String> enhanceResponse(ResponseEntity<?> response, Exception exception) {
return ResponseEntity.status(response.getStatusCode()).body("My custom response body.");
}
}
Keep in mind that according to spec 401 response must send WWW-Authenticate header. The enhanceResponse that we override sends that header. Take a look at the implementation and send that header if you return 401.
You have to setAuthenticationEntryPoint in TokenEndpointAuthenticationFilter Bean if you config by AuthorizationServer
#Bean
public TokenEndpointAuthenticationFilter tokenEndpointAuthenticationFilter() {
CustomOauth2AuthenticationEntryPoint entryPoint = new CustomOauth2AuthenticationEntryPoint();
TokenEndpointAuthenticationFilter filter = new TokenEndpointAuthenticationFilter(authenticationManager, requestFactory());
filter.setAuthenticationEntryPoint(entryPoint);
return filter;
}

SpringBoot - Filters exception handler

I have a spring-boot application. I have a class that is ControllerAdvice to handle the exceptions thrown by the app.
I created a filter that I want to use it to validate a header for all my requests. When I throw the custom exception from that filter, it does not go through my exception handler, it uses the default Spring error control.
Is there any way to handle the errors from filters in my own way?
If you catch the exception in a Filter, then handle with HandlerExceptionResolver, the #ControllerAdvice will start to work
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
#Component
public class HystrixRequestContextServletFilter extends OncePerRequestFilter {
private static final String API_KEY = "apiKey";
private final HandlerExceptionResolver handlerExceptionResolver;
#Autowired
public HystrixRequestContextServletFilter(HandlerExceptionResolver handlerExceptionResolver) {
this.handlerExceptionResolver = handlerExceptionResolver;
}
#Override
public void doFilterInternal(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain filterChain) {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
String apiKey = httpRequest.getHeader(API_KEY);
if (apiKey == null) {
throw new AuthenticationException("no apikey in request, path [" + httpRequest.getRequestURI() + "]");
}
ApiKeyHystrixRequestVariable.set(apiKey);
filterChain.doFilter(httpRequest, httpResponse);
} catch (Exception e) {
handlerExceptionResolver.resolveException(httpRequest, httpResponse, null, e);
} finally {
context.shutdown();
}
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return request.getRequestURI().startsWith("/internal");
}
}
You can write a controller that extends BasicErrorController and write a method that has a #GetMapping annotation like this:
#RestController
public class FilterExceptionController extends BasicErrorController {
private static final Logger LOGGER = LoggerFactory.getLogger(FilterExceptionController.class);
public FilterExceptionController(){
super(new DefaultErrorAttributes(),new ErrorProperties());
}
#GetMapping
private <T> ResponseResult<T> serviceExceptionHandler(HttpServletRequest request) {
Map<String,Object> body= getErrorAttributes(request,isIncludeStackTrace(request,MediaType.ALL));
String message = String.valueOf(body.get("message"));
body.forEach((k,v)->LOGGER.info("{} ==> {}",k,v));
return RestResultGenerator.genError(500,"Filter Error Occurred, Message: [ "+message+" ]");
}
#Override
public String getErrorPath() {
return "/error";
}
}
There is my test filter:
#WebFilter(filterName = "ipFilter",urlPatterns = "/*")
public class IpFilter implements Filter{
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if(!Arrays.asList(new String[]{"127.0.0.1"}).contains(servletRequest.getRemoteAddr())){
throw new ServiceException(403,"ip forbid");
}
}
#Override
public void destroy() {
}
}
This is result(but only get exception message not code):
enter image description here

RESTEASY -Spring Exception mapper for javax.ws.rs.NotFoundException is not getting Involed

Hi I am using Reasteasy 3 along spring 3.
I have defined multiple Exception mappers for my project, and most of them are getting invoked whenever concerned Exceptions are raised.
But I am facing problem with **“javax.ws.rs.NotFoundException” when this exception is raised its handler is not getting invoked.**
All Exception mappers including mapper for “NotFoundException” exception are defined/configured in same manner and they are getting invoked except mapper for NotFoundException.
Is there any different way to configure Exception mappers for JAX-RS exception ..please help I am stuck here .
code for Exception mappers
package com.xyz.exception;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
#Provider
public class CCDBNotFoundExceptionMapper implements ExceptionMapper<NotFoundException> {
#Override
public Response toResponse(NotFoundException e) {
// Logs and code
}
}
other Exception mapper configured and defined in a Excatly similar way are getting Invoked ... see snap shot for working Exception mapper
package com.xyz.exception;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import org.jboss.resteasy.plugins.providers.jaxb.JAXBUnmarshalException;
#Provider
public class CCDBJAXBUnmarshalExceptionMapper implements ExceptionMapper<JAXBUnmarshalException> {
#Override
public Response toResponse(NotFoundException e) {
// Logs and code
}
}
package com.xyz.exception;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
#Provider
public class CCDBApplicationExceptionMapper implements ExceptionMapper<CCDBApplicationException>{
#Override
public Response toResponse(NotFoundException e) {
// Logs and code
}
}
Spring Configuration for provider annotation
<!-- Auto Reegistry of RESTEasy providers -->
<context:component-scan base-package="com.xyz">
<context:include-filter type="annotation" expression="javax.ws.rs.ext.Provider"/>
</context:component-scan>
I tried everything but Exception mappers for any "javax.ws.rs.*" exceptions are not getting invokes although they are getting registered .
to overcome this problem i have used servlet filter ... i posting my code to help others whoever is facing same issues
Register below filter in your web.xml file.
filter code :
public class CCDBExceptionFilter implements Filter {
Logger logger = Logger.getLogger(CCDBExceptionFilter.class);
private static final String NOTFOUND_MESSAGE = "{\"ErrorResponse\":{\"errorCode\":1,\"errorMessage\":\"Invalid request, There is no service configured to handle provided request\"}}";
public void init(FilterConfig filterConfig) throws ServletException {
logger.debug("CCDBExceptionFilter got initiated" );
}
public void doFilter(ServletRequest request, ServletResponse response,FilterChain filterChain)throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
HttpServletRequest req = (HttpServletRequest) request;
logger.debug("Request recived to process " + req.getPathInfo());
DummyResponse dummyResponse = new DummyResponse((HttpServletResponse)response);
filterChain.doFilter(request, dummyResponse);
logger.debug("Request handled for " + req.getPathInfo());
if(404 == dummyResponse.getErrorcode()){
logger.debug("Found 404 error code" );
res.setStatus(400);
PrintWriter out = response.getWriter();
out.println(NOTFOUND_MESSAGE);
out.close();
}
}
public void destroy() {
}
}
Dummy Response class
public class DummyResponse extends HttpServletResponseWrapper {
Logger logger = Logger.getLogger(DummyResponse.class);
private CharArrayWriter buffer; // This can be used as an Writer
int errorcode;
public int getErrorcode() {
return errorcode;
}
public void setErrorcode(int errorcode) {
this.errorcode = errorcode;
}
public DummyResponse(HttpServletResponse response) {
super(response);
buffer = new CharArrayWriter();
}
public String toString() {
return buffer.toString();
}
public PrintWriter getWriter() {
return new PrintWriter(buffer);
}
public void setStatus(int sc) {
if (404 == sc) {
setErrorcode(404);
super.setStatus(400);
} else {
super.setStatus(sc);
}
}
public void sendError(int sc, String msg) throws IOException {
if (404 == sc) {
setErrorcode(404);
super.setStatus(400);
} else {
super.setStatus(sc);
}
}
public void sendError(int sc) throws IOException {
if (404 == sc) {
setErrorcode(404);
super.setStatus(400);
} else {
super.setStatus(sc);
}
}
}

Resources