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.
Related
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
I want to implement this example using Keyclock server with Spring Security 5.
I'm going to use OAuth2.0 authentication with JWT token. I'm interested how I can get the current logged in user into the Rest Endpoint?
I have configured Spring Security not to store user sessions using http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);.
One possible way is to use this code:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
But I don't know is it going to work. Can someone give some advice for that case?
SecurityContextHolder, SecurityContext and Authentication Objects
By default, the SecurityContextHolder uses a ThreadLocal to store these details, which means that the security context is always available to methods in the same thread of execution. Using a ThreadLocal in this way is quite safe if care is taken to clear the thread after the present principal’s request is processed. Of course, Spring Security takes care of this for you automatically so there is no need to worry about it.
SessionManagementConfigurer consist of isStateless() method which return true for stateless policy. Based on that http set the shared object with NullSecurityContextRepository and for request cache NullRequestCache. Hence no value will be available within HttpSessionSecurityContextRepository. So there might not be issue with invalid/wrong details for user with static method
Code:
if (stateless) {
http.setSharedObject(SecurityContextRepository.class,
new NullSecurityContextRepository());
}
if (stateless) {
http.setSharedObject(RequestCache.class, new NullRequestCache());
}
Code:
Method to get user details
public static Optional<String> getCurrentUserLogin() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(extractPrincipal(securityContext.getAuthentication()));
}
private static String extractPrincipal(Authentication authentication) {
if (authentication == null) {
return null;
} else if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
return springSecurityUser.getUsername();
} else if (authentication.getPrincipal() instanceof String) {
return (String) authentication.getPrincipal();
}
return null;
}
public static Optional<Authentication> getAuthenticatedCurrentUser() {
log.debug("Request to get authentication for current user");
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(securityContext.getAuthentication());
}
sessionManagement
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
You might like to explore Methods with Spring Security to get current user details with SessionCreationPolicy.STATELESS
After the service validate the token, you can parse it, and put it into the securitycontext, it can contains various data, so you have to look after it what you need. For example, subject contains username etc...
SecurityContextHolder.getContext().setAuthentication(userAuthenticationObject);
The SecurityContextHolder's context maintain a ThreadLocal entry, so you can access it on the same thread as you write it in the question.
Note that if you use reactive (webflux) methodology, then you have to put it into the reactive context instead.
in spring security:
i think with tow way logout called: when a session timeout occurred or a user logout itself...
anyway in these ways , destroyedSession called in HttpSessionEventPublisher and SessionRegistry remove SessionInformation from sessionIds list...
when i use below method for force logout specific user , this method just "expired" SessionInformation in SessionRegistry. now when i get all online user "getAllPrincipals()" from SessionRegistry, the user that session expired, is in the list!
#Override
public boolean forceLogOut(int userId){
for (Object username: sessionRegistry.getAllPrincipals()) {
User temp = (User) username;
if(temp.getId().equals(userId)){
for (SessionInformation session : sessionRegistry.getAllSessions(username, false)) {
session.expireNow();
}
}
}
return true;
}
how can i logout 'specific user' or 'sessionId' that session object remove from "Web Server" and "Session Registry" ?
i googling and found HttpSessionContext in Servlet API that can get HttpSession from specific sessionId. and then invalidate session. but i think this method is not completely useful!
(note. this class is deprecated!)
what is the best way? Whether I'm wrong?
Try like this:
#RequestMapping(value="/logout", method = RequestMethod.GET)
public String logoutPage (HttpServletRequest request, HttpServletResponse response){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null){
//new SecurityContextLogoutHandler().logout(request, response, auth);
persistentTokenBasedRememberMeServices.logout(request, response, auth);
SecurityContextHolder.getContext().setAuthentication(null);
}
return "redirect:/login?logout";
}
To logout specific session Id check that link:
how to log a user out programmatically using spring security
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 ?
I have a custom field along with "j_username" and "j_password" on my login.jsp, that I need to authenticate the user. I am using a CustomUsernamePasswordAuthenticationFilter to access the custom field as follows.
public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
String myCustomField= request.getParameter("myCustomField");
request.getSession().setAttribute("CUSTOM_FIELD", myCustomField);
return super.attemptAuthentication(request, response);
}
}
I tried accessing the session in loadByUsername method of UserDetailsService class but I get an error. Here is the code for my custom UserDetailsService.
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException, DataAccessException {
ServletRequestAttributes attr = (ServletRequestAttributes)RequestContextHolder.currentRequestAttributes();
HttpSession session = attr.getRequest().getSession();
User userObject = dbObject.retrieveUser(userName,myCustomParameter)
// code here to retrieve my user from the DB using the userName and myCustomParameter that was retrieved from login.jsp and put in the session. Get the custom parameter from the session here.
if (userObject == null)
throw new UsernameNotFoundException("user not found");
return new AuthenticationUserDetails(userObject);
}
Is there any way where I can access this custom parameter for authentication? Sending it through the session doesn't seem to be working.
Wouldn't the session be created AFTER the authentication takes place. So a new authenticated session might be created after your call to attemptAuthentication
Here's the spring doc on the Abstract class you're implementing
http://static.springsource.org/spring-security/site/docs/3.0.x/apidocs/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html#successfulAuthentication%28javax.servlet.http.HttpServletRequest,%20javax.servlet.http.HttpServletResponse,%20org.springframework.security.core.Authentication%29
You might be losing the session attribute by the time loadByUsername is called.
I ran into the exact problem.
The problem appeared to be that the RequestAttributes was not bound to the current thread. To make it work, I had to explicitly bind it to the current thread.
In CustomUsernamePasswordAuthenticationFilter, after the statement
request.getSession().setAttribute("CUSTOM_FIELD", myCustomField);
Add:
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
This worked for for me.