Spring 4.3.6 STOMP / WebSockets with auth-token - java

SOLVED: see comment
I am currently trying to open a WebSockets connection between a spring server and an angular client with a x-auth-token. Therefore I have the following pieces of code:
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompSession;
import org.springframework.messaging.simp.stomp.StompHeaders;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#Configuration
#EnableWebSocketMessageBroker
#Order(Ordered.HIGHEST_PRECEDENCE + 99)
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
LOGGER.info("registering websockets");
registry.addEndpoint("/api/v1/websocket").setAllowedOrigins("*").withSockJS();
}
#Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exceptio) {
LOGGER.info("ERRORORRRRRRRR");
exception.printStackTrace();
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new ChannelInterceptorAdapter() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
LOGGER.info("in override");
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
String authToken = accessor.getFirstNativeHeader("x-auth-token");
LOGGER.info("Header auth token: " + authToken);
// Principal user = ... ; // access authentication header(s)
//accessor.setUser(user);
}
return message;
}
});
}
}
As you can see from my WebSocketConfig I am currently not really processing the x-auth-token, but rather trying to log it once. In the end I was trying to follow the Spring documentation: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-authentication-token-based
The only problem I have with this is that in configureClientInboundChannel() the preSend() method seems not get called - and I cannot figure out why.
On the client side, the following error is displayed:
As you can see by comparing the error log and the endpoint defined in registerStompEndpoints() shows the actual endpoint is the correct one. Still I do not understand, why LOGGER.info("in override"); in configureClientInboundChannel() is not called.
I would really like to go with the auth-token being transferred via the stomp headers instead of the hacky token-as-URL-paramter-solution. Does someone here have an idea? Did I miss something in the already linked spring documentation?
Thanks in advance for your help!

Related

Set SameSite attribute of JSESSIONID cookie - JHipster application

I'm trying to set the SameSite attribute of the JSESSIONID cookie in our JHipster gateway, and upon trying to verify in Chrome, there is nothing showing up under the SameSite column for it.
Possibly of note: we're currently not deployed and running the application locally on HTTP (a localhost address). Running in TLS mode also has the same problem, however.
These are two things I've tried in order to get this working:
The second approach from the first answer here How to enable samesite for jsessionid cookie - a filter that is used in JHipster's SecurityConfiguration.java file in the configure() method.
import java.io.IOException;
import java.util.Collection;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
public class SameSiteFilter implements javax.servlet.Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
addSameSiteCookieAttribute((HttpServletResponse) response); // add SameSite=strict cookie attribute
}
private void addSameSiteCookieAttribute(HttpServletResponse response) {
Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
boolean firstHeader = true;
for (String header : headers) { // there can be multiple Set-Cookie attributes
if (firstHeader) {
response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
firstHeader = false;
continue;
}
response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
}
}
#Override
public void destroy() {
}
}
A CookieSerializer which we got from an internal partner:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.web.http.DefaultCookieSerializer;
import org.springframework.session.web.http.CookieSerializer;
#Configuration
class CookieConfiguration {
#Bean
public static CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setSameSite("Lax");
return serializer;
}
}
Neither of these work. Is there something else we can try for this particular flavor of Spring?
In case you are using Tomcat (i.e. not WebFlux), the following configuration will add SameSite=strict to all cookies, including JSESSIONID:
#Configuration
public class SameSiteCookieConfiguration implements WebMvcConfigurer {
#Bean
public TomcatContextCustomizer configureSameSiteCookies() {
return context -> {
final Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
cookieProcessor.setSameSiteCookies("strict");
context.setCookieProcessor(cookieProcessor);
};
}
}

Why is Angular not sending cookies with a POST request even though I've set withCredentials to true?

I have edited the question to make more sense. The original was:
How can I fix Angular and Spring Boot configuration with Spring JDBC authentication so that I am able to logout even with CSRF protection enabled?
With CSRF disabled for /logout:
I am able to login (receiving CSRF and JSESSIONID cookies) and logout (200 OK is received) using Postman.
I am able to login (receiving CSRF and JSESSIONID cookies) and logout (200 OK is received) using Firefox and the Angular frontend.
With CSRF enabled for /logout:
I am able to login (receiving CSRF and JSESSIONID cookies) and logout (200 OK is received) using Postman.
I am able to login using Firefox and the Angular frontend.
When trying to log out, however...
There is first a preflight request that succeeds:
Then, I see a request to /logout:
I debugged the backend and it seems Spring is unable to find a matching CSRF token in its TokenRepository. So I end up with a MissingCsrfTokenException and a 403 Forbidden. How can I fix that?
Backend:
SecurityConfiguration:
package org.adventure.configuration;
import org.adventure.security.RESTAuthenticationEntryPoint;
import org.adventure.security.RESTAuthenticationFailureHandler;
import org.adventure.security.RESTAuthenticationSuccessHandler;
import org.adventure.security.RESTLogoutSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private RESTAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private RESTAuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
private RESTAuthenticationFailureHandler authenticationFailureHandler;
#Autowired
private RESTLogoutSuccessHandler restLogoutSuccessHandler;
#Autowired
private DataSource dataSource;
#Autowired
private AccountsProperties accountsProperties;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests().antMatchers("/h2-console/**")
.permitAll();
httpSecurity.authorizeRequests().antMatchers("/secure/**").authenticated();
httpSecurity.cors().configurationSource(corsConfigurationSource());
httpSecurity.csrf()
.ignoringAntMatchers("/h2-console/**")
.ignoringAntMatchers("/login")
//.ignoringAntMatchers("/logout")
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
httpSecurity.headers()
.frameOptions()
.sameOrigin();
httpSecurity.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
httpSecurity.formLogin().successHandler(authenticationSuccessHandler);
httpSecurity.formLogin().failureHandler(authenticationFailureHandler);
httpSecurity.logout().logoutSuccessHandler(restLogoutSuccessHandler);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
JdbcUserDetailsManagerConfigurer jdbcUserDetailsManagerConfigurer = auth.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema();
if (Objects.nonNull(accountsProperties)) {
FirstUser firstUser = accountsProperties.getFirstUser();
if (Objects.nonNull(firstUser)) {
String name = firstUser.getName();
String password = firstUser.getPassword();
if (Objects.nonNull(name) && Objects.nonNull(password) &&
!("".equals(name) || "".equals(password))) {
jdbcUserDetailsManagerConfigurer.withUser(User.withUsername(name)
.password(passwordEncoder().encode(password))
.roles("USER"));
}
}
FirstAdmin firstAdmin = accountsProperties.getFirstAdmin();
if (Objects.nonNull(firstAdmin)) {
String name = firstAdmin.getName();
String password = firstAdmin.getPassword();
if (Objects.nonNull(name) && Objects.nonNull(password) &&
!("".equals(name) || "".equals(password))) {
jdbcUserDetailsManagerConfigurer.withUser(User.withUsername(name)
.password(passwordEncoder().encode(password))
.roles("ADMIN"));
}
}
}
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12); // Strength increased as per OWASP Password Storage Cheat Sheet
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
configuration.setAllowedHeaders(List.of("X-XSRF-TOKEN", "Content-Type"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
source.registerCorsConfiguration("/secure/classifieds", configuration);
source.registerCorsConfiguration("/login", configuration);
source.registerCorsConfiguration("/logout", configuration);
return source;
}
}
RESTAuthenticationEntryPoint:
package org.adventure.security;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#Component
public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
RESTAuthenticationFailureHandler
package org.adventure.security;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#Component
public class RESTAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
super.onAuthenticationFailure(request, response, exception);
}
}
RESTAuthenticationSuccessHandler
package org.adventure.security;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
#Component
public class RESTAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
clearAuthenticationAttributes(request);
}
}
RESTLogoutSuccessHandler
package org.adventure.security;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
#Component
public class RESTLogoutSuccessHandler implements LogoutSuccessHandler {
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
response.setStatus(HttpServletResponse.SC_OK);
}
}
Frontend:
To /login and /logout I make POST requests with withCredentials: true, and have a HttpInterceptor configured:
import { Injectable } from '#angular/core';
import { HttpInterceptor, HttpXsrfTokenExtractor, HttpRequest, HttpHandler, HttpEvent } from '#angular/common/http';
import { Observable } from 'rxjs';
#Injectable()
export class XsrfInterceptor implements HttpInterceptor {
constructor(private tokenExtractor: HttpXsrfTokenExtractor) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let requestToForward = req;
let token = this.tokenExtractor.getToken() as string;
if (token !== null) {
requestToForward = req.clone({ setHeaders: { "X-XSRF-TOKEN": token } });
}
return next.handle(requestToForward);
}
}
LogoutService:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class LogoutService {
private url = 'http://localhost:8080/logout';
constructor(private http: HttpClient) { }
public logout(): Observable<any> {
return this.http.post(
this.url, { withCredentials: true }).pipe(
map(response => {
console.log(response)
})
);
}
}
In HttpClient, the POST method has a little bit different signature than a GET: https://github.com/angular/angular/blob/master/packages/http/src/http.ts
The second parameter is any request body we want to send, not the options, which are the third parameter. So withCredentials: true was simply never set correctly on the actual request.
Changing the call to:
this.http.post(
this.url, null, { withCredentials: true })
fixed the problem.

Why requests are returned 404 status when using WebFilter corsFilter in webflux?

I want to build a REST api in Spring Webflux using functional endpoints. For CORS I use a WebFilter corsFilter method which sets the required headers. I do see that the method is called (I see the log messages from it) and I see that the headers on the response are indeed the ones I set in my Webflux api. However, as soon as I started to use the corsFilter the requests return 404 status (earlier they would return JSON). I suspect that corsFilter doesn't pass over the request to the router functions. Why would that be?
Specifically I'm wondering if this line is enough to connect the cors config with the routes:
HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler(RouterFunctions.toWebHandler(route))
.applicationContext(ctx).build();
This is my main class:
package com.mypackage;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import reactor.ipc.netty.http.server.HttpServer;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
import static org.springframework.web.reactive.function.server.RequestPredicates.method;
import static org.springframework.web.reactive.function.server.RequestPredicates.path;
import static org.springframework.web.reactive.function.server.RouterFunctions.nest;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;
#SpringBootApplication
public class Server {
private static final Logger log = LogManager.getLogger(Server.class);
public static final String HOST = "localhost";
public static final int PORT = 8080;
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(CorsConfiguration.class);
Server server = new Server();
server.startReactorServer(ctx);
System.out.println("Press ENTER to exit.");
System.in.read();
}
public RouterFunction<ServerResponse> routingFunction() {
PersonRepository repository = new DummyPersonRepository();
PersonHandler handler = new PersonHandler(repository);
return nest(path("/person"),
nest(accept(APPLICATION_JSON),
route(GET("/{id}"), handler::getPerson)
.andRoute(method(HttpMethod.GET), handler::listPeople)
).andRoute(POST("/").and(contentType(APPLICATION_JSON)), handler::createPerson));
}
public void startReactorServer(AnnotationConfigApplicationContext ctx) {
RouterFunction<ServerResponse> route = this.routingFunction().filter((request, next) -> {
log.warn(request.path());
if (request.path().contains("person")) {
log.warn("calling next()");
return next.handle(request);
} else {
return ServerResponse.status(UNAUTHORIZED).build();
}
});
HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler(RouterFunctions.toWebHandler(route))
.applicationContext(ctx).build();
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
HttpServer server = HttpServer.create(HOST, PORT);
server.newHandler(adapter).block();
}
}
and this is my CORS config class:
package com.mypackage;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
#Configuration
#EnableWebFlux
public class CorsConfiguration {
private static final Logger log = LogManager.getLogger(CorsConfiguration.class);
private static final String ALLOWED_HEADERS = "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN, mode";
private static final String ALLOWED_METHODS = "GET, PUT, POST, DELETE, OPTIONS";
private static final String ALLOWED_ORIGIN = "*";
private static final String MAX_AGE = "3600";
#Bean
public WebFilter corsFilter() {
log.warn("from CorsConfiguration!!!");
return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
log.warn("after ServerHttpRequest");
if (CorsUtils.isCorsRequest(request)) {
log.warn("inside isCorsRequest");
ServerHttpResponse response = ctx.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
headers.add("Access-Control-Max-Age", MAX_AGE);
headers.add("Access-Control-Allow-Headers", ALLOWED_HEADERS);
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
return chain.filter(ctx);
};
}
}
To use the functional approach when defining your endpoints Spring Boot's official documentation has a very simple example.
FooBarApplication.class this is our main class.
#SpringBootApplication
public class FooBarApplication {
public static void main(String[] args) {
SpringApplication.run(FooBarApplication.class, args);
}
}
RoutingConfiguration.class (or whatever you wanna call it)
#Configuration
public class RoutingConfiguration {
#Bean
public RouterFunction<ServerResponse> monoRouterFunction(UserHandler userHandler) {
return route(GET("/{user}").and(accept(APPLICATION_JSON)), userHandler::getUser)
.andRoute(GET("/{user}/customers").and(accept(APPLICATION_JSON)), userHandler::getUserCustomers)
.andRoute(DELETE("/{user}").and(accept(APPLICATION_JSON)), userHandler::deleteUser);
}
}
#Component
public class UserHandler {
public Mono<ServerResponse> getUser(ServerRequest request) {
// ...
}
public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
// ...
}
public Mono<ServerResponse> deleteUser(ServerRequest request) {
// ...
}
}
any class annotated with #Configuration will be run at startup and run all #Bean annotated methods. So this will run the monoRouterFunction and set up all our routes for us.
Example taken from the official spring boot documentation Spring boot webflux scroll down a little bit.
EDIT:
and as a side note the #EnableWebFlux annotation means that you will disable the auto-configuration of webflux and set upp configuration manually. I do not recommend this if you are just starting out (i know the name is very misleading) you can read about the webflux auto-configuration here Spring WebFlux Auto-configuration
EDIT2:
WebFlux has a built in CorsFilter that you can use all you need is to configure it.
#Bean
CorsWebFilter corsWebFilter() {
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.setAllowedOrigins(Arrays.asList("http://allowed-origin.com"));
corsConfig.setMaxAge(8000L);
corsConfig.addAllowedMethod("PUT");
corsConfig.addAllowedHeader("Baeldung-Allowed");
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig);
return new CorsWebFilter(source);
}
Example taken from Enabling CORS with a WebFilter

Spring boot and Jersey, can't make ExceptionMapper work

I am using Spring boot + Spring Security + Jersey.
I am trying to do something always that an Unathorized error happens with an ExceptionMapper, but it doesn't seem to work. However, other Mappers that I've done work perfectly.
This is my code:
Unauthorized Excepcion:
package com.ulises.usersserver.rest.exceptionsmappers;
import com.ulises.usersserver.rest.dto.ErrorDTO;
import com.ulises.usersserver.rest.dto.ErrorDTOBuilder;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import static com.ulises.usersserver.constants.Constants.REQUEST_ERROR_UNATHORIZED;
public class NotAuthorizedMapper implements ExceptionMapper<NotAuthorizedException> {
#Override
public Response toResponse(NotAuthorizedException e) {
final ErrorDTO errorDTO = ErrorDTOBuilder.builder()
.message(REQUEST_ERROR_UNATHORIZED)
.build();
return Response.status(Response.Status.UNAUTHORIZED)
.type(MediaType.APPLICATION_JSON_TYPE)
.entity(errorDTO)
.build();
}
}
Other custom mapper that works perfectly:
package com.ulises.usersserver.rest.exceptionsmappers;
import com.ulises.usersserver.rest.dto.ErrorDTO;
import com.ulises.usersserver.rest.dto.ErrorDTOBuilder;
import com.ulises.usersserver.services.exceptions.UserNotFoundException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import static com.ulises.usersserver.constants.Constants.REQUEST_ERROR_USER_DOESNT_EXIST;
public class UserNotFoundExceptionMapper implements ExceptionMapper<UserNotFoundException> {
#Override
public Response toResponse(UserNotFoundException e) {
final ErrorDTO errorDTO = ErrorDTOBuilder.builder()
.message(REQUEST_ERROR_USER_DOESNT_EXIST)
.build();
return Response.status(Response.Status.NOT_FOUND).entity(errorDTO).build();
}
}
They are of course registered in Jersey's config:
#Bean
public ResourceConfig jerseyConfig() {
final ResourceConfig resourceConfig = new ResourceConfig();
...
resourceConfig.register(NotFoundMapper.class);
resourceConfig.register(NotAuthorizedMapper.class);
...
return resourceConfig;
}
I don't seem to be able to map other exceptions such as InternalServerErrorException (I managed to map this one by doing
ExceptionMapper<Exception>, which doesn't look very correct to me.
Anyone knows why is this happening? I've checked all possible questions here and none of them solved this.
Thanks in advance.
Okay, it seems Jersey has no control over Spring Security's exceptions.
The way to solve this (had to dig alot) is to override the method from AuthenticationEntryPoint that S-Security uses to return the 401 response if an user isn't authorized.
So I created the following class implementing that interface:
public class CustomEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response, AuthenticationException authException) throws IOException {
JSONObject json = new JSONObject();
json.put("Message", "You don't have access to view this section. Please, log in with an authorized account.");
response.addHeader("Content-Type", "application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().println(json);
}
}
And then just add this configuration to S-Security to use this class as entry point:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().authenticationEntryPoint(new CustomEntryPoint());
..... any other config you had .....
}

How to configure route for port forwarding by Apache Camel?

Here is the codec and data format configuration:
package com.chorke.boot.jproxy.config;
import org.apache.camel.component.hl7.HL7DataFormat;
import org.apache.camel.component.hl7.HL7MLLPCodec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
#Configuration
#ImportResource({"classpath*:/META-INF/camel/applicationContext-camel.xml"})
public class ApacheCamelConfig {
#SuppressWarnings("unused")
private static final Logger log = LoggerFactory.getLogger(ApacheCamelConfig.class);
#Bean
public HL7MLLPCodec hl7codec() {
HL7MLLPCodec hl7codec = new HL7MLLPCodec();
hl7codec.setCharset("UTF-8");
return hl7codec;
}
#Bean
public HL7DataFormat hl7Format() {
HL7DataFormat hl7Format = new HL7DataFormat();
return hl7Format;
}
}
Here is the port forwarding route:
package com.chorke.boot.jproxy.route;
import org.apache.camel.builder.RouteBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ProxyRoute extends RouteBuilder {
private static final Logger log =LoggerFactory.getLogger(ProxyRoute.class);
#Override
public void configure() throws Exception {
from("mina2://tcp://0.0.0.0:22210?codec=#hl7codec&sync=true").process(new Processor() {
public void process(Exchange exchange) throws Exception {
String body = exchange.getIn().getBody(String.class);
log.info("Port-Forwarded body:\n {}", body);
}
}).to("mina2://tcp://192.168.0.10:22210?codec=#hl7codec&sync=true").end();
from("mina2://tcp://0.0.0.0:22211?codec=#hl7codec&sync=true").process(new Processor() {
public void process(Exchange exchange) throws Exception {
String body = exchange.getIn().getBody(String.class);
log.info("Port-Forwarded body:\n {}", body);
}
}).to("mina2://tcp://192.168.0.11:22211?codec=#hl7codec&sync=true").end();
}
}
The route summery is:
============================================
request forwarded
tcp ip:port tcp ip:port
============================================
0.0.0.0:22210 192.168.0.10:22210
0.0.0.0:22211 192.168.0.11:22211
============================================
And it's working fine, it's protocol specific for MLLP. But our goal is to route any request regardless their protocol. Lets say it could be handle any kind of request, not limited to HTTP, REST, SOAP, MLLP, SMTP, FTP, SMB or etc. Would you please help us to configure port-forwarding route regardless their protocol.
There is no out of the box support for protocol detection and route port forwarding in Apache Camel. You are better finding some existing product that can do that.

Categories

Resources