Is it possible to add same-site attribute to Spring Security CSRF's .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) - spring-boot

My security configuration has a following line:
...csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())...
Which sends a csrf cookie with every request back to client. This cookie has no same-site attribute set. Is it possible to add the same-site attribute as well? I looked around some methods inside that class and there is nothing about extra attributes to my knowledge.
How can this be done?

Unfortunately, as of version 4.0.1, the servlet-api doesn't allow you to add the Same-Site attribute to a Cookie. Hopefully this will change soon.
But in the meantime, you could provide your own CsrfTokenRepository implementation that instead of adding a Cookie to the HttpServletResponse (and thus being limited by the servlet-api's representation of a cookie), sets the cookie directly in HTTP header:
public class CustomCsrfTokenRepository implements CsrfTokenRepository {
// implement other methods...
#Override
public void saveToken(CsrfToken token, HttpServletRequest request,
HttpServletResponse response) {
// some version of this:
response.setHeader("Set-Cookie", "HttpOnly; SameSite=strict");
}
}
You can take a look at CookieCsrfTokenRepository to fill in the gaps.

Just to chime in and follow NatFars answer, this solution is especially simple in Kotlin where you can delegate to other objects without having to copy code from the original object in hacky ways:
class CookieCsrfTokenRepositoryWrapper(private val repo: CsrfTokenRepository): CsrfTokenRepository by repo {
override fun saveToken(token: CsrfToken?, req: HttpServletRequest?, res: HttpServletResponse?) {
repo.saveToken(token, req, res)
res?.getHeaders("Set-Cookie")?.toList()?.forEach {
if(it.contains("XSRF") && !it.contains("SameSite"))
res.setHeader("Set-Cookie", "$it; SameSite=strict")
}
}
}
val repo = CookieCsrfTokenRepositoryWrapper(CookieCsrfTokenRepository.withHttpOnlyFalse())
About delegation in Kotlin: https://kotlinlang.org/docs/reference/delegation.html

Related

set Domain on cookie using spring security when login success

How can I set the property "domain" on the users cookie when the user has authenticated from spring?
Edit: id like to add domain=".mydomain.com" to cookie with id JSESSIONID
I dont want to deal with spring-session-core or the particular implementation of the session like redis, and Im not using spring-boot. What is the easiest way to do this?
I dont want to jump in the rabbit hole of redis if I can avoid it.
Edit: investigated if set_cookie can be modified in custom implementation of AuthenticationSuccessHandlerImpl that extends AbstractAuthenticationTargetUrlRequestHandler, but "set_cookie" isnt set until
response.sendRedirect(redirectUrl);
of DefaultRedirectStrategy implements RedirectStrategy, but the also isCommitted()==True so set_cookie cant be changed.
I varified this by implementing my redirect strategy:
#Override
public void sendRedirect(HttpServletRequest request, HttpServletResponse response, java.lang.String url)
throws IOException {
LOGGER.info("sendRedirect cookie size: "+response.getHeaders(HttpHeaders.SET_COOKIE).size()+ " is commited:"+response.isCommitted());
String redirectUrl = calculateRedirectUrl(request.getContextPath(), url);
redirectUrl = response.encodeRedirectURL(redirectUrl);
LOGGER.info("sendRedirect cookie size: "+response.getHeaders(HttpHeaders.SET_COOKIE).size()+ " is commited:"+response.isCommitted());
response.sendRedirect(redirectUrl);
}
Looks like set_cookie is set in response.sendRedirect and is committed at the same time.

Using a request header value in #PreAuthorize

Is it possible to use a request header value in #PreAuthorize?
In my app, all requests have a custom header included which I need to use in conjunction with the user role to determine whether or not they should be allowed to access the controller.
It's ok if someone manually specifies a header as that won't be a security issue, as ultimately the role will control this. But I will need to use it to cut down on checking for that manually in each controller method.
Thank you,
Matt
1 - This may be the fastest method if you will only use it in a few places.
#GetMapping(value = "/private-api-method")
#PreAuthorize("#request.getHeader('header-name') == 'localhost:8080'")
public ResponseEntity<String> privateApiMethod(HttpServletRequest request) {
return ResponseEntity.ok("OK!");
}
OR
#GetMapping(value = "/private-api-method")
#PreAuthorize("#header == 'localhost:8080'")
public ResponseEntity<String> privateApiMethod(#RequestHeader("header-name") String header) {
return ResponseEntity.ok("OK!");
}
2 - This may be the best method if you will use it in many places. (In the SecurityServise, you can add multiple different methods of checking.)
#GetMapping(value = "/private-api-method")
#PreAuthorize("#securityService.checkHeader(#request)")
public ResponseEntity<String> privateApiMethod(HttpServletRequest request) {
return ResponseEntity.ok("OK!");
}
3 - You can choose a special method for yourself
A Custom Security Expression with Spring Security
Since you intend to check for a particular header/cookie/request-attribute for every controller methods, you should opt for a Filter as this would be a standard and you can have a guarantee for it be executed for each and every method and that too only once by extending from OncePerRequestFilter
Having said that, there would be 2 way you can achieve this:
By extending AbstractAuthenticationProcessingFilter or OncePerRequestFilter
For this you may refer the spring-security jwt token validation flow which all would advocate for:
Add method security at your desired controller method as #PreAuthorize("hasAuthority('USER_ROLE')")
Intercept the request before UsernamePasswordAuthenticationFilter, extract the Authentication header or cookies from the request and validate the token value for claims.
public class CustomHeaderAuthFilter extends AbstractAuthenticationProcessingFilter{
#Override
public Authentication attemptAuthentication(
HttpServletRequest request, HttpServletResponse response){
// Get all the headers from request, throw exception if your header not found
Enumeration<String> reqHeaders = request.getHeaderNames();
Assert.notNull(reqHeaders, "No headers found. Abort operation!");
Collections.list(reqHeaders)
.stream()
.filter(header_ -> header_.equals("TARGET_HEADER_NAME"))
.findAny().ifPresent(header_ -> {
// header found, would go for success-andler
});
// Here it means request has no target header
SecurityContextHolder.clearContext();
failureHandler.onAuthenticationFailure(request, response, new CustomException(""));
}
}
Going by this way, you need to register your filter with WebSecurityConfigurerAdapter and you may also provide your AuthenticationProvider if you extend from AbstractAuthenticationProcessingFilter.
By accessing HTTP Headers in rest controllers using #RequestHeader as dm-tr has mentioned.
Maybe you can try this
#PreAuthorize("hasAuthority('ROLE_SOMETHING')")
#RequestMapping("PATH")
public void checkIt(#RequestHeader("header-name") String header) {
if (null != header /* && header meets certain condition*/) {
// stuff
} else throw new ResponseStatusException(HttpStatus.FORBIDDEN); // PERMISSION NOT GRANTED, 403 ERROR
}

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)
}
}
}

How to disable Session cookie in Apache HttpAsyncClientBuilder

I'm talking to a service that fails in getting the user authentication cookie if there is a JSESSIONID cookie in the request, and I can't modify this service.
It also returns this session cookie on each response, so my first request work (no other cookie than the user's one), but next requests will always fail.
My restTemplate configuration uses a custom request factory that extends Spring's HttpComponentsAsyncClientHttpRequestFactory with an AsyncClient from Apache's HttpAsyncClientBuilder.
Is there a way to configure that to always ignore the session cookie ?
Thanks in advance!
It would have been nice to find a solution impliying only configuration, but I couldn't so I ended up extending the BasicCookieStore:
public class DefaultCookieStore extends BasicCookieStore {
private static String SESSION_COOKIE_NAME = "JSESSIONID";
#Override
public void addCookie(Cookie cookie) {
if (!SESSION_COOKIE_NAME.equals(cookie.getName())) {
super.addCookie(cookie);
}
}
}
And adding it to my HttpAsyncClientBuilder and HttpClientBuilder with the method setDefaultCookieStore.
Probably not the best thing, but it works well.

Response MIME type for Spring Boot actuator endpoints

I have updated a Spring Boot application from 1.4.x to 1.5.1 and the Spring Actuator endpoints return a different MIME type now:
For example, /health is now application/vnd.spring-boot.actuator.v1+json instead simply application/json.
How can I change this back?
The endpoints return a content type that honours what the client's request says it can accept. You will get an application/json response if the client send an Accept header that asks for it:
Accept: application/json
In response to the comment of https://stackoverflow.com/users/2952093/kap (my reputation is to low to create a comment): when using Firefox to check endpoints that return JSON I use the Add-on JSONView. In the settings there is an option to specify alternate JSON content types, just add application/vnd.spring-boot.actuator.v1+jsonand you'll see the returned JSON in pretty print inside your browser.
As you noticed the content type for actuators have changed in 1.5.x.
If you in put "application/json" in the "Accept:" header you should get the usual content-type.
But if you don't have any way of modifying the clients, this snippet returns health (without details) and original content-type (the 1.4.x way).
#RestController
#RequestMapping(value = "/health", produces = MediaType.APPLICATION_JSON_VALUE)
public class HealthController {
#Inject
HealthEndpoint healthEndpoint;
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<Health > health() throws IOException {
Health health = healthEndpoint.health();
Health nonSensitiveHealthResult = Health.status(health.getStatus()).build();
if (health.getStatus().equals(Status.UP)) {
return ResponseEntity.status(HttpStatus.OK).body(nonSensitiveHealthResult);
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(nonSensitiveHealthResult);
}
}
}
Configuration (move away existing health)
endpoints.health.path: internal/health
Based on the code in https://github.com/spring-projects/spring-boot/issues/2449 (which also works fine but completely removes the new type) I came up with
#Component
public class ActuatorCustomizer implements EndpointHandlerMappingCustomizer {
static class Fix extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
Object attribute = request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (attribute instanceof LinkedHashSet) {
#SuppressWarnings("unchecked")
LinkedHashSet<MediaType> lhs = (LinkedHashSet<MediaType>) attribute;
if (lhs.remove(ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON)) {
lhs.add(ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON);
}
}
return true;
}
}
#Override
public void customize(EndpointHandlerMapping mapping) {
mapping.setInterceptors(new Object[] {new Fix()});
}
}
which puts the new vendor-mediatype last so that it will use application/json for all actuator endpoints when nothing is specified.
Tested with spring-boot 1.5.3
Since SpringBoot 2.0.x the suggested solution in implementing the EndpointHandlerMappingCustomizer doesn't work any longer.
The good news is, the solution is simpler now.
The Bean EndpointMediaTypes needs to be provided. It is provided by the SpringBoot class WebEndpointAutoConfiguration by default.
Providing your own could look like this:
#Configuration
public class ActuatorEndpointConfig {
private static final List<String> MEDIA_TYPES = Arrays
.asList("application/json", ActuatorMediaType.V2_JSON);
#Bean
public EndpointMediaTypes endpointMediaTypes() {
return new EndpointMediaTypes(MEDIA_TYPES, MEDIA_TYPES);
}
}
To support application/vnd.spring-boot.actuator.v1+json in Firefox's built in JSON viewer, you can install this addon: json-content-type-override. It will convert content types that contain "json" to "application/json".
Update: Firefox 58+ has built-in support for these mime types, and no addon is needed anymore. See https://bugzilla.mozilla.org/show_bug.cgi?id=1388335

Resources