How to customize the behavior of session scoped bean by current user is Spring MVC - spring

Consider following scenario: Spring Security authenticates login data against custom UserDetailsServiceimplementation as follows
#Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
UserProfile profile = users.find(name);
if (profile.getUsername().equals("admin"))
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
return new User(profile.getUsername(), profile.getPassword(), authorities);
}
If authentication succeeds, I want to create unique session scoped service in controller, with customized behavior by valid UserProfile object state. I guess best way to do that is to declare the session bean manually in configuration file and somehow autowire UserProfile or session owner to it's constructor, but how that's possible, when UserProfile is not even an managed object?
In this case, I want server to create service for authenticated user, which maintains SSH connection to remote host with credentials stored in UserProfile
Also, how to restrict a creation of such service just to post login? Is there way to achieve this kind of behavior, or is it actually bad architecture?

You can use the SecurityContextHolder to access the authenticated user for the current request. I think the best approach is to create a singleton Service with a method like this:
public UserDetails getCurrentUser() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
return (UserDetails) principal;
} else {
//handle not authenticated users
return null;
}
}
Now you can autowire and use the service in your controllers.

Related

Spring boot websocket: how to get the current principal programmatically?

By this thread I know that I can access to the principal by passing it as an argument to the method.
Nevetheless I need to access to this information in a transparent way, I tried with:
SecurityContextHolder.getContext().getAuthentication()
But it gives me null. So, isn't there another way?
It seems that, in order to obtain the full reference I have to define a custom channel interceptor:
private static class MyReceiver implements ChannelInterceptor{
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
SimpMessageType type = getType(message);
if(type == SimpMessageType.SUBSCRIBE) {
message.getHeaders().get("simpUser")); //it works here
}
return ChannelInterceptor.super.preSend(message, channel);
}
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
This will give you the current logged-in Username in Spring Security
Note :
UserDetails object is the one that Spring Security uses to keep user-related information.
SecurityContext is used to store the details of the currently authenticated user and SecurityContextHolder is a helper class that provides access to the security context

Get current logged in user from Spring when SessionCreationPolicy.STATELESS is used

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.

Spring Security force logout when password change

I have 2 user role ADMIN and USER. ADMIN can change USER's password. I want to force logout when user's password changed by ADMIN. I can save changed password and use them when next login. But I want to force logout to them.
UserDetails userDetails = userDetailsService.loadUserByUsername(vendor.getUsername());
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setAuthenticated(false);
This is not working.
First you need to configure the session configuration under your security configuration as follows
#Override
protected void configure(HttpSecurity http) throws Exception {
// this enables ConcurrentSessionFilter to allow us to read all sessions by using getAllPrincipals
http
.sessionManagement().maximumSessions(10)
.sessionRegistry(sessionRegistry())
.expiredUrl("/login?expire");
// Rest of the configuration
}
This enables you to call sessionRegistry.getAllSessions to manage the list of active session and expire them. SessionRegistry is autowired FYI.
List<Object> principals = sessionRegistry.getAllPrincipals();
for (Object principal: principals) {
// Check for the principal you want to expire here
List<SessionInformation> sessionInformations = sessionRegistry.getAllSessions(principal);
for (SessionInformation sessionInformation : sessionInformations) {
sessionInformation.expireNow();;
}
}

How to maintain the authentication within SecurityContext after application restart?

Other than using J2EE preauthentication, I put authentication management in a custom pojo class:
public boolean grantAuthentication(UserRole role) {
List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
if(role.equals(UserRole.ROLE_ADMIN)){
authorities.add(new SimpleGrantedAuthority(UserRole.ROLE_ADMIN.toString()));
}
authorities.add(new SimpleGrantedAuthority(UserRole.ROLE_USER.toString()));
Authentication authentication = new UsernamePasswordAuthenticationToken(user, user.getPassword(),
authorities);
user.setAuthorities(authorities);
// here the authentication inject into SecurityContext
SecurityContextHolder.getContext().setAuthentication(authentication);
return true;
}
Above class is within the controller class that handling user login form from the login page.
But if user gets in the middle of something, like /order/view and then restart the application, and refresh the page, the there is an exception complaining about:
java.lang.NullPointerException: Cannot obtain authentication object in security context at this time
Cannot obtain authentication object in security context at this time
So is there anyway to maintain the authentication object in the session or something, or do i have to redirect user back to the login page? if so, then how can I put user back to the login page?
If you add spring session management it will be handled automatically.Add session management in springsecurity.xml as like this,
<security:session-management
invalid-session-url="/jsp/general/sessionTimeout.html" />

How to access a custom parameter from the login page in spring security?

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.

Resources