Set timeout for a session at user login and logout user at the end of session timeout - spring

I try to make a mechanism in Spring that when the user login on the website, I assign for his login session a time of validity. For example, I want to set the session to be active for just 10 minutes and after 10 minutes of inactivity I want to logout the user.
My first implementation is like that:
#Service
public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
request.getSession().setMaxInactiveInterval(60*10); // active session for 10 minutes
super.onAuthenticationSuccess(request, response, authentication);
}
}
But doesn't work expected.
So, I tried another way like that:
public class SessionListener implements HttpSessionListener {
#Override
public void sessionCreated(HttpSessionEvent event) {
event.getSession().setMaxInactiveInterval(60*10); // active session for 10 minutes
}
#Override
public void sessionDestroyed(HttpSessionEvent event) {
event.getSession().invalidate();
}
}
Here, I noticed the event entry in sessionDestroyed function, but invalidate function doesn't work as I would expect, so the user can still navigate on the website.
Have everyone any idea about what is my wrong in my configuration or how I can resolve preferably without add config in web.xml. I saw many responses with add session-timeout in web.xml, but I want to make this configuration from Spring configuration classes. And of course, I'm newbie with Spring and Spring Security.

Related

Spring Security access any user Authentication object

I'm working on the SpringBoot stateful application. For the administration purpose, I need to be able to access any user session and modify the attributes there.
Right now, I know how to successfully access the current (my) user Authentication object:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
OAuth2User principal = (OAuth2User) authentication.getPrincipal();
but how to do the same for any other user?
Is it possible to find the session by username or something similar? I'd really appreciate the example.
There is no built-in mechanism to do something like what you want, but you can write a custom HttpSessionListener that would save references to active sessions, remove them upon expiration and also expose some methods to manipulate session attributes. You would also probably want to associate some user id with the sessions that you can use to perform user lookup, so registering an AuthenticationSuccessHandler to do that would also be needed.
Your logged in users' manager would look something like this:
#Service
public class LoggedInUsersManagerService implements HttpSessionListener {
// assuming you have some session storage here,
// can be something as simple as just a map
public void sessionCreated(HttpSessionEvent se) {
final HttpSession session = se.getSession();
sessionStore.put(session.getAttribute(USER_ID_ATTRIBUTE), session);
}
public void sessionDestroyed(HttpSessionEvent se) {
final HttpSession session = se.getSession();
sessionStore.remove(session.getAttribute(USER_ID_ATTRIBUTE));
}
public void doSomethingWithUserSession(UserIdType id) {
final HttpSession session = sessionStore.get(id);
if(session != null) {
//do what you need with the session attributes
}
}
}
and your SuccessHandler would look like
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
HttpSession session = request.getSession();
session.setAttribute(USER_ID_ATTRIBUTE, getUserIdFromAUthentication(authentication));
//maybe do something else as well
}
}
You can register the success handler in your spring security configuration, for example like this
http
.oauth2login()
.successHandler(myAuthenticationSuccessHandler)
Keep in mind that manipulating session data while the user is still using your service is not really a good idea, so I wouldn't recommend doing something like this unless you are absolutely required to.

Change URL of application according to logged in user in Spring boot

I have been working a spring boot project for a while now. I am accessing my app's login with local url like localhost:8080/XYZapp/login then after a successful login my url changes to localhost:8080/XYZapp/authenticated (which is my dashboard).
All this working fine but now I want my URL to change dynamically acc. to logged in user like localhost:8080/XYZapp/abcCompany/authenticated. This abcCompany should change every time according to loggedin user.
I have googled it on some references and on SO but could not found much luck.
There are a couple of things that you will need to do.
The first has already been mentioned in the comments, which is to use an AuthenticationSuccessHandler:
#Component
public TenantAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) {
if (authentication.getPrincipal() instanceof MyUser) {
MyUser user = (MyUser) authentication.getPrincipal();
String uri = "/XYZapp/" + user.getTenantName() + "/authenticated";
AuthenticationSuccessHandler handler =
new SimpleUrlAuthenticationSuccessHandler(uri);
handler.onAuthenticationSuccess(handler);
}
// throw exception
}
}
Which you can then wire in your DSL:
#Autowire
AuthenticationSuccessHandler tenantAuthenticationSuccessHandler;
// ...
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.successHandler(this.tenantAuthenticationSuccessHandler);
Of course, you might consider caching the SimpleUrlAuthenticationSuccessHandler instances so you aren't creating one for every single login.
Second, you'll likely need to be able to know the name of the tenant (e.g. abcCompany) based on the user that gets looked up.
So, you'll likely need a custom UserDetailsService so that you can retrieve that extra information from your datastore:
public class MyUser {
String tenantName;
// ...
}
// ...
#Component
public class MyUserUserDetailsService implements UserDetailsService {
public UserDetails findUserByUsername(String username) {
// query some backing store, Spring Data repository, etc.
return myUser;
}
private static class MyUserUserDetails extends MyUser implements UserDetails {
// ...
}
}

Spring security shares context between multiple threads, how can I avoid this?

I have been working on a spring-based service, using JWT for authentication.
The service handling the user requests calls an authorization service in a filter which sets up the security context and looks pretty much like this :
#Component
public class TokenAuthenticationFilter extends OncePerRequestFilter {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#Autowired
private AuthorizationServiceClient authorizationServiceClient;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
User user = authorizationServiceClient.requestUserFromToken(request.getHeader("X-Auth-Token"));
SecurityContextHolder.getContext().setAuthentication(new UserAuthentication(user));
filterChain.doFilter(request, response);
SecurityContextHolder.getContext().setAuthentication(null);
} catch (HttpClientErrorException e) {
response.sendError(e.getStatusCode().value());
}
}
}
The AuthorizationServiceClient calls a remote service which handles the validation of the user's role and credentials.
I have been facing a very strange behavior :
When a page on my UI was making multiple request simultaneously, I end up getting a 500, caused by a NullPointerException.
The root cause is the Principal (containing the identity of the user) being null, when it shouldn't have.
After a painful investigation, I ended figuring that the SecurityContextHolder, even though it was using a ThreadLocal, was using sessions, and then would be shared between the threads.
The SecurityContextHolder.getContext().setAuthentication(null); was erasing the value used in concurrent threads when some requests were made in the same session, and was leading to the NPE.
So, if like me, you'd like to prevent the use of the sessions, you need to set up the security using :
http.
[...]
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).
[...]

Add Maintenance Mode to Spring (Security) app

I'm looking for a way to implement a Maintenance Mode in my Spring app.
While the app is in Maintenance Mode only users role = MAINTENANCE should be allowed to log in. Everyone else gets redirected to login page.
Right now I just built a Filter:
#Component
public class MaintenanceFilter extends GenericFilterBean {
#Autowired SettingStore settings;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if(settingStore.get(MaintenanceMode.KEY).isEnabled()) {
HttpServletResponse res = (HttpServletResponse) response;
res.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
} else {
chain.doFilter(request, response);
}
}
}
And added it using:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// omitted other stuff
.addFilterAfter(maintenanceFilter, SwitchUserFilter.class);
}
Because as far as I figured out SwitchUserFilter should be the last filter in Spring Security's filter chain.
Now every request gets canceled with a 503 response. Though there's no way to access the login page.
If I add a redirect to the Filter, this will result in an infinite loop, because access to login page is also denied.
Additionally I can't figure out a nice way to get the current users roles. Or should I just go with SecurityContextHolder ?
I'm looking for a way to redirect every user to the login page (maybe with a query param ?maintenance=true) and every user with role = MAINTENANCE can use the application.
So the Filter / Interceptor should behave like:
if(maintenance.isEnabled()) {
if(currentUser.hasRole(MAINTENANCE)) {
// this filter does nothing
} else {
redirectTo(loginPage?maintenance=true);
}
}
I now found two similar solutions which work, but the place where I inject the code doesn't look that nice.
For both I add a custom RequestMatcher, which get's #Autowired and checks if Maintenance Mode is enabled or not.
Solution 1:
#Component
public class MaintenanceRequestMatcher implements RequestMatcher {
#Autowired SettingStore settingStore;
#Override
public boolean matches(HttpServletRequest request) {
return settingStore.get(MaintenanceMode.KEY).isEnabled()
}
}
And in my Security Config:
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired MaintenanceRequestMatcher maintenanceRequestMatcher;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.requestMatchers(maintenanceRequestMatcher).hasAuthority("MY_ROLE")
.anyRequest().authenticated()
// ...
}
Solution 2:
Very similar, but uses HttpServletRequest.isUserInRole(...):
#Component
public class MaintenanceRequestMatcher implements RequestMatcher {
#Autowired SettingStore settingStore;
#Override
public boolean matches(HttpServletRequest request) {
return settingStore.get(MaintenanceMode.KEY).isEnabled() && !request.isUserInRole("MY_ROLE");
}
}
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired MaintenanceRequestMatcher maintenanceRequestMatcher;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.requestMatchers(maintenanceRequestMatcher).denyAll()
.anyRequest().authenticated()
// ...
}
This will perform a denyAll() if Maintenance Mode is enabled and the current user does not have MY_ROLE.
The only disadvantage is, that I cannot set a custom response. I'd prefer to return a 503 Service Unavailable. Maybe someone can figure out how to do this.
It's kinda of a chicken or egg dilemma, you want to show unauthorized users a "we're in maintenance mode ..." message, while allow authorized users to login, but you don't know if they are authorized until they log in. Ideally it would be nice to have this in some sort of filter, but I found in practice it was easier for me to solve a similar issue by putting the logic after login, like in the UserDetailsService.
Here's how I solved it on a project. When I'm in maintenance mode, I set a flag for the view to show the "we're in maintenance mode .." message, in a global header or on the login page. So users, regardless of who they are know it's maintenance mode. Login should work as normal.
After user is authenticated, and in my custom UserDetailsService, where their user details are loaded with their roles, I do the following:
// if we're in maintenance mode and does not have correct role
if(maintenance.isEnabled() && !loadedUser.hasRole(MAINTENANCE)) {
throw new UnauthorizedException(..)
}
// else continue as normal
It's not pretty, but it was simple to understand (which I think is good for security configuration stuff) and it works.
Update:
With you solution I'd have to destroy everyone's session, else a user
which was logged in before maintenance mode was enabled, is still able
to work with the system
On our project we don't allow any users to be logged in while in maintenance mode. An admin, kicks off a task which enables "maintenance..." msg, with a count down, then at the end, we expire everyone's session using SessionRegistry.
I was a similar situation and found this answer is helpful. I followed the second approach and also managed to return custom response.
Here is what I have done to return the custom response.
1- Define a controller method that returns the needed custom response.
#RestController
public class CustomAccessDeniedController {
#GetMapping("/access-denied")
public String getAccessDeniedResponse() {
return "my-access-denied-page";
}
}
2- In your security context, you should permit this URL to be accessible.
http.authorizeRequests().antMatchers("/access-denied").permitAll()
3- Create a custom access denied exception handler
#Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
#Autowired
private SettingStore settingStore;
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
if(settingStore.get(MaintenanceMode.KEY).isEnabled()) {
response.sendRedirect(request.getContextPath() + "/access-denied");
}
}
}
4- Register the custom access denier exception handler in the security config
#Autowired
private CustomAccessDeniedHandler accessDeniedHandler;
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);

Spring - Best way to control session creation

I am currently retrieving the session timeout from a database since it should be configurable so I just can't declare it in the web.xml.
In my HttpSessionEventPublisher, I basically retrieve the session object from the HttpSessionEvent and I set the session timeout value that I've retrieved from the database using setMaxInactiveInterval.
Upon investigation, whenever I access a POST url in my site, the HttpSessionEventPublisher is triggered and it creates a new Session object. I would like to control this behavior by only creating a Session object if and only if the user is successfully authenticated (logged in, passing through the AuthenticationProvider)
Is this possible?
The HttpSessionEventPublisher does not create sessions itself. It just translates servlet session events to the equivalent ones of spring security. Actually the creation of sessions is not controlled by spring security, but it can initiate one if needed.
If you just want to set the session timeout only upon authentication, then you may extend the authentication handler you use and set the timeout there.
For example the following code extends SavedRequestAwareAuthenticationSuccessHandler and retrieves the timeout from application properties (instead of database as in your case)
#Component
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
#Value("#{appProperties['session.timeout']}")
private int sessionTimeout;
private final Logger logger = LoggerFactory.getLogger(AuthenticationSuccessHandler.class);
#Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse res, Authentication authentication) throws ServletException, IOException {
logger.debug("onAuthenticationSuccess");
HttpSession session = req.getSession();
session.setMaxInactiveInterval(sessionTimeout);
super.onAuthenticationSuccess(req, res, authentication);
}
}

Resources