I'm working with spring cloud gateway filters and need to get response body to log it.
I understand that it's problematic, as spring gateway is built on spring reactor, but nevertheless I'm looking for any way to do this.
Have global filter, code:
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
#Component
public class BodyRewrite implements RewriteFunction<byte[], byte[]> {
#Override
public Publisher<byte[]> apply(ServerWebExchange exchange, byte[] body) {
System.out.println("-------------------------");
System.out.println(" APPLY METHOD");
System.out.println("-------------------------");
String originalBody = body==null?"":new String(body);
if (!ServerWebExchangeUtils.isAlreadyRouted(exchange)) {
return Mono.just(originalBody.getBytes());
} else {
System.out.println("RESPONSE: " + originalBody);
}
return new Publisher<byte[]>() {
#Override
public void subscribe(Subscriber<? super byte[]> subscriber) {
}
};
}
}
#Component
class ModifyResponseBodyFilter implements GlobalFilter, Ordered {
#Autowired
private ModifyResponseBodyGatewayFilterFactory modifyResponseBodyGatewayFilterFactory;
#Autowired
private BodyRewrite bodyRewrite;
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("---------------------------");
System.out.println(" GLOBAL FILTER");
System.out.println("---------------------------");
GatewayFilter delegate=modifyResponseBodyGatewayFilterFactory.apply(new ModifyResponseBodyGatewayFilterFactory.Config()
.setRewriteFunction(byte[].class, byte[].class, bodyRewrite));
return delegate.filter(exchange, chain);
}
#Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER-1;
}
In the console I only get this output about 30 times in a row and no output with phrase "APPLY METHOD".
---------------------------
GLOBAL FILTER
---------------------------
im a rookie,both in English and Programing.
here is a way but may not elegant:
create a modifyResponseBodyFilter with the ModifyResponseBodyGatewayFilterFactory, and implement the RewriteFunction.
public class BodyRewrite implements RewriteFunction<byte[], byte[]> {
#Override
public Publisher<byte[]> apply(ServerWebExchange exchange, byte[] body) {
String originalBody = body==null?"":new String(body);
if (!ServerWebExchangeUtils.isAlreadyRouted(exchange)) {
return Mono.just(originalBody.getBytes());
} else {
// its the reponse body when already routed
}
}
}
public class ModifyResponseBodyFilter implements GlobalFilter, Ordered {
#Autowired
private ModifyResponseBodyGatewayFilterFactory modifyResponseBodyGatewayFilterFactory;
#Autowired
private BodyRewrite bodyRewrite;
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
GatewayFilter delegate=modifyResponseBodyGatewayFilterFactory.apply(new ModifyResponseBodyGatewayFilterFactory.Config()
.setRewriteFunction(byte[].class, byte[].class, bodyRewrite));
return delegate.filter(exchange, chain);
}
#Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER-1;
}
}
Related
I have a controller in my Spring Boot application. In my controller, I have an endpoint where I need to timeout the call if too much time elapses. I do this by returning a Callable from this method and including the config spring.mvc.async.request-timeout in my application.yml. This seems to work well for our purposes.
I also have an Aspect class in this application that contains a method that is triggered whenever a method in my controller is called. The point of this method is to log details such as the amount of time taken for an endpoint, what the response code was, and etc. This works well when the response of the method is not a Callable (ie. a ResponseEntity) since I can get response information from the return type without issue. However, I cannot get this response information when the method returns a Callable without invoking ((Callable) ProceedingJoinPoint.proceed()).call() from the aspect class. This makes API calls longer, and I believe that's because it invokes call() twice. Is there any way that I can get the response information without having to use call() in the Aspect class?
Here is a simple example of what I have so far in my aspect class:
#Around("...")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = joinPoint.proceed();
if (!(result instanceof Callable<?>)) {
// Do logging using result, which is a ResponseEntity...
} else {
Object callableResult = ((Callable<?>) result).call();
// Do logging using callableResult, which is a ResponseEntity...
}
return result;
}
Thank you.
I encountered the same situation at work a while ago and the following seems to solve it: Log response body after asynchronous Spring MVC controller method
Note: Try logging the async response in a class annotated with #ControllerAdvice and implements ResponseBodyAdvice instead. This should capture the real response instead of callable.
You could have a class annotated with #Aspects for logging request and #ControllerAdvice for logging response together.
e.g.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicLong;
#Aspect
#ControllerAdvice
public class LoggingAdvice implements ResponseBodyAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingAdvice.class);
private static final AtomicLong ID = new AtomicLong();
#Before("within(com.example.demo.controller..*)")
public void endpointBefore(JoinPoint p) {
LOGGER.info(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " START");
Object[] signatureArgs = p.getArgs();
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try {
if (signatureArgs != null && signatureArgs.length > 0) {
LOGGER.info("\nRequest object: \n" + mapper.writeValueAsString(signatureArgs[0]));
} else {
LOGGER.info("request object is empty");
}
} catch (JsonProcessingException e) {
}
}
#Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
long id = ID.incrementAndGet();
ServletServerHttpResponse responseToUse = (ServletServerHttpResponse) response;
HttpMessageConverter httpMessageConverter;
LoggingHttpOutboundMessageWrapper httpOutputMessage = new LoggingHttpOutboundMessageWrapper();
try {
httpMessageConverter = (HttpMessageConverter) selectedConverterType.newInstance();
httpMessageConverter.write(body, selectedContentType, httpOutputMessage);
LOGGER.info("response {}, {}, {}, {}, {}", id, responseToUse.getServletResponse().getStatus(), responseToUse.getServletResponse().getContentType(),
responseToUse.getHeaders(), httpOutputMessage.getResponseBodyInString());
} catch (InstantiationException | IllegalAccessException | IOException e) {
e.printStackTrace();
}
return body;
}
private static final class LoggingHttpOutboundMessageWrapper implements HttpOutputMessage {
private HttpHeaders httpHeaders = new HttpHeaders();
private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
#Override
public OutputStream getBody() throws IOException {
return byteArrayOutputStream;
}
#Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
public String getResponseBodyInString() {
return new String(byteArrayOutputStream.toByteArray());
}
}
}
I use Spring MVC (4.0.1) as a backend for rest services and angularjs as frontend.
every request to my server backend has a http-header with a session id
I can read this header in my server backend with the following code:
#Autowired
protected HttpServletRequest request;
String xHeader=request.getHeader("X-Auth-Token"); //returns the sessionID from the header
Now I call this method getPermission(xHeader) it return only true or false. If the user exists in my DB it return true else false!
I want now create a filter with this behavior, that checks every request if the user have the permission to access my controllers! But if the method returns false it should send back a 401 error and not reach my controller!
How can I do this and create my own filter? I use only Java Config and no XML.
I think I must add the filter here:
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Filter[] getServletFilters() {
MyOwnFilter=new MyOwnFilter();
return new Filter[] {MyOwnFilter};
}
}
Alternative to Filters, you can use HandlerInterceptor.
public class SessionManager implements HandlerInterceptor{
// This method is called before the controller
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
String xHeader = request.getHeader("X-Auth-Token");
boolean permission = getPermission(xHeader);
if(permission) {
return true;
}
else {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return false;
// Above code will send a 401 with no response body.
// If you need a 401 view, do a redirect instead of
// returning false.
// response.sendRedirect("/401"); // assuming you have a handler mapping for 401
}
return false;
}
#Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
#Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}
And then add this interceptor to your webmvc config.
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean
SessionManager getSessionManager() {
return new SessionManager();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getSessionManager())
.addPathPatterns("/**")
.excludePathPatterns("/resources/**", "/login");
// assuming you put your serve your static files with /resources/ mapping
// and the pre login page is served with /login mapping
}
}
Below is the filter to perform the logic you have mentioned
#WebFilter("/*")
public class AuthTokenFilter implements Filter {
#Override
public void destroy() {
// ...
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
//
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String xHeader = ((HttpServletRequest)request).getHeader("X-Auth-Token");
if(getPermission(xHeader)) {
chain.doFilter(request, response);
} else {
request.getRequestDispatcher("401.html").forward(request, response);
}
}
}
And you got it right, the spring config should be following.
public class MyWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Filter[] getServletFilters() {
return new Filter[]{new AuthTokenFilter()};
}
}
Spring can use filters, but they recommend that you use their version of filters, known as an interceptor
http://viralpatel.net/blogs/spring-mvc-interceptor-example/
There is a quick run through of how they work. They are nearly identical to filters, but designed to work inside the Spring MVC lifecycle.
I assume that you are trying to implement some kind of OAuth security which is based on jwt token.
Nowdays there are several ways to do so but here is my favourite one:
Here is how the filter looks like:
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.filter.GenericFilterBean;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;
public class JwtFilter extends GenericFilterBean {
#Override
public void doFilter(final ServletRequest req,
final ServletResponse res,
final FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new ServletException("Missing or invalid Authorization header.");
}
final String token = authHeader.substring(7); // The part after "Bearer "
try {
final Claims claims = Jwts.parser().setSigningKey("secretkey")
.parseClaimsJws(token).getBody();
request.setAttribute("claims", claims);
}
catch (final SignatureException e) {
throw new ServletException("Invalid token.");
}
chain.doFilter(req, res);
}
}
Pretty simple there is the user controller also where you can find the login method:
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
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 io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
#RestController
#RequestMapping("/user")
public class UserController {
private final Map<String, List<String>> userDb = new HashMap<>();
public UserController() {
userDb.put("tom", Arrays.asList("user"));
userDb.put("sally", Arrays.asList("user", "admin"));
}
#RequestMapping(value = "login", method = RequestMethod.POST)
public LoginResponse login(#RequestBody final UserLogin login)
throws ServletException {
if (login.name == null || !userDb.containsKey(login.name)) {
throw new ServletException("Invalid login");
}
return new LoginResponse(Jwts.builder().setSubject(login.name)
.claim("roles", userDb.get(login.name)).setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, "secretkey").compact());
}
#SuppressWarnings("unused")
private static class UserLogin {
public String name;
public String password;
}
#SuppressWarnings("unused")
private static class LoginResponse {
public String token;
public LoginResponse(final String token) {
this.token = token;
}
}
}
Of course we have Main where you can see the filter bean:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#EnableAutoConfiguration
#ComponentScan
#Configuration
public class WebApplication {
#Bean
public FilterRegistrationBean jwtFilter() {
final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new JwtFilter());
registrationBean.addUrlPatterns("/api/*");
return registrationBean;
}
public static void main(final String[] args) throws Exception {
SpringApplication.run(WebApplication.class, args);
}
}
Last but not least there is an example controller:
import io.jsonwebtoken.Claims;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/api")
public class ApiController {
#SuppressWarnings("unchecked")
#RequestMapping(value = "role/{role}", method = RequestMethod.GET)
public Boolean login(#PathVariable final String role,
final HttpServletRequest request) throws ServletException {
final Claims claims = (Claims) request.getAttribute("claims");
return ((List<String>) claims.get("roles")).contains(role);
}
}
Here is a link to GitHub all thanks goes to nielsutrecht for the great work I have used this project as base and it works perfectly.
You can also implement it using an aspect with a pointcut that targets a certain annotation. I have written a library that enables you to use annotations that perform authorization checks based on a JWT token.
You can find the project with all the documentation on: https://github.com/nille85/jwt-aspect. I have used this approach multiple times in order to secure a REST Backend that is consumed by a single page application.
I have also documented on my blog how you can use it in a Spring MVC Application: http://www.nille.be/security/creating-authorization-server-using-jwts/
The following is an extract from the example project on https://github.com/nille85/auth-server
The example underneath contains a protected method getClient. The annotation #Authorize that the aspect uses checks if the value from the "aud jwt claim" matches the clientId parameter that is annotated with #ClaimValue. If it matches, the method can be entered. Otherwise an exception is thrown.
#RestController
#RequestMapping(path = "/clients")
public class ClientController {
private final ClientService clientService;
#Autowired
public ClientController(final ClientService clientService) {
this.clientService = clientService;
}
#Authorize("hasClaim('aud','#clientid')")
#RequestMapping(value = "/{clientid}", method = RequestMethod.GET, produces = "application/json")
#ResponseStatus(value = HttpStatus.OK)
public #ResponseBody Client getClient(#PathVariable(value = "clientid") #ClaimValue(value = "clientid") final String clientId) {
return clientService.getClient(clientId);
}
#RequestMapping(value = "", method = RequestMethod.GET, produces = "application/json")
#ResponseStatus(value = HttpStatus.OK)
public #ResponseBody List<Client> getClients() {
return clientService.getClients();
}
#RequestMapping(path = "", method = RequestMethod.POST, produces = "application/json")
#ResponseStatus(value = HttpStatus.OK)
public #ResponseBody Client registerClient(#RequestBody RegisterClientCommand command) {
return clientService.register(command);
}
}
The Aspect itself can be configured like:
#Bean
public JWTAspect jwtAspect() {
JWTAspect aspect = new JWTAspect(payloadService());
return aspect;
}
The PayloadService that is needed can for example be implemented like:
public class PayloadRequestService implements PayloadService {
private final JWTVerifier verifier;
public PayloadRequestService(final JWTVerifier verifier){
this.verifier = verifier;
}
#Override
public Payload verify() {
ServletRequestAttributes t = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = t.getRequest();
final String jwtValue = request.getHeader("X-AUTH");
JWT jwt = new JWT(jwtValue);
Payload payload =verifier.verify(jwt);
return payload;
}
}
You can create and configure your own filter by doing following steps.
1) Create your class by implementing the filter interface and override its methods.
public class MyFilter implements javax.servlet.Filter{
public void destroy(){}
public void doFilter(Request, Response, FilterChain){//do what you want to filter
}
........
}
2) Now configure your filter in web.xml
<filter>
<filter-name>myFilter</filter-name>
<filter-class>MyFilter</filter-class>
</filter>
3) Now provide url mapping of the filter.
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
4) Now restart your server and check all the web request will first come to MyFilter and then proceed to the respective controller.
Hopefully it will be the required answer.
Your approach looks correct.
Once I have used something similar to following (Removed most of the lines and kept it simple).
public class MvcDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ERROR);
FilterRegistration.Dynamic monitoringFilter = servletContext.addFilter("monitoringFilter", MonitoringFilter.class);
monitoringFilter.addMappingForUrlPatterns(dispatcherTypes, false, "/api/admin/*");
}
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { WebMvcConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
Also you need a custom filter looks like below.
public class CustomXHeaderFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String xHeader = request.getHeader("X-Auth-Token");
if(YOUR xHeader validation fails){
//Redirect to a view
//OR something similar
return;
}else{
//If the xHeader is OK, go through the chain as a proper request
chain.doFilter(request, response);
}
}
#Override
public void destroy() {
}
#Override
public void init(FilterConfig arg0) throws ServletException {
}
}
Hope this helps.
Additionally you can use FilterRegistrationBean if you Spring Boot. It does the same thing (I think so) which FilterRegistration.Dynamic does.
I'm adding some auth logic into cloud api gateway. I've added GatewayFilter:
import java.util.List;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpStatus;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PatternMatchUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
public class AuthorizationFilter implements GatewayFilter {
#Override
public Mono<Void> filter(
ServerWebExchange exchange, GatewayFilterChain chain) {
List<String> authorization = exchange.getRequest().getHeaders().get("Authorization");
if (CollectionUtils.isEmpty(authorization) &&
!PatternMatchUtils.simpleMatch(URL_WITHOUT_AUTH, exchange.getRequest().getURI().toString())) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//Add some custom data in body of the response
return exchange.getResponse().setComplete();
}
String token = authorization.get(0).split(" ")[1];
// token validation
return chain.filter(exchange);
}
}
but I can't add some data into the body of response. Can you please help me to find out how it works and how I can customize that?
P.S.
I'm trying to add some data in response using flux but it doesn't work:
DataBuffer b = exchange.getResponse().bufferFactory().allocateBuffer(256);
b.write("12345".getBytes());
return exchange.getResponse().writeWith(s -> Flux.just(b));
What I'm doing wrong?
After some help from spring guys, I was able to make it work. So instead of writing directly to response I had to throw my custom exception and handle it properly:
#Bean
public ErrorWebExceptionHandler myExceptionHandler() {
return new MyWebExceptionHandler();
}
public class MyWebExceptionHandler implements ErrorWebExceptionHandler {
#Override
public Mono<Void> handle(
ServerWebExchange exchange, Throwable ex) {
byte[] bytes = "Some text".getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
return exchange.getResponse().writeWith(Flux.just(buffer));
}
}
Here's a working solution
import java.util.List;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class AuthorizationFilter implements GatewayFilter {
#Override
public Mono<Void> filter(
ServerWebExchange exchange, GatewayFilterChain chain) {
if (isAuthorizationTokenValid(exchange.getRequest().getHeaders().get("Authorization")))
return chain.filter(exchange);
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//Add some custom data in body of the response,
//Returning "Unauthorized" in the body here
return exchange.getResponse().writeWith(Flux.just(new DefaultDataBufferFactory().wrap("Unauthorized".getBytes())));
}
private boolean isAuthorizationTokenValid(List<String> authorizationTokens){
//Your logic here
return true;
}
}
You should use ServerHttpResponseDecorator to modify the response.
Your code should be like:
import java.util.List;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpStatus;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PatternMatchUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
public class AuthorizationFilter implements GatewayFilter {
#Override
public Mono<Void> filter(
ServerWebExchange exchange, GatewayFilterChain chain) {
List<String> authorization = exchange.getRequest().getHeaders().get("Authorization");
if (CollectionUtils.isEmpty(authorization) &&
!PatternMatchUtils.simpleMatch(URL_WITHOUT_AUTH, exchange.getRequest().getURI().toString())) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
#Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.map(dataBuffer -> {
// probably should reuse buffers
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
byte[] uppedContent = new String(content, Charset.forName("UTF-8")).toUpperCase().getBytes();
return bufferFactory.wrap(uppedContent);
}));
}
return super.writeWith(body); // if body is not a flux. never got there.
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build()); // replace response with decorator
}
String token = authorization.get(0).split(" ")[1];
// token validation
return chain.filter(exchange);
}
}
You can find a complete example here.
I am developing a filter with Spring security which extends of
OncePerRequestFilter class and It has to update parameters in the REST service .Parameters are entered by the header with the annotation #RequestHeader.
I have tried to update parameters from the filter with the follows class:
public class HeaderMapRequestWrapper extends HttpServletRequestWrapper {
/**
* construct a wrapper for this request
*
* #param request
*/
public HeaderMapRequestWrapper(HttpServletRequest request) {
super(request);
}
private Map<String, String> headerMap = new HashMap<String, String>();
/**
* add a header with given name and value
*
* #param name
* #param value
*/
public void addHeader(String name, String value) {
headerMap.put(name, value);
}
public void removeteHeader(String name){
headerMap.remove(name);
}
#Override
public String getHeader(String name) {
String headerValue = super.getHeader(name);
if (headerMap.containsKey(name)) {
headerValue = headerMap.get(name);
}
return headerValue;
}
/**
* get the Header names
*/
#Override
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
for (String name : headerMap.keySet()) {
names.add(name);
}
return Collections.enumeration(names);
}
#Override
public Enumeration<String> getHeaders(String name) {
List<String> values = Collections.list(super.getHeaders(name));
if (headerMap.containsKey(name)) {
values.add(headerMap.get(name));
}
return Collections.enumeration(values);
}
}
And with the method .addHeader("parameter", "New value"), but when I read the parameter in the method it has not changed, but if read it from .getHeader("parameter") method from HttpServletRequest class, which I have inyected in the same method class. The changes are done, but in the method parameters not appear.
The method is the follows:
#Autowired
HttpServletRequest a;
//Annotations #GetMapping......
public void method (#RequestHeader(value="Parameter") String parameter){
System.out.print(parameter); //Parameter did not change
system.out.print(a.getHeader("parameter")); //Parameter changed.
}
Does Someone know how to change the operation parameters from a filter?, or from other way....
I'm not sure what you're doing wrong but when I try it with my setup it seems to work. Please take a look at the following code.
/**
* username: test, password: test
* Added Header: param
* REST URL: http://localhost:8080/hello
*/
package com.test;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.filter.OncePerRequestFilter;
#SpringBootApplication
public class TestRequestWrapperApplication {
public static void main(String[] args) {
SpringApplication.run(TestRequestWrapperApplication.class, args);
}
}
#Component
class RequestWrapperFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HeaderMapRequestWrapper wrappedRequest = new HeaderMapRequestWrapper((HttpServletRequest)request);
wrappedRequest.addHeader("param", "Hello World!");
filterChain.doFilter(wrappedRequest, response);
}
}
#RestController
class TestRest {
#GetMapping("hello")
public String hello(#RequestHeader("param") String param) {
return "param: " + param;
}
}
#Configuration
class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("test").authorities("test").password("test");
}
}
class HeaderMapRequestWrapper extends HttpServletRequestWrapper {
public HeaderMapRequestWrapper(HttpServletRequest request) {
super(request);
}
private Map<String, String> headerMap = new HashMap<String, String>();
public void addHeader(String name, String value) {
headerMap.put(name, value);
}
public void removeteHeader(String name) {
headerMap.remove(name);
}
#Override
public String getHeader(String name) {
String headerValue = super.getHeader(name);
if (headerMap.containsKey(name)) {
headerValue = headerMap.get(name);
}
return headerValue;
}
#Override
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
for (String name : headerMap.keySet()) {
names.add(name);
}
return Collections.enumeration(names);
}
#Override
public Enumeration<String> getHeaders(String name) {
List<String> values = Collections.list(super.getHeaders(name));
if (headerMap.containsKey(name)) {
values.add(headerMap.get(name));
}
return Collections.enumeration(values);
}
}
I use Spring MVC (4.0.1) as a backend for rest services and angularjs as frontend.
every request to my server backend has a http-header with a session id
I can read this header in my server backend with the following code:
#Autowired
protected HttpServletRequest request;
String xHeader=request.getHeader("X-Auth-Token"); //returns the sessionID from the header
Now I call this method getPermission(xHeader) it return only true or false. If the user exists in my DB it return true else false!
I want now create a filter with this behavior, that checks every request if the user have the permission to access my controllers! But if the method returns false it should send back a 401 error and not reach my controller!
How can I do this and create my own filter? I use only Java Config and no XML.
I think I must add the filter here:
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Filter[] getServletFilters() {
MyOwnFilter=new MyOwnFilter();
return new Filter[] {MyOwnFilter};
}
}
Alternative to Filters, you can use HandlerInterceptor.
public class SessionManager implements HandlerInterceptor{
// This method is called before the controller
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
String xHeader = request.getHeader("X-Auth-Token");
boolean permission = getPermission(xHeader);
if(permission) {
return true;
}
else {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return false;
// Above code will send a 401 with no response body.
// If you need a 401 view, do a redirect instead of
// returning false.
// response.sendRedirect("/401"); // assuming you have a handler mapping for 401
}
return false;
}
#Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
#Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}
And then add this interceptor to your webmvc config.
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean
SessionManager getSessionManager() {
return new SessionManager();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getSessionManager())
.addPathPatterns("/**")
.excludePathPatterns("/resources/**", "/login");
// assuming you put your serve your static files with /resources/ mapping
// and the pre login page is served with /login mapping
}
}
Below is the filter to perform the logic you have mentioned
#WebFilter("/*")
public class AuthTokenFilter implements Filter {
#Override
public void destroy() {
// ...
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
//
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String xHeader = ((HttpServletRequest)request).getHeader("X-Auth-Token");
if(getPermission(xHeader)) {
chain.doFilter(request, response);
} else {
request.getRequestDispatcher("401.html").forward(request, response);
}
}
}
And you got it right, the spring config should be following.
public class MyWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Filter[] getServletFilters() {
return new Filter[]{new AuthTokenFilter()};
}
}
Spring can use filters, but they recommend that you use their version of filters, known as an interceptor
http://viralpatel.net/blogs/spring-mvc-interceptor-example/
There is a quick run through of how they work. They are nearly identical to filters, but designed to work inside the Spring MVC lifecycle.
I assume that you are trying to implement some kind of OAuth security which is based on jwt token.
Nowdays there are several ways to do so but here is my favourite one:
Here is how the filter looks like:
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.filter.GenericFilterBean;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;
public class JwtFilter extends GenericFilterBean {
#Override
public void doFilter(final ServletRequest req,
final ServletResponse res,
final FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new ServletException("Missing or invalid Authorization header.");
}
final String token = authHeader.substring(7); // The part after "Bearer "
try {
final Claims claims = Jwts.parser().setSigningKey("secretkey")
.parseClaimsJws(token).getBody();
request.setAttribute("claims", claims);
}
catch (final SignatureException e) {
throw new ServletException("Invalid token.");
}
chain.doFilter(req, res);
}
}
Pretty simple there is the user controller also where you can find the login method:
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
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 io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
#RestController
#RequestMapping("/user")
public class UserController {
private final Map<String, List<String>> userDb = new HashMap<>();
public UserController() {
userDb.put("tom", Arrays.asList("user"));
userDb.put("sally", Arrays.asList("user", "admin"));
}
#RequestMapping(value = "login", method = RequestMethod.POST)
public LoginResponse login(#RequestBody final UserLogin login)
throws ServletException {
if (login.name == null || !userDb.containsKey(login.name)) {
throw new ServletException("Invalid login");
}
return new LoginResponse(Jwts.builder().setSubject(login.name)
.claim("roles", userDb.get(login.name)).setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, "secretkey").compact());
}
#SuppressWarnings("unused")
private static class UserLogin {
public String name;
public String password;
}
#SuppressWarnings("unused")
private static class LoginResponse {
public String token;
public LoginResponse(final String token) {
this.token = token;
}
}
}
Of course we have Main where you can see the filter bean:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#EnableAutoConfiguration
#ComponentScan
#Configuration
public class WebApplication {
#Bean
public FilterRegistrationBean jwtFilter() {
final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new JwtFilter());
registrationBean.addUrlPatterns("/api/*");
return registrationBean;
}
public static void main(final String[] args) throws Exception {
SpringApplication.run(WebApplication.class, args);
}
}
Last but not least there is an example controller:
import io.jsonwebtoken.Claims;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/api")
public class ApiController {
#SuppressWarnings("unchecked")
#RequestMapping(value = "role/{role}", method = RequestMethod.GET)
public Boolean login(#PathVariable final String role,
final HttpServletRequest request) throws ServletException {
final Claims claims = (Claims) request.getAttribute("claims");
return ((List<String>) claims.get("roles")).contains(role);
}
}
Here is a link to GitHub all thanks goes to nielsutrecht for the great work I have used this project as base and it works perfectly.
You can also implement it using an aspect with a pointcut that targets a certain annotation. I have written a library that enables you to use annotations that perform authorization checks based on a JWT token.
You can find the project with all the documentation on: https://github.com/nille85/jwt-aspect. I have used this approach multiple times in order to secure a REST Backend that is consumed by a single page application.
I have also documented on my blog how you can use it in a Spring MVC Application: http://www.nille.be/security/creating-authorization-server-using-jwts/
The following is an extract from the example project on https://github.com/nille85/auth-server
The example underneath contains a protected method getClient. The annotation #Authorize that the aspect uses checks if the value from the "aud jwt claim" matches the clientId parameter that is annotated with #ClaimValue. If it matches, the method can be entered. Otherwise an exception is thrown.
#RestController
#RequestMapping(path = "/clients")
public class ClientController {
private final ClientService clientService;
#Autowired
public ClientController(final ClientService clientService) {
this.clientService = clientService;
}
#Authorize("hasClaim('aud','#clientid')")
#RequestMapping(value = "/{clientid}", method = RequestMethod.GET, produces = "application/json")
#ResponseStatus(value = HttpStatus.OK)
public #ResponseBody Client getClient(#PathVariable(value = "clientid") #ClaimValue(value = "clientid") final String clientId) {
return clientService.getClient(clientId);
}
#RequestMapping(value = "", method = RequestMethod.GET, produces = "application/json")
#ResponseStatus(value = HttpStatus.OK)
public #ResponseBody List<Client> getClients() {
return clientService.getClients();
}
#RequestMapping(path = "", method = RequestMethod.POST, produces = "application/json")
#ResponseStatus(value = HttpStatus.OK)
public #ResponseBody Client registerClient(#RequestBody RegisterClientCommand command) {
return clientService.register(command);
}
}
The Aspect itself can be configured like:
#Bean
public JWTAspect jwtAspect() {
JWTAspect aspect = new JWTAspect(payloadService());
return aspect;
}
The PayloadService that is needed can for example be implemented like:
public class PayloadRequestService implements PayloadService {
private final JWTVerifier verifier;
public PayloadRequestService(final JWTVerifier verifier){
this.verifier = verifier;
}
#Override
public Payload verify() {
ServletRequestAttributes t = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = t.getRequest();
final String jwtValue = request.getHeader("X-AUTH");
JWT jwt = new JWT(jwtValue);
Payload payload =verifier.verify(jwt);
return payload;
}
}
You can create and configure your own filter by doing following steps.
1) Create your class by implementing the filter interface and override its methods.
public class MyFilter implements javax.servlet.Filter{
public void destroy(){}
public void doFilter(Request, Response, FilterChain){//do what you want to filter
}
........
}
2) Now configure your filter in web.xml
<filter>
<filter-name>myFilter</filter-name>
<filter-class>MyFilter</filter-class>
</filter>
3) Now provide url mapping of the filter.
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
4) Now restart your server and check all the web request will first come to MyFilter and then proceed to the respective controller.
Hopefully it will be the required answer.
Your approach looks correct.
Once I have used something similar to following (Removed most of the lines and kept it simple).
public class MvcDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ERROR);
FilterRegistration.Dynamic monitoringFilter = servletContext.addFilter("monitoringFilter", MonitoringFilter.class);
monitoringFilter.addMappingForUrlPatterns(dispatcherTypes, false, "/api/admin/*");
}
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { WebMvcConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
Also you need a custom filter looks like below.
public class CustomXHeaderFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String xHeader = request.getHeader("X-Auth-Token");
if(YOUR xHeader validation fails){
//Redirect to a view
//OR something similar
return;
}else{
//If the xHeader is OK, go through the chain as a proper request
chain.doFilter(request, response);
}
}
#Override
public void destroy() {
}
#Override
public void init(FilterConfig arg0) throws ServletException {
}
}
Hope this helps.
Additionally you can use FilterRegistrationBean if you Spring Boot. It does the same thing (I think so) which FilterRegistration.Dynamic does.