How to set attributes with easymock - session

I need to mock a request with a session, the session has an attribute (project)
HttpServletRequest req = createNiceMock(HttpServletRequest.class);
HttpSession session = createNiceMock(HttpSession.class);
session.setAttribute("currentProject", project); // project is not null here
expect(req.getSession()).andReturn(session);
replay(req);
But the test produces a NullPointerException in the following code
HttpSession session = request.getSession();
Project p = (Project) session.getAttribute("currentProject");
Project p is null after that line. Why??

You are not recording anything for getAttribute. session is a mock. So you can't set anything to it. You need to record calls.
A working version of this code would be
HttpServletRequest req = createNiceMock(HttpServletRequest.class);
HttpSession session = createNiceMock(HttpSession.class);
expect(session.getAttribute("currentProject").andReturn(project);
expect(req.getSession()).andReturn(session);
replay(req, session);
Note that I recommend using spring-test instead of EasyMock if you are in a Spring context. It makes testing much simpler. See my answer to another question.

Related

Spring Security 6.0 CsrfToken behavior change

I tested Spring Security as part of my Spring Boot Setup in version 6.0-M5, 6.0-RC1 and 6.0-RC2. I recognized a behavior change and wanted to ask whether this may be a bug. I return the CSRF token as a serialized JSON, but since RC1 the content of the token in the JSON is garbage.
My working code in Spring Boot 6 Milestone 5 still working as expected.
#RestController
public class CsrfController {
#GetMapping("/rest/user/csrf")
public CsrfToken csrf(CsrfToken token) {
return token;
}
}
In my use case I query the controller using a unit test.
#LocalServerPort
int serverPort;
#Autowired
private TestRestTemplate webclient;
#Test
public void getCsrf() {
ResponseEntity<String> entity = webclient.getForEntity("http://localhost:" + serverPort +
"/rest/user/csrf", String.class);
// ... here some code to get the token from the JSON body ...
assertTrue(result.matches("^[a-f0-9\\-]+$"));
This is the first query of the server. A session object between client and server is not established in past queries. This worked in M5 but stopped working in Spring Boot 6 RC1 and RC2
The following controller code made it work again in RC2:
#GetMapping("/rest/user/csrf")
public CsrfToken csrf(HttpServletRequest request, HttpServletResponse response) {
CsrfToken repoToken = tokenRepo.loadToken(request);
if (repoToken != null) {
return repoToken;
}
// required because it is required but ay not be initialized by the tokenRepo
request.getSession();
repoToken = tokenRepo.generateToken(request);
tokenRepo.saveToken(repoToken, request, response);
return repoToken;
}
If I tried the old code in RC2, I received on client side a malformed string. I did not receive a UUID styled token in my JSON serialized response body. I think it is related to the uninitialized session object.
Is this a bug or is an uninitialized session and a resulting not working CrsfToken specified behavior?
I think the issue is in the way I try to get and use the XSFR token.
Because I want to use an Angular frontend, I configured my token repository to provide the tokens via Cookie.
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
This produces cookies the old UUID style. However the authentication expects the new tokens as generated by https://github.com/spring-projects/spring-security/issues/11960 . Probably the cookie mechanism still needs to be migrated until final Spring Boot 3.0.

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

Broken HttpSession integrity in distributed WAS7 environment

The project is running on WAS7 with load balancing environment, using JSF2.1 and primefaces4. After authentication filter the userinfo is to put it into session. when viewing the page backend bean is getting the user info from session using facescontext like below, bean scope is viewscoped
FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest) context
.getExternalContext().getRequest();
HttpSession session = request.getSession(false);
UserInfo userInfo= null;
if(session != null) {
userInfo= (UserProfile) session
.getAttribute("UserInfo");
// do some logic
}
Its working fine as expected. when more number user (concurrently) accessing the page or doing same operation in the page at the same time, some times session gives wrong object i.e. other users info object not mine. Based on the user info the all business logic is getting executed, so the page shows wrong info.
Any idea why is this happening, and a solution to prevent this if any ?

How to successfully invalidate a session when logging out in Spring MVC

I have tried several times to invalidate the session. I have used the following code to remove the items from the session and invalidating the session itself.
public String logout(HttpServletRequest request) {
HttpSession session = request.getSession();
if(session != null) {
session.removeAttribute("user");
}
session.invalidate();
return "redirect:/";
}
Somehow, when another user logs in, the previous user's details are loaded onto the page. I didn't use Spring Security not wanting to complicate things. I'm not sure how to handle this problem.
my suggestion is to set the userDetails object in sessionObject using #sessionAttribute() in contrller. While doing logOut operation, set the userDetails object to null. In Jsp check if userDetails is null or not using jstl. This is really simple and will fit for your requirement.

Resources