Spring security's SecurityContextHolder: session or request bound?
It depends on how you configured it (or lets say, you can configure a different behaviour).
In a Web application you will use the ThreadLocalSecurityContextHolderStrategy
which interacts with SecurityContextPersistenceFilter
.
The Java Doc of SecurityContextPersistenceFilter
starts with:
Populates the {@link
SecurityContextHolder} with
information obtained from the
configured {@link
SecurityContextRepository} prior to
the request and stores it back in the
repository once the request has
completed and clearing the context
holder. By default it uses an {@link
HttpSessionSecurityContextRepository}.
See this class for information
HttpSession related
configuration options.
Btw: HttpSessionSecurityContextRepository is the only implementation of SecurityContextRepository (I have found in the default libs)
It works like this:
- The
HttpSessionSecurityContextRepository
uses the httpSession (Key="SPRING_SECURITY_CONTEXT") to store anSecurityContext
Object. - The
SecurityContextPersistenceFilter
is an filter that uses anSecurityContextRepository
for example theHttpSessionSecurityContextRepository
to load and storeSecurityContext
Objects. If an HttpRequest passes the filter, the filter get theSecurityContext
from the repository and put it in the SecurityContextHolder (SecurityContextHolder#setContext
) - The
SecurityContextHolder
has two methodssetContext
andgetContext
. Both uses aSecurityContextHolderStrategy
to specify what exactly is done in the set- and get-Context methods. - For example theThreadLocalSecurityContextHolderStrategy
uses a thread local to store the context.
So in summary: The user principal (element of SecurityContext) is stored in the HTTP Session. And for each request it is put in a thread local from where you access it.
How do I get the Session Object in Spring?
Your friend here is org.springframework.web.context.request.RequestContextHolder
// example usage
public static HttpSession session() {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
return attr.getRequest().getSession(true); // true == allow create
}
This will be populated by the standard spring mvc dispatch servlet, but if you are using a different web framework you have add org.springframework.web.filter.RequestContextFilter
as a filter in your web.xml
to manage the holder.
EDIT: just as a side issue what are you actually trying to do, I'm not sure you should need access to the HttpSession
in the retieveUser
method of a UserDetailsService
. Spring security will put the UserDetails object in the session for you any how. It can be retrieved by accessing the SecurityContextHolder
:
public static UserDetails currentUserDetails(){
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null) {
Object principal = authentication.getPrincipal();
return principal instanceof UserDetails ? (UserDetails) principal : null;
}
return null;
}
Is UserDetails or principal really stored in Session?
Yes, each execution thread has an associated SecurityContext context. You can retrieve the authentication / user details by using SecurityContextHolder like this:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Here's a good tutorial about this: http://www.baeldung.com/get-user-in-spring-security
Can SecurityContextHolder Shared across users
By default, the implementation of SecurityContextHolder
is bound to an instance of ThreadLocal
:
- It is scalable as much as your container provides simultaneous threads to Spring Security.
- The abstraction is not an implementation of
HttpSession
; however, it provides integration with the underlying HTTP Servlet provider to transfer HTTP security information to the application layer managed by Spring. - The composition over
ThreadLocal
enables the user of API to be able take advantage of working with multiple views of data in different threads. That's also why if you have proper thread configurations in your HTTP container, you should not have a scalability issue in terms of number of users.
Put user in HttpSession with Spring Security default login and authenticate
Assuming you wanted to add user to session on seccessful login, You can create the AuthenticationSuccessHandler
like below and register using successHandler(new AuthenticationSuccessHandlerImpl())
Update:
If we create the object AuthenticationSuccessHandlerImpl
, it will not be spring mananged and hence autowire
into your Securityconfig
and use it like shown below.
Here autowire the AuthenticationSuccessHandler
in your WebSecurityConfig
@Autowired
AuthenticationSuccessHandler authenticationSuccessHandler;
and use it
WebSecurityConfig.java
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**", "/registration").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll().successHandler(authenticationSuccessHandler) // See here
.and()
.logout()
.permitAll();
}
The AuthenticationSuccessHandlerImpl.java
import java.io.IOException;
import java.security.Principal;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import com.techdisqus.auth.repository.UserRepository;
@Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler{
@Autowired HttpSession session; //autowiring session
@Autowired UserRepository repository; //autowire the user repo
private static final Logger logger = LoggerFactory.getLogger(AuthenticationSuccessHandlerImpl.class);
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// TODO Auto-generated method stub
String userName = "";
if(authentication.getPrincipal() instanceof Principal) {
userName = ((Principal)authentication.getPrincipal()).getName();
}else {
userName = ((User)authentication.getPrincipal()).getUsername();
}
logger.info("userName: " + userName);
//HttpSession session = request.getSession();
session.setAttribute("userId", userName);
}
}
Hope this helps.
Is SecurityContextHolder necessary
SecurityContextHolder
holds the authentication information in a ThreadLocal
context (by default). It is a fundamental helper class to Spring Security, which provides access to the security context. You (and the framework) can access to the authentication in every methods run in the same thread. It is necessery even if you protect urls or use method security, but sometimes you need user information in business logic too and you can get it from there.
So the answer is yes, it is always needed, if you use spring security.
Related Topics
How to Listen for Key Presses (Within Java Swing) Across All Components
How to Clear Permgen Space Error in Tomcat
What Does Maven Update Project Do in Eclipse
What Are the Date Formats Available in Simpledateformat Class
Postgresql Uuid Supported by Hibernate
Why Does Java Allow Arrays of Size 0
Java String Concatenation with + Operator
How to Read Excel Cell Having Date with Apache Poi
How to Append a Node to an Existing Xml File in Java
Java 8: Mandatory Checked Exceptions Handling in Lambda Expressions. Why Mandatory, Not Optional
Spring Boot Actuator Without Spring Boot
Create Simple Pojo Classes (Bytecode) at Runtime (Dynamically)