set Domain on cookie using spring security when login success - spring

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.

Related

Spring Session integration into Spring Security - how to populate Authentication

When integrating spring session with spring security I'm not sure how the SecurityContextImpl#Authentication is supposed to be populated when a session is identified by spring session.
Context:
The spring-boot application does not actually handle login,logout or creating the session itself. The session is created in an external, non-spring microservice and shared via MongoDB. Sharing and mapping the session information works and was utilized without issues before spring security.
What works:
Spring session properly resolves the session-id
Spring session retrieves the session (using the session-id) from the session-repository(mongo) and the attributes are populated
The request has a populated session object including all the attributes
What does not work:
Using http.authorizeRequests().antMatchers("admin/**").authenticated() and then requestion and endpoint (with an session cookie) does by no means populate SecurityContext#Authenticated
Possible options
a) I understand, I could implement a custom CustomUserNameFromSessionFilter to prepopulate the
Authenticated in SecureContext (yet authenticated=false) and place it early in SecurityFilter chain. In addition I implement a custom AuthenticationProvider CustomFromSessionAuthenticationProvider, which then picks up Authenticated and basically sets authenticated=true if the session is valid (which is always true at this point)
b) Use RememberMeAuthenticationFilter but I'm not sure how this documentation suits that purpose
c) Somehow utilize AbstractPreAuthenticatedProcessingFilter but it rather seems to be used for external auth-requests
Every option seems not right, and the need for such an implementation seems too common that there is not an existing/better solution. What is the correct approach?
Code snippets
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().disable();
// Logout is yet handled by PHP Only, we yet cannot delete/write sessions here
http.logout().disable();
http.formLogin().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
http.authorizeRequests()
.antMatchers("/admin").authenticated();
}
Thank you #Steve Riesenberg for providing just enough of the hint for me to find the right solution!
To understand my solution and understand when one needs to go this extra route, if first explain the default integration:
Classic spring-session integration in spring-security
When you use spring-security including the authentication via your spring-boot app, the integration of spring-session and spring-security will become natural without anything else required.
When you authorize (login) your user initially the first time via an Authentication via spring-security, it will:
Store the Authentication object in the SecurityContext of that request. - Then SecurityContext will then be stored in the HttpSession (if one exists), with spring-session where ever you configured spring-session to (redis/mongo).
The SecurityContext is stored using a session attribute of the key SPRING_SECURITY_CONTEXT right in the common session data (serialized).
When you then take the session-id given to you after this authentication and make an additional request, the following happens
spring session loads the HttpSession from your storage (including the SecurityContext in the session attribute with the key SPRING_SECURITY_CONTEXT
spring security will call HttpSessionSecurityContextRepository very early in the SecurityFilter chain and check HttpSession for the existence of the session attribute SPRING_SECURITY_CONTEXT and if a SecurityContext is found. If yes, it uses this SecurityContext and loads it as the current request SecurityContext. Since this context includes the already authenticated Authentication object from the prior authentication, the AuthenticationManager/Provider will skip authentication since it is all done and your request is treated as authenticated.
This is the vanilla way and it has one requirement - the authentication process (login) needs to write the SecurityContext into the HttpSession object during the login process.
My case - external login process
In my case, an external, a non spring-boot microservice is handling the entire login process. Still, it stores the session in the (external) session storage, which in my case is MongoDB.
Spring-session is properly configured and can read this session using the session cookie/session id and load the session created externally.
The big 'but' is, this external login will not store any SecurityContext in the session data, since it cannot do that.
At this point - if you have an external login service creating the session, and it is a spring-boot service, be sure you write the SecurityContext properly. So all other microservices then can load this session properly (authenticated) just using the session id and the default spring-session/security integration.
Since this is not an option for me, and if it is none for you, the following solution seems to be the 'by design' way in spring-boot/security IMHO:
You implement your own CustomHttpSessionSecurityContextRepository and register that in the security configuration via
public class ApplicationSecurity extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.securityContext()
.securityContextRepository(new FromExternalSessionSecurityContextRepository());
}
}
This ensure we replace the stock HttpSessionSecurityContextRepository with our own implementation.
Now our custom implementation
public class CustomExternalSessionSecurityContextRepository implements SecurityContextRepository
{
#Override
public SecurityContext loadContext(final HttpRequestResponseHolder requestResponseHolder)
{
HttpServletRequest request = requestResponseHolder.getRequest();
HttpSession httpSession = request.getSession(false);
// No session yet, thus we cannot load an authentication-context from the session. Create a new, blanc
// Authentication context and let others AuthenticationProviders deal with it.
if (httpSession == null) {
return generateNewSecurityContext();
}
Optional<Long> userId = Optional.ofNullable(httpSession.getAttribute(Attribute.SUBJECT_ID.attributeName))
SecurityContext sc = generateNewSecurityContext();
if (userId.isEmpty()) {
// Return a emtpy context if the session has neither no subjectId
return sc;
}
// This is an session of an authenticated user. Create the security context with the principal we know from
// the session and mark the user authenticated
// OurAuthentication uses userId.get() as principal and implements Authentication
var authentication = new OurAuthentication(userId.get());
authentication.setAuthenticated(true);
sc.setAuthentication(authentication);
httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, sc);
return sc;
}
#Override
public void saveContext(
final SecurityContext context, final HttpServletRequest request, final HttpServletResponse response
)
{
// do implement storage back into HttpSession if you want spring-boot to be
// able to write it.
}
#Override
public boolean containsContext(final HttpServletRequest request)
{
HttpSession session = request.getSession(false);
if (session == null) {
return false;
}
return session.getAttribute(SPRING_SECURITY_CONTEXT_KEY) != null;
}
private SecurityContext generateNewSecurityContext()
{
return SecurityContextHolder.createEmptyContext();
}
}
So now changed the behavior of how spring-security loads a SecurityContext from the session. Instead of expecting SecurityContext to be present already, we check if the session is proper and the create the SecurityContext from the given data and store return it. This will make the entire following chain use and respect this SecurityContext

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.

How to get request attributes in authentication-success-handler

I am trying to do few things in authentication-success-handler and I need to access few values which was part of initial request data being posted to Spring security.
I am posting following information when user trying to do login
j_username
j_password
storeCode
Spring security is able to authenticate user successfully and is calling "authentication-success-handler".
public class WebshopAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler
{
public WebshopAuthenticationSuccessHandler() {
}
#Override
public void onAuthenticationSuccess(final HttpServletRequest request,
final HttpServletResponse response, final Authentication authentication)
throws IOException, ServletException {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
request.getAttribute( "storeCode" );
attr.getRequest().getAttribute( "storeCode" );
}
}
But in all way, I am not able to get value of storeCode and its coming as null.
Not sure what I am doing wrong.
I am assuming that Spring is creating a new instance of Request and response while calling onAuthenticationSuccess, but how can I pass/ retrieve values which passed passed from the login page?
If the data is from an HTTP POST request, you should be using getParameter, not getAttribute. Attributes are server-side state only, not submitted by the client.

Spring Security: How to clear `remember me` cookie programmatically?

I'm using logout method in web-app like below, but if i check remember me logout doesn't work, because cookie isn't cleared. How to clear programmatically this cookie in my method (or how to make better logout method) ?
public void logout() {
AnonymousAuthenticationToken anonymous = new AnonymousAuthenticationToken("anonymous", "anonymous", new ArrayList(Arrays.asList(new GrantedAuthorityImpl("ROLE_ANONYMOUS"))));
SecurityContextHolder.getContext().setAuthentication(anonymous);
}
If you are using the standard Spring Security cookie name (which is SPRING_SECURITY_REMEMBER_ME_COOKIE), you can do this:
void cancelCookie(HttpServletRequest request, HttpServletResponse response)
{
String cookieName = "SPRING_SECURITY_REMEMBER_ME_COOKIE";
Cookie cookie = new Cookie(cookieName, null);
cookie.setMaxAge(0);
cookie.setPath(StringUtils.hasLength(request.getContextPath()) ? request.getContextPath() : "/");
response.addCookie(cookie);
}
You'll have to change the cookieName value if you are using a custom cookie name.
The AbstractRememberMeServices class has an implementation of LogoutHandler.logout which cancels the cookie. Inject the LogoutHandler and call this method.

Resources