#Component
#Slf4j
public class JwtAuthenticationFilter implements GatewayFilter {
#Autowired
private JwtUtil jwtUtil;
#Override
public Mono<Void> filter(final ServerWebExchange exchange,
final GatewayFilterChain chain) {
log.info("Start --> filter()");
ServerHttpRequest request = (ServerHttpRequest) exchange.getRequest();
if (!request.getHeaders().containsKey("Authorization")) {
ServerHttpResponse response = exchange.getResponse();
log.debug("response status {}", response.getStatusCode());
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
final String token = request.getHeaders().getOrEmpty("Authorization").get(0);request = {ReactorServerHttpRequest#12622}
try {
jwtUtil.validateToken(token);
} catch (JwtTokenMalformedException | JwtTokenMissingException e) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.BAD_REQUEST);
log.debug("response status {}", response.getStatusCode());
return response.setComplete();
}
Claims claims = jwtUtil.getClaims(token);
exchange.getRequest().mutate().header("id", String.valueOf(claims.get("id"))).build();
log.info("end filter()");
return chain.filter(exchange);
}
}
can someone please explain me how to write junits for this. I am very much new to this Junits and i tried in google also, but could not find the how to check if conditions using Junit/Mockito
Related
I have two spring apps: Users-App(login/registration, etc.) and main app. I need to access main app via JWT. How can I send token from users-app to main-app by using "Simple" Controller?
My Controller(users-app):
#Controller
#RequiredArgsConstructor
public class UserController {
private final UserService userService;
#Autowired
private UserRegistrationValidator userValidator;
#Autowired
private LoginValidator loginValidator;
#GetMapping("/")
public String startPage() {
return "redirect:/index";
}
#GetMapping("/index")
public String homePage() {
return "home";
}
#GetMapping("/users")
public String getUsers() {
return "redirect:http://localhost:8080/";
}
#GetMapping("/login")
public String loginPage(HttpServletRequest request, Model model) {
User user = new User();
model.addAttribute("user", user);
if (isCookiesExists(request)) return "redirect:/users";
return "login";
}
#PostMapping("/signin")
public String loginUser(#ModelAttribute("user") User user, BindingResult bindingResult) {
loginValidator.validate(user, bindingResult);
if (bindingResult.hasErrors()) {
return "login";
}
return "redirect:/users";
}
#GetMapping("/register")
public String registerUser(HttpServletRequest request, Model model) {
User user = new User();
model.addAttribute("my_user", user);
if (isCookiesExists(request)) return "redirect:/users";
return "register";
}
#PostMapping("/register")
public String saveUser(#ModelAttribute("my_user") User user, BindingResult bindingResult) {
userValidator.validate(user, bindingResult);
if (bindingResult.hasErrors()) {
return "register";
}
userService.saveUser(user);
return "redirect:/login";
}
private boolean isCookiesExists(HttpServletRequest request) {
if (request.getCookies() != null) {
List<String> auth = Arrays.stream(request.getCookies())
.filter(cookie -> cookie.getName().equals("auth"))
.map(Cookie::getValue)
.filter(Objects::nonNull).collect(Collectors.toList());
return auth.size() != 0;
}
return false;
}
}
Creating tokens:
#Service
#RequiredArgsConstructor
#Transactional
#Slf4j
public class GetTokenServiceImpl implements GetTokenService {
private final PasswordEncoder passwordEncoder;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private UserService userService;
#Override
public String createToken(HttpServletRequest request, User user) {
Algorithm algorithm = Algorithm.HMAC512("my_secret_key_10210_oqpowqkq192199qkkwoxa");
return JWT.create()
.withSubject(user.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + 30 * 60 * 1000))
.withIssuer(request.getRequestURL().toString())
.withClaim(
"roles",
user.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList())
)
.sign(algorithm);
}
#Override
public String createRefreshToken(HttpServletRequest request, User user) {
Algorithm algorithm = Algorithm.HMAC512("my_secret_key_10210_oqpowqkq192199qkkwoxa");
return JWT.create()
.withSubject(user.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + 60 * 60 * 10000))
.withIssuer(request.getRequestURL().toString())
.sign(algorithm);
}
#Override
public Map<String, String> getTokens(HttpServletRequest request, String username, String password) {
com.user.app.server.model.User myUser = userService.getUser(username);
if (myUser == null || password == null || !passwordEncoder.matches(password, myUser.getPassword()) ) {
log.error("Error logging in: {} ", "Bad Credentials error");
}
User user = (User) userDetailsService.loadUserByUsername(username);
String accessToken = createToken(request, user);
String refreshToken = createRefreshToken(request, user);
Map<String, String> tokens = new HashMap<>();
tokens.put("access_token", accessToken);
tokens.put("refresh_token", refreshToken);
return tokens;
}
}
And my authorization filter:
#Slf4j
public class CustomAuthorizationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String header = "";
if (request.getServletPath().equals("/users")){
header = request.getHeader(AUTHORIZATION);
}
if (header != null && header.startsWith("Bearer ")) {
try {
String token = header.substring("Bearer ".length());
Algorithm algorithm = Algorithm.HMAC512("my_secret_key_10210_oqpowqkq192199qkkwoxa");
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT decodedJWT = verifier.verify(token);
String username = decodedJWT.getSubject();
String[] roles = decodedJWT.getClaim("roles").asArray(String.class);
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
stream(roles).forEach(s -> authorities.add(new SimpleGrantedAuthority(s)));
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
} catch (Exception exception) {
log.error("Error logging in: {}", exception.getMessage());
response.setHeader("error", exception.getMessage());
response.setStatus(FORBIDDEN.value());
Map<String, String> error = new HashMap<>();
error.put("error_message", exception.getMessage());
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), error);
}
} else {
filterChain.doFilter(request, response);
}
}
}
Ps: Actually I did this by saving the token in cookies, but I didn't think this is a good practice. This is additional filter in users-app:
#Component
#RequiredArgsConstructor
public class CustomHeaderFilter implements Filter {
private final GetTokenService tokenService;
private Map<String, String> tokens = new HashMap<>();
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp =(HttpServletResponse) response;
if (req.getServletPath().equals("/signin")) {
tokens = tokenService
.getTokens(req, req.getParameter("username"), req.getParameter("password"));
}
if (req.getServletPath().equals("/users")) {
String value = "Bearer " + tokens.get("access_token");
resp.setHeader(AUTHORIZATION, value);
Cookie cookies = new Cookie("auth", tokens.get("access_token"));
cookies.setPath("/");
cookies.setMaxAge(1800);
resp.addCookie(cookies);
}
chain.doFilter(req, response);
}
}
And this is how I get from cookies(in main app):
#Slf4j
public class CustomAuthorizationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws IOException {
String header = request.getHeader(AUTHORIZATION);
Cookie[] cookies = request.getCookies();
if (cookies != null) {
String[] info = stream(cookies).map(Cookie::getValue).toArray(String[]::new);
header = "Bearer " + info[0];
}
try {
String token = header.substring("Bearer ".length());
Algorithm algorithm = Algorithm.HMAC512("my_secret_key_10210_oqpowqkq192199qkkwoxa");
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT decodedJWT = verifier.verify(token);
String username = decodedJWT.getSubject();
String[] roles = decodedJWT.getClaim("roles").asArray(String.class);
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
stream(roles).forEach(s -> authorities.add(new SimpleGrantedAuthority(s)));
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
} catch (Exception exception) {
log.error("Error logging in: {}", exception.getMessage());
response.setHeader("error", exception.getMessage());
response.setStatus(FORBIDDEN.value());
Map<String, String> error = new HashMap<>();
error.put("error_message", exception.getMessage());
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), error);
}
}
}
I am trying to read response body from ServerHttpResponse in a FilterFactory class that extents AbstractGatewayFilterFactory. The method executes, but I never see the log line printed. Is this the correct approach to read response ? If yes, what am I missing here ?
#Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest.Builder reqBuilder = exchange.getRequest().mutate();
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 -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
log.info("Response : {}", new String(content, StandardCharsets.UTF_8));
return bufferFactory.wrap(content);
}));
}
return super.writeWith(body);
}
};
long start = System.currentTimeMillis();
return chain.filter(exchange.mutate()
.request(reqBuilder.build())
.response(decoratedResponse)
.build());
};
}
I am using spring cloud gateway as edge server.
This is the flow
If request has a header named 'x-foo' then find the header value, get a string from another server and send that string as response instead of actually proxying the request.
Here is code for Filter DSL
#Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("foo-filter", r -> r.header('x-foo').and().header("x-intercepted").negate()
.filters(f -> f.filter(fooFilter))
.uri("http://localhost:8081")) // 8081 is self port, there are other proxy related configurations too
.build();
}
Code for Foo filter
#Component
#Slf4j
public class FooFilter implements GatewayFilter {
#Autowired
private ReactiveRedisOperations<String, String> redisOps;
#Value("${header-name}")
private String headerName;
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
var foo = request.getHeaders().getFirst(headerName);
return redisOps.opsForHash()
.get("foo:" + foo, "response")
.doOnSuccess(s -> {
log.info("data on success");
log.info(s.toString()); // I am getting proper response here
if (s != null) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().set("x-intercepted", "true");
byte[] bytes = s.toString().getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
response.writeWith(Mono.just(buffer));
response.setComplete();
}
})
.then(chain.filter(exchange));
}
}
The problem is, the response has the response is getting proper 200 code, the injected header is present on response but the data is not available in response.
This is how I got working.
Use flatMap instead of doOnSuccess
don't use then or switchIfEmpty instead use onErrorResume
Return the response.writeWith
#Component
#Slf4j
public class FooFilter implements GatewayFilter {
#Autowired
private ReactiveRedisOperations<String, String> redisOps;
#Value("${header-name}")
private String headerName;
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
var foo = request.getHeaders().getFirst(headerName);
return redisOps.opsForHash()
.get("foo:" + foo, "response")
.flatMap(s -> {
log.info("data on success");
log.info(s.toString()); // I am getting proper response here
if (s != null) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().set("x-intercepted", "true");
byte[] bytes = s.toString().getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(buffer));
}else{ return chain.filter(exchange).then(Mono.fromRunnable(() -> {log.info("It was empty")} }
})
.onErrorResume(chain.filter(exchange));
}
}
everyone. I am now working in a Spring Boot project that one of the function is to send some HTTP requests to third party API and get the responses.
I decided to use RestTemplate as the http client and I created a bean for RestTemplate in a java file with #Configuration
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new RequestLoggingInterceptor());
restTemplate.setInterceptors(interceptors);
restTemplate.setErrorHandler(new RestResponseErrorHandler());
return restTemplate;
}
Custom error handler and logging interceptor are set in restTemplate. Then, I injected the RestTemplate into my service class by
#Autowired
RestTemplate restTemplate;
However, I found that my custom error handler is not working properly and I tried to check the error handler by
ResponseErrorHandler customErrorHandler = restTemplate.getErrorHandler();
I found that theErrorHandler is still DefaultResponseErrorHandler but not my custom errorHandler. Why? Is there anything wrong?
Updated
Below are my custom error handler and logging interceptor
public class RestResponseErrorHandler implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
if (!response.getStatusCode().equals(HttpStatus.ACCEPTED)) {
return true;
}
return false;
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
if (!response.getStatusCode().equals(HttpStatus.ACCEPTED)) {
throw new RestResponseErrorException(response.getStatusCode() + "" + response.getStatusText());
}
}
}
and
public class RequestLoggingInterceptor implements ClientHttpRequestInterceptor {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
logRequest(request, body);
ClientHttpResponse response = execution.execute(request, body);
logResponse(response);
return response;
}
private void logRequest(HttpRequest request, byte[] body) throws IOException {
if (log.isDebugEnabled()) {
log.debug("Request begin -----------------------------");
log.debug("URI:{}", request.getURI());
log.debug("Method:{}", request.getMethod());
log.debug("Headers:{}", request.getHeaders().toString());
log.debug("Request body:{}", new String(body, "UTF-8"));
log.debug("Request end -----------------------------");
}
}
private void logResponse(ClientHttpResponse response) throws IOException {
if (log.isDebugEnabled()) {
log.debug("Response begin -----------------------------");
log.debug("Status code:{}", response.getStatusCode());
log.debug("Status text:{}", response.getStatusText());
log.debug("Headers:{}", response.getHeaders().toString());
log.debug("Response body:{}", StreamUtils.copyToString(response.getBody(), Charset.defaultCharset()));
log.debug("Response end -----------------------------");
}
}
}
I'm trying to add a custom filter before I invoke my REST Service. In this below class, I'm trying to add the custom filter in the HttpRequest but I'm getting error :-
java.lang.UnsupportedOperationException: null
at java.util.Collections$UnmodifiableMap.computeIfAbsent(Collections.java:1535) ~[na:1.8.0_171]
at org.springframework.util.CollectionUtils$MultiValueMapAdapter.add(CollectionUtils.java:459) ~[spring-core-5.0.7.RELEASE.jar:5.0.7.RELEASE]
public class AuthenticationWebFilter implements WebFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationWebFilter.class);
#Autowired
private TokenServiceRequest tokenServiceRequest;
#Autowired
private AuthenticationProvider authenticationProvider;
public AuthenticationWebFilter(TokenServiceRequest tokenServiceRequest, AuthenticationProvider authenticationProvider) {
super();
this.tokenServiceRequest = tokenServiceRequest;
this.authenticationProvider = authenticationProvider;
}
#Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
HttpHeaders requestHeaders = serverWebExchange.getRequest().getHeaders();
HttpHeaders responseHeaders = serverWebExchange.getResponse().getHeaders();
LOGGER.info("Response HEADERS: "+responseHeaders);
LOGGER.info("Request HEADERS: "+serverWebExchange.getRequest().getHeaders());
tokenServiceRequest.setUsername(serverWebExchange.getRequest().getHeaders().getFirst(CommerceConnectorConstants.USERNAME));
tokenServiceRequest.setPassword(serverWebExchange.getRequest().getHeaders().getFirst(CommerceConnectorConstants.PASSWORD));
tokenServiceRequest.setClientId(serverWebExchange.getRequest().getHeaders().getFirst(CommerceConnectorConstants.CLIENT_ID));
tokenServiceRequest.setSecretClient(serverWebExchange.getRequest().getHeaders().getFirst(CommerceConnectorConstants.SECRET_CLIENT));
LOGGER.info("Token Received: " + authenticationProvider.getUserAccessToken(tokenServiceRequest).getTokenId());
//responseHeaders.set(CommerceConnectorConstants.X_AUTH_TOKEN, authenticationProvider.getUserAccessToken(tokenServiceRequest).getTokenId());
//responseHeaders.add(CommerceConnectorConstants.X_AUTH_TOKEN, authenticationProvider.getUserAccessToken(tokenServiceRequest).getTokenId());
//This below code is not working
serverWebExchange.getRequest().getQueryParams().add("test", "value");
//This below code is not working
//serverWebExchange.getRequest().getHeaders().add(CommerceConnectorConstants.X_AUTH_TOKEN, authenticationProvider.getUserAccessToken(tokenServiceRequest).getTokenId());
LOGGER.info("Exiting filter#AuthenticationWebFilter");
return webFilterChain.filter(serverWebExchange);
}
}
In HTTPResponse, I can set the custom headers but my requirement is to add the custom header in the HTTPRequest. Please advise.
If you're in spring cloud gateway, request header could be modified by implements GlobalFilter or GatewayFilter.
#Component
public class LogFilter implements GlobalFilter, Ordered {
private Logger LOG = LoggerFactory.getLogger(LogFilter.class);
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(
exchange.mutate().request(
exchange.getRequest().mutate()
.header("customer-header", "customer-header-value")
.build())
.build());
}
#Override
public int getOrder() {
return 0;
} }
If you're in ZuulFilter, addZuulRequestHeader could modified the request header.
RequestContext.getCurrentContext().addZuulRequestHeader("customer-header", "customer-header-value");
Hope it's helpful.
public class CustomTokenFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
ServerHttpRequest mutateRequest = serverWebExchange.getRequest().mutate()
.header("token", "test")
.build();
ServerWebExchange mutateServerWebExchange = serverWebExchange.mutate().request(mutateRequest).build();
return webFilterChain.filter(mutateServerWebExchange);
}
}
I think the exception is thrown because of security reasons. It would be nasty if a filter could add/modify the HTTP request headers. Of course, you can accomplish this by creating a series of decorators:
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebExchangeDecorator;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
public class CustomFilter implements WebFilter {
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
ServerWebExchangeDecorator decorator = new ServerWebExchangeDecoratorImpl(serverWebExchange);
//do your stuff using decorator
return webFilterChain.filter(decorator);
}
}
class ServerWebExchangeDecoratorImpl extends ServerWebExchangeDecorator {
private ServerHttpRequestDecorator requestDecorator;
public ServerWebExchangeDecoratorImpl(ServerWebExchange delegate) {
super(delegate);
this.requestDecorator = new ServerHttpRequestDecoratorImpl(delegate.getRequest());
}
#Override
public ServerHttpRequest getRequest() {
return requestDecorator;
}
}
class ServerHttpRequestDecoratorImpl extends ServerHttpRequestDecorator {
// your own query params implementation
private MultiValueMap queryParams;
public ServerHttpRequestDecoratorImpl(ServerHttpRequest request) {
super(request);
this.queryParams = new HttpHeaders();
this.queryParams.addAll(request.getQueryParams());
}
#Override
public MultiValueMap<String, String> getQueryParams() {
return queryParams;
}
//override other methods if you want to modify the behavior
}
I'm having the same problem because headers already have the same key; My solution is to set the key in the header, first check whether the key exists;
#Configuration
public class AuthGatewayFilter implements GlobalFilter, Ordered {
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Consumer<HttpHeaders> httpHeaders = httpHeader -> {
// check exists
if(StringUtils.isBlank(httpHeader.getFirst("xxx"))){
httpHeader.add("xxx", "xxx");
}
};
ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate().headers(httpHeaders).build();
exchange = exchange.mutate().request(serverHttpRequest).build();
return chain.filter(exchange);
}
}