How to remove existing sessions by specific principal name - spring

I'm using Spring Session 1.3.0 with Redis backend in my project.
I have an use case that the super admin might update the roles of existing user who might already logged in. I want to delete the existing session records for those users after changing their roles.
Is there API of Spring Session to archive it?

#Autowired
private SessionRegistry sessionRegistry;
public void expireUserSessions(String username) {
for (Object principal : sessionRegistry.getAllPrincipals()) {
if (principal instanceof User) {
UserDetails userDetails = (UserDetails) principal;
if (userDetails.getUsername().equals(username)) {
for (SessionInformation information : sessionRegistry.getAllSessions(userDetails, true)) {
information.expireNow();
}
}
}
}
}

Also work out another way to clean sessions of specific user,
#Autowired
FindByIndexNameSessionRepository sessionRepository;
sessionRepository.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
username).keySet().forEach(session -> sessionRepository.delete((String) session));

Related

JPA AuditorAware not getting created for each user

I have implemented auditing using JPA auditing. My code looks like this:
#Configuration
#EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class JpaConfiguration {
#Bean
#Scope(value= ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public AuditorAware<String> auditorAware() {
final String currentUser;
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(null != authentication) {
currentUser = authentication.getName();
} else {
currentUser = null;
}
return () -> Optional.ofNullable(currentUser);
}
}
The issue I am facing is if I login with one user and perform some operation, it's working fine. But when I logout and login with another user, It's still using the last user only.
After debugging the code what I found is spring not creating bean of AuditorAware for each user. It's behaving like singleton bean. Even I specify the scope as prototype also, still it's behaving like singleton.
The AuditorAware is supposed to be a singleton. You should retrieve the current user, each time the AuditAware.getCurrentAuditor is called. Not just once.
Rewrite your code to something like this.
#Bean
public AuditorAware<String> auditorAware() {
return () -> getCurrentAuthentication().map(Authentication::getName());
}
private Optional<Authentication> getCurrentAuthentication() {
return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication());
}

Spring security jdbcAuthentication does not work with default roles processing

Using
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("dba").password("root123").roles("ADMIN","DBA");
my example works fine. For example for
http.authorizeRequests()
// ...
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.and().formLogin()
.and().exceptionHandling().accessDeniedPage("/Access_Denied");
If I have changed inMemoryAuthentication to spring jdbc default - i got an role issue than.
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource);
I sure I configured db and schema using spring recommendations (to be able to use default jdbc authentication).
In debug mode I can see result of loading from db in the
org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl
#loadUserByUsername(username)[line 208]
return createUserDetails(username, user, dbAuths);
It returns similar result with in memory configuration:
org.springframework.security.core.userdetails.User#183a3:
Username: dba;
Password: [PROTECTED];
Enabled: true;
AccountNonExpired: true;
credentialsNonExpired: true;
AccountNonLocked: true;
Granted Authorities: ADMIN,DBA
As you can see it loads correspond Granted Authorities, but http request redirects me to .accessDeniedPage("/Access_Denied"). I confused because It should work for user like time before.
I do not use spring boot in my project.
My logs does not contain any configuration of jdbc errors.
I have spend a lot of time to investigate details and my ideas have just finished.
Do you think I need add to build some cache libraries or something else?
There are 2 gotchas in play here.
The first is that when using hasRole('ADMIN') that first a check is done if it starts with the role prefix (for which the default is ROLE_) if not the passed in role is prefix with it (see also the reference guide). So in this case the actual authority checked is ROLE_ADMIN and not ADMIN as you expect/assume.
The second is that when using the in memory option the roles method does the same as mentioned here. It checks if the passed in roles start with the role prefix and if not adds it. So in your sample with the in memory one you end up with authorities ROLE_ADMIN and ROLE_DBA.
However in your JDBC option you have authorities ADMIN and DBA and hence the hasRole('ADMIN') check fails because ROLE_ADMIN isn't equal to ADMIN.
To fix you have several options.
Instead of hasRole use hasAuthority the latter doesn't add the role prefix and for the in memory option use authorities instead of roles.
In the JDBC option prefix the authorities in the database with ROLE_
Set the default role prefix to empty.
Using hasAuthority
First change the configuration of the in memory database to use authorities instead of roles.
auth.inMemoryAuthentication()
.withUser("dba").password("root123")
.authorities("ADMIN","DBA");
next change your expressions as well
.antMatchers("/db/**").access("hasAuthority('ADMIN') and hasAuthority('DBA')")
Prefix with ROLE_
In the script that inserts the authorities prefix the authorities with ROLE_.
Remove the default role prefix
This is a bit tricky and is extensivly described in [the migration guide].
There is no easy configuration option and requires a BeanPostProcessor.
public class DefaultRolesPrefixPostProcessor implements BeanPostProcessor, PriorityOrdered {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
// remove this if you are not using JSR-250
if(bean instanceof Jsr250MethodSecurityMetadataSource) {
((Jsr250MethodSecurityMetadataSource) bean).setDefaultRolePrefix(null);
}
if(bean instanceof DefaultMethodSecurityExpressionHandler) {
((DefaultMethodSecurityExpressionHandler) bean).setDefaultRolePrefix(null);
}
if(bean instanceof DefaultWebSecurityExpressionHandler) {
((DefaultWebSecurityExpressionHandler) bean).setDefaultRolePrefix(null);
}
if(bean instanceof SecurityContextHolderAwareRequestFilter) {
((SecurityContextHolderAwareRequestFilter)bean).setRolePrefix("");
}
return bean;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
#Override
public int getOrder() {
return PriorityOrdered.HIGHEST_PRECEDENCE;
}
}
You can see see what happened enabling the logging. In your application.properties add:
# ==============================================================
# = Logging springframework
# ==============================================================
logging.level.org.springframework.jdbc=DEBUG
logging.level.org.springframework.security=DEBUG
logging.level.org.springframework.web=DEBUG
logging.level.org.springframework.http=DEBUG

spring security LDAP get additional fields

I am using Spring Security with LDAP (Active directory), I am able to authenticate user and create my own user detail object by extending LdapUserDetailsMapper.
By default I am getting certain fields and groups and DN.
But I would like to get additional fields, like email, contact number, which are available in Active Directory.
So how to get those information ?
My configuration
#Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("hmie.co.in", "ldap://1.1.1.1:389/");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setUserDetailsContextMapper(userDetailsContextMapper);
return provider;
}
Custom user detail mapping
#Service
public class MyUserDetailsContextMapper extends LdapUserDetailsMapper implements UserDetailsContextMapper {
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
LdapUserDetailsImpl ldapUserDetailsImpl = (LdapUserDetailsImpl) super.mapUserFromContext(ctx, username, authorities);
MyUserDetails myUserDetails = new MyUserDetails();
myUserDetails.setAccountNonExpired(ldapUserDetailsImpl.isAccountNonExpired());
myUserDetails.setAccountNonLocked(ldapUserDetailsImpl.isAccountNonLocked());
myUserDetails.setCredentialsNonExpired(ldapUserDetailsImpl.isCredentialsNonExpired());
myUserDetails.setEnabled(ldapUserDetailsImpl.isEnabled());
myUserDetails.setUsername(ldapUserDetailsImpl.getUsername());
myUserDetails.setAuthorities(ldapUserDetailsImpl.getAuthorities());
String dn = ldapUserDetailsImpl.getDn();
int beginIndex = dn.indexOf("cn=") + 3;
int endIndex = dn.indexOf(",");
myUserDetails.setEmployeeName(dn.substring(beginIndex, endIndex));
beginIndex = dn.indexOf("ou=") + 3;
endIndex = dn.indexOf(",", beginIndex);
myUserDetails.setDepartment(dn.substring(beginIndex, endIndex));
return myUserDetails;
}
}
To get the complete LDAP Directory attributes and values i did like this. But here i am using inteface org.springframework.ldap.core.AttributesMapper instead of class org.springframework.security.ldap.userdetails.LdapUserDetailsMapper.
ldapTemplate.search("o=XXXXX", new EqualsFilter("uid", userName).encode(),
new AttributesMapper() {
#Override
public Object mapFromAttributes(Attributes attr) throws NamingException {
// TODO Auto-generated method stub
NamingEnumeration<String> namingEnumeration = attr.getIDs();
while (namingEnumeration.hasMoreElements()) {
String attributeName= (String) namingEnumeration.nextElement();
System.out.println(attributeName+" = "+attr.get(attributeName));
}
return null;
}
});
In the above piece of code attr.getIDs() returns the Active directory attributes like CN,DN,SN and mail. attr.get(attribute) returns the value of attribute.
The code in mapUserFromContext is so close! The key detail is that the ctx object passed in to the method already contains the additional Active Directory attributes for the principal. The attribute values are accessible using method ctx.getStringAttribute("attribute-name"). For example, you would access the surname attribute of the principal with ctx.getStringAttribute("sn"). To get the user's email and contact number, you would only need to access the appropriate attributes. In my company's Active Directory, those attributes are mail and phone, respectively. The attributes might be named differently in your system.

capture the third party web service session Id during Spring Security Session

I have implemented Spring security in a Spring MVC web application.
For the authentication purpose I am using LDAP and for authorization I am calling a third party Web Service that provides me All the authorizations and also a Session Id.
Once user log out or session timeout, I need to call the third party web service again with the same session Id for invalidation of session.
I have created a Log out Listener that listen to SessionDestroyedEvent like this
public class LogoutListener implements ApplicationListener<SessionDestroyedEvent>{
private SecurityServiceHandler securityServiceHandler;
#Override
public void onApplicationEvent(SessionDestroyedEvent event) {
SecurityContext securityContext = event.getSecurityContext();
UserDetails ud=null;
if(securityContext!=null){
ud = (UserDetails) securityContext.getAuthentication().getPrincipal();
if(securityServiceHandler==null){
securityServiceHandler = new SecurityServiceHandler();
}
//String sessionId = securityServiceHandler.getSessionId();
String sessionId = VirgoSessionManager.getSessionId();
System.out.println(ud.getUsername());
System.out.println(VirgoSessionManager.getSessionId());
securityServiceHandler.invalidateSession(ud.getUsername(),sessionId);
//reset the sessionId
securityServiceHandler.setSessionId(null);
}
}
I have used ThreadLocal in the VirgoSessionManager Class like follow
public class VirgoSessionManager {
private static ThreadLocal<String> sessionId = new ThreadLocal<String>();
public static String getSessionId(){
return sessionId.get();
}
public static void setSessionId(String sId) {
sessionId.set(sId);
}
public static void remove() {
sessionId.remove();
}
}
My problem is the that The VirgoSessionManager is not returning the session I have set during the Third party Session creation call after successful session cration even though I have implemented thread Local.
Any help will be appreciated.
Thank you!
you can have completely different thread serving your log out functionality which results in not having any value in ThreadLocal variable. For example tomcat uses thread pools so you need to be careful here. Try to log it/debug using Thread.currentThread().getName() and Thread.currentThread().getId() in getSessionId() and a place you set this value
I fixed the issue with the separate thread calling the Logout/Session time out.
I created a new customized User class and extended the Original spring "org.springframework.security.core.userdetails.User" class. I added new field "sessionId" to my customized user class.
So whenever I get the logged user details from Spring SecurityContext during logout/timeout, I will always have that sessionId and use to call invalidateSession method.
My customized user class looks like this.
package com.wvo.custom.security;
import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
public class CustomUser extends User {
private String virgoSessionId ;
/**
*
*/
private static final long serialVersionUID = 1L;
public CustomUser(String username, String password,boolean enabled, boolean accountNonExpired, boolean accountNonLocked, boolean credentialsNonExpired,
Collection<? extends GrantedAuthority> authorities) {
super(username, password,enabled, accountNonExpired, accountNonLocked, credentialsNonExpired, authorities);
}
public String getVirgoSessionId() {
return virgoSessionId;
}
public void setVirgoSessionId(String virgoSessionId) {
this.virgoSessionId = virgoSessionId;
}
}
Thank you !

Implementing simple ldap user details mapper - grails

In my previous question i was working on adding roles to users logging from ldap. hopefully ive managed to register custom AuthoritiesPopulator for ldap. But now i would like to add some more functionallity to application and for that i need some more information about users than login-name.
Following this guide:
http://grails-plugins.github.io/grails-spring-security-ldap/docs/manual.106/guide/2.%20Usage.html
Im guessing i have to implement my own ldap details context mapper, and i did as shown below:
class CustomUserDetailsContextMapper implements UserDetailsContextMapper {
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
// FETCHING DATA and ADDING ROLE
return new CustomUserDetails(trueUsername, null, true, true, true, true, list, email)
}
#Override
public void mapUserToContext(UserDetails arg0, DirContextAdapter arg1) {
throw new IllegalStateException("Only retrieving data from AD is currently supported")
}
}
And adding mapping in resource.config:
ldapUserDetailsMapper(amelinium1.grails.CustomUserDetailsContextMapper)
But it doesn't seem to work. Application seem to use GormUserDetailsService to return User not the Context. Am i missing something here ?
Would appreciate any help!

Resources