Using #PreAuthorize in a method that returns a SseEmitter returns HTTP 406 - spring

I'm using jwt for authentication, I'm suspecting that it has something to do with the request header which has the token.
Receiving a server sent event without adding #PreAuthorize works just fine:
#GetMapping("/sse")
public SseEmitter serverSentEvent() throws IOException
{
SseEmitter emitter = new SseEmitter(2592000000L);
emitter.send("OK");
return emitter;
}
How ever when i add #PreAuthorize to check use role, i get a 406 response (can’t establish a connection to the server at http://localhost:8080/sse)
#GetMapping("/sse")
#PreAuthorize("hasRole('ADMIN')")
public SseEmitter serverSentEvent() throws IOException
{
SseEmitter emitter = new SseEmitter(2592000000L);
emitter.send("OK");
return emitter;
}

Apparently, the JWT interceptor wasn't adding the token in authorization header for EventSource requests, so I used ng-event-source which allows for custom HTTP headers in EventSource.

Related

Spring RSocket over WebSocket - Access user information from HTTP session

In my web application, users login using a username/password combination and get a session cookie. When initiating a WebSocket connection, I can easily access the user information in the WebSocketHandler, for example:
#Component
public class MyWebSocketHandler implements WebSocketHandler {
#Override
public Mono<Void> handle(WebSocketSession session) {
// two ways to access security context information, either like this:
Mono<Principal> principal = session.getHandshakeInfo().getPrincipal();
// or like this
Mono<SecurityContext> context = ReactiveSecurityContextHolder.getContext();
//...
return Mono.empty();
}
}
Both reuse the HTTP session from the WebSocket handshake, I don't have to send additional authentication over the WebSocket itself. With STOMP the same thing applies: I can just reuse the information of the HTTP session.
How do I achieve the same thing using RSocket? For example, how would I get information about the user inside a MessageMapping method like this?:
#Controller
public class RSocketController {
#MessageMapping("test-stream")
public Flux<String> streamTest(RSocketRequester requester) {
// this mono completes empty, no security context available :(
Mono<SecurityContext> context = ReactiveSecurityContextHolder.getContext();
return Flux.empty();
}
}
I found many resources how to setup authentication with RSocket, but they all rely on an additional authentication after the WebSocket connection is established, but I specifically want to reuse the web session and don't want to send additional tokens over the websocket.
Have you tried the following? I found it in the documentation: 2.2 Secure Your RSocket Methods (might have to scroll down a bit) https://spring.io/blog/2020/06/17/getting-started-with-rsocket-spring-security
#PreAuthorize("hasRole('USER')") // (1)
#MessageMapping("fire-and-forget")
public Mono<Void> fireAndForget(final Message request, #AuthenticationPrincipal UserDetails user) { // (2)
log.info("Received fire-and-forget request: {}", request);
log.info("Fire-And-Forget initiated by '{}' in the role '{}'", user.getUsername(), user.getAuthorities());
return Mono.empty();
}
You can get the user information using #AuthenticationPrincipal Mono<UserDetails> userDetails.
In case someone use JWT authentication as me you need to add #AuthenticationPrincipal Mono<Jwt> jwt to your method arguments.
But for this to work, you need to configure the RSocketMessageHandler bean, that resolvs the argument.
#Bean
public RSocketMessageHandler rSocketMessageHandler(RSocketStrategies strategies) {
RSocketMessageHandler handler = new RSocketMessageHandler();
handler.getArgumentResolverConfigurer()
.addCustomResolver(new AuthenticationPrincipalArgumentResolver());
handler.setRSocketStrategies(strategies);
return handler;
}
Important you have to use org.springframework.security.messaging.handler.invocation.reactive.AuthenticationPrincipalArgumentResolver() class as the resolver, and for that you need spring-security-messaging dependency.

Spring security - Get SESSION cookie value in AuthenticationSuccessHandler

I know that spring security creates a cookies names SESSION on successful authentication. Is it possible to get hold of that cookie value in AuthenticationSuccessHandler.
I have a following implementation inside which I need that SESSION cookie value. I looked as response headers of HttpServletResponse, but they have XSRF-TOKEN set-cookie headers,
#Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException {
// GET SESSION, COOKIE VALUE HERE
}
}
Can you please help.
The SESSION cookie is created by Spring Session's DefaultCookieSerializer, which is called every time a new Session is created, and not necessarily after successful authentication.
Spring Session's SessionRepositoryFilter wraps the HttpServletRequest in such a way that whenever you obtain an HttpSession from the request at any point in your application, you're actually getting a Spring Session object. However, this cookie is written to the response after your handler has been called, as you can see in SessionRepositoryFilter:
try {
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
finally {
wrappedRequest.commitSession(); //the SESSION cookie is created if necessary
}
So if the session has just been created for this request...
The cookie won't be available in the HttpServletRequest because the cookie hasn't been sent yet (and so the browser couldn't have sent it)
The cookie won't be HttpServletResponse as a "Set-Cookie" header because it will be written after your application has handled the request.
However, you could get the cookie value:
String cookieValue = request.getSession().getId();
Note: The above code will force Spring Session to create a session backed Redis/Jdbc/etc that will be used later to generate the SESSION cookie.
I got it using the getSession().getId() method from request. My example is using the Webflux implementation with Kotlin but apparently works similar in HttpServletRequest implementation see https://javaee.github.io/javaee-spec/javadocs/javax/servlet/http/HttpServletRequest.html#getSession--
class AuthenticationSuccessHandler : ServerAuthenticationSuccessHandler {
private val location = URI.create("https://redirect.page")
private val redirectStrategy: ServerRedirectStrategy = DefaultServerRedirectStrategy()
override fun onAuthenticationSuccess(webFilterExchange: WebFilterExchange?, authentication: Authentication?): Mono<Void> {
val exchange = webFilterExchange!!.exchange
return exchange.session.flatMap {
it.id // 87b5639c-7404-48a1-b9da-3ca47691a962
this.redirectStrategy.sendRedirect(exchange, location)
}
}
}

Make Angular client login to Spring backend with JWT

I added JWT to my Spring backend following this guide: https://auth0.com/blog/securing-spring-boot-with-jwts/
When I send a PUT request using a software like Postman everything works fine, but as soon as I'm trying to login with my Angular client the data in the HttpServletRequest is empty.
I check the data in the attemptAuthentication method of the JWTLoginFilter in the following way:
#Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException, IOException, ServletException {
String reqBody = req.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
// this appears to be empty on angular client calls
System.out.println(reqBody);
ObjectMapper objectMapper = new ObjectMapper().configure(Feature.AUTO_CLOSE_SOURCE, true)
.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
AccountCredentials creds = objectMapper.readValue(reqBody, AccountCredentials.class);
return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(creds.getUsername(),
creds.getPassword(), Collections.emptyList()));
}
I'm sending the request out of the client like this:
const user = {
username: "asdf",
password: "asdf"
};
// imported from '#angular/http'
const headers = new Headers({
'Content-Type': 'application/json'
});
const body = JSON.stringify(user);
return this.http
.put("http://localhost:8080/api/login", body, {headers})
.toPromise()
.then(response => response.json().data as User)
.catch(this.handleError);
My suggestion would be that I'm sending the request body in a wrong way, but I can't see, what I'm doing wrong.
I tried:
sending the body as a regular JS object
sending it wrapped in another object
sending it as a JSON string as shown in the example
using POST instead of PUT (although it works with PUT in Postman)
changing the Content-Type Header to other values
None of this made any data appear in the backend.
If you need more information about anything, please ask me.
I got it.
I needed to allow CORS also on my HttpSecurity object as following:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.so()
.on();
}
}
I'm not really sure, why the requests I sent with Postman could get received without CORS enabled in my HttpSecurity, but nevertheless it's working now.
I hope I can help others with this in future.
This might be the reason, Spring OAuth 2 normally expect a POST request with URL encoded username and password. So try something like this.
return this.http.post(Url, "grant_type=password&username=" + username + "&password=" + password)
I'm not 100% sure if this the case as mine was a Spring-MVC, but I expect it to be very similar.

Building a façade with spring which calls another server and returns its response

For an application I need to create a security façade in Spring 4.x.
This thiny layer must accepts any request from our mobile application and execute a security check for the provided token (with openId and Oauth).
Upon a successful validation, the request needs to be forwarded to the backend application, which does not need to be aware of the security token mechanism.
Thus, the flow will be something like this:
security_facade_url/path/of/the/request
With a header that indicates the backend to invoke upon successful validation of the token
Upon successful validation the security façade sends a request to the backend URL
backend_application_url/path/of/the/request
The façade must not have a controller which maps to any possible path of the request, but must call the request on the correct backend server, based on a value in the header of the request. Then return this response to the user.
What I have so far is an implementation of the HandlerInterceptor. This interceptor works, however, I am not really happy with the way I need to avoid the afterCompletion by throwing an exception in the postHandle method.
If I do not throw an error, the default error page is appended to the correct response in the afterCompletion step.
This is my code so far:
public class RequestProcessingInterceptor implements HandlerInterceptor {
private final Logger log = LoggerFactory.getLogger(RequestProcessingInterceptor.class);
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
log.info("Doing some security stuff now ...");
log.warn("... security ok ... since I am not really checking stuff");
return true;
}
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
log.info("Forwarding request and sending that info back ...");
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
WebResource service = client.resource(UriBuilder.fromUri("http://localhost:8080").build());
response.setContentType("application/json");
response.getWriter().write(service.path(modelAndView.getModel().get("path").toString()).accept("application/json").get(String.class));
response.setStatus(200);
throw new Exception("Need to avoid the execution of the afterCompletion. Only way to do so is by throwing an exception...");
}
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
Is there a more proper way to intervene with the Spring livecycle or obtain the behaviour as described above?
Found a better solution. For what I need, I do not need to manipulate the results in an interceptor.
A much cleaner way is to define a Controller which maps with the request methods.
#RequestMapping(method = {RequestMethod.GET, RequestMethod.PUT, RequestMethod.POST})
public void handleRequest(HttpServletRequest request, HttpServletResponse response) { // code omitted }
You should not try to avoid the call to afterCompletion. Just implement an empty method and let SpringFramework call it.
Provided your controller returns null indicating that no view has to be called, it should work with a smoother Spring integration.
But I cannot understand why you use Spring MVC here. As you only interact with low level HttpServletRequest and HttpServletResponse, you could as well use :
a dedicated servlet in charge to relay the request and response to the backend and write the returned value in the response
a filter that would do the security stuff before passing request to filter chain

Spring 3.2 REST API add cookie to the response outside controller

I'm using Spring 3.2.4 and Spring Security 3.2.3 to handle RESTful API call to "get security token" request that returns the token (which would be used to secure subsequent requests to the service). This is a POST request which has a body with username and password and is processed in the controller:
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public SessionTokenResponse getSessionToken(#RequestBody Credentials credentials, ModelAndView interceptorModel) throws AccessException {
final String token = webGate.getSessionTokenForUser(credentials.getUsername(), credentials.getPassword());
LOGGER.debug("Logged in user : " + credentials.getUsername());
interceptorModel.addObject(SessionConstants.INTERCEPTOR_MODEL_TOKEN_KEY, token); // Used by post-processing in interceptors, e.g. add Cookie
return new SessionTokenResponse(ResponseMessages.SUCCESS, token);
}
After the controller has successfully finished processing the request I would like to add a cookie with the token to the response.
I tried HandlerInterceptorAdapter implementation, but I cannot find the way to the the 'token' from the response or ModelAndView:
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView interceptorModel) throws Exception {
final String token = (String) interceptorModel.getModel().get(SessionConstants.INTERCEPTOR_MODEL_TOKEN_KEY);
if (token != null) {
final Cookie obsso = new Cookie(cookieName, token);
obsso.setPath(cookiePathUri);
obsso.setDomain(cookieDomain);
obsso.setMaxAge(cookieMaxAge);
response.addCookie(obsso);
}
}
The interceptorModel is null .
It seems that Spring MVC doesn't provide it to the postHandle since the #ResponseBody has been already resolved and there is no need for the ModelAndView anymore (this is just my assumption based on the debugging).
What is the correct way of achieving that (add cookie to the response) outside the controller in the interceptor or maybe listener?
To retrieve the token you can use the request object
request.setAttribute(SessionConstants.INTERCEPTOR_MODEL_TOKEN_KEY, token);
and then in the postHandle
String token = ( String ) request.getAttribute(SessionConstants.INTERCEPTOR_MODEL_TOKEN_KEY);
However I don't think you can add a cookie to the response object in postHandle as the response is already committed.
Perhaps you could store the token information on the servlet context instead.
In your controller, add the token information to the servlet context.
Then implement preHandle, so that every api call can check if token for that user exists on servlet context, if so you can add cookie to the response.

Resources