Spring Security hasRole() for Unauthenticated Users, Considering Role Hierarchy - java

I have a role hierarchy in my Spring Boot 2 + Spring Security application:
#Bean
public RoleHierarchy roleHierarchy() {
var rh = new RoleHierarchyImpl();
rh.setHierarchy("ROLE_ADMIN > ROLE_USER and ...");
return rh;
}
Now I (as an admin) want to create an entity on behalf of another user, but I should check if that user has a certain authority based on the above hierarchy.
I know that it's possible to call spring security hasRole() for the current authenticated user, but in my case, the user I want to authorize is not authenticated.
Now, I can check to see if the user has that specific authority:
public boolean hasAuthority(User user, String authority) {
return user.getAuthorities()
.stream()
.anyMatch(grantedAuthority -> grantedAuthority.getName().equals(authority));
}
But this way the hierarchy, which is fairly long, would be ignored.
I would be thankful for any help.

You could use the role hierarchy, see RoleHierarchy#getReachableGrantedAuthorities:
Collection<? extends GrantedAuthority> getReachableGrantedAuthorities(Collection<? extends GrantedAuthority> authorities)
Returns an array of all reachable authorities.
Reachable authorities are the directly assigned authorities plus all authorities that are (transitively) reachable from them in the role hierarchy.
Example:
Role hierarchy: ROLE_A > ROLE_B and ROLE_B > ROLE_C.
Directly assigned authority: ROLE_A.
Reachable authorities: ROLE_A, ROLE_B, ROLE_C.
Parameters:
authorities - List of the directly assigned authorities.
Returns:
List of all reachable authorities given the assigned authorities.
Your modified code:
public boolean hasAuthority(User user, String authority) {
return roleHierarchy()
.getReachableGrantedAuthorities(user.getAuthorities())
.stream()
.anyMatch(grantedAuthority -> grantedAuthority.getName().equals(authority));
}

Related

Authentication Manager Builder in Spring Security

I was exploring spring security and tried to build a small application wherein I have a an entity name User and a userRepository having a one declared method findByUserName(String userName)
#Entity
#Table(name="user")
class User {
#id
private Long id;
private String userName;
private String password;
}
I have heard that spring security depends on principles and not users.
So we have to have a class which implements UserDetails (provided by spring security).
What's the reason behind this?
Secondly, once we have written all this code we need to configure it into a class which I have done as shown below:
public class AppSecurityConfid extends WebSecurityCongigurerAdapter {
// here we have to autowire the service class which we have made to call the
userRepository and find the user based on userName
#Bean
public DAOAuthenicationProvider authenicationProvider() {
// wherein we create an instance and pass the autowired instance and set the
password encoder and return the instance
}
protected void configurer(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider());
}
}
Things up to here make sense, but why we need Authentication Build Manager in this scheme of things?
I am not an expert but I'd like to say something, maybe it can help:
Spring uses "in the background" a way to retrieve user data for authentication when you activate Spring Security. Of course, this method can be overriden so the developer can change how Spring obtains this data in order to support situations where the data is sparced in different tables, from a file, an API REST query, etc.
The authentication data is structured as a list, where each element corresponds to the data used to authenticate each user. This data is structured as a tuple of 3 elements: String username, String hashedPassword and boolean isAccountActive.
You need to provide a way to obtain this data for each user. You do not need to provide the data explicitly, just the way (method) to obtain it. One way to do it (as you said) is creating a class that implements UserDetailsService, which, for example, forces you to implement UserDetails loadUserByUsername(String email);. In this method you need to provide an instance of a class that implements UserDetails, which corresponds to the UserDetails of the User with the username passed as a parameter. This methods (and similar) are used by Spring Security "in the background" to retrieve the UserDetails of a certain user when is trying to access your web server.
If the Userdetails match with the credentials provided in the request, Spring will allow the request to hit your controllers; else it will throw a HTTP 401. Of course there are other authentication methods, but for the sake of simplicity we understand credentials as user / password with HTTP basic authentication.
So, why do we need a class that implements UserDetails? Because is the contract that a class needs to fulfill if it has to be used for internal authentication in Spring Security. Also to separate from a User class the logic of the business with the logic of the security. Probably creating your own class that extends UserDetails is the best idea, but is not actually necessary. For example if you have your own class to contain the data of a user, you just need to understand how to transform your User instance to UserDetails, so Spring Security can use it transparently and the opposite: how the UserDetails instance can be transformed into one of your users.
For example this is a method to obtain the User instance using the UserDetails instance that is currently authenticated in Spring Boot.
#Service
public class SecurityServiceClass{
#Override
public User getLoggedUser() {
String username = ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername();
Optional<User> user = this.userService.get().stream().filter(r -> r.getEmail().equals(username)).findFirst();
UserDetails userDetails = ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal());
// TODO: make error in case of null
return user.orElse(new User());
}
}
Here I retrieve the User by retrieving the username from the UserDetails and querying it to the DB to recover the User. I am accessing the DB using a repository class.
Here I do the opposite, transforming a User to a UserDetails by creating a Userdetails instance based on the relevant data of the User. (Note that I use the email as username)
#Service
public class UserServiceClass extends GenericServiceClass<User, UUID> {
#Autowired
public UserServiceClass(UserRepository userRepository) {
super(userRepository);
}
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Optional<User> selected = ((UserRepository) this.genericRepository).getUserByEmail(s);
if (selected.isPresent())
{
// Obtain user by email (username)
User user = selected.get();
// Obtain the roles of this user to construct the instance of UserDetails for SpringBoot Security.
Set<Role> roles = user.getRoles();
return org.springframework.security.core.userdetails.User
.withUsername(s)
.roles(roles.stream().toArray(
(n) -> {
return new String[n];
}
))
.password(user.getHashedPassword())
.build();
}
else
{
throw new UsernameNotFoundException("The user with email " + s + " is not registered in the database");
}
}
Finally, regarding AuthenticationManagerBuilder: This is a method that is used to configure authentication. As far as I know, you can define how your application should obtain the UserDetails. I am not sure if you can provide a method or a lambda to retrieve the triplet for authentication String username, String hashedPassword and boolean isAccountActive. What I do know and did in my application is provide the SQL query used to retrieve the triplet from my DB since I have there all that information. Code:
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authenticationBuilder) throws Exception
{
Session session = this.sessionFactory.getCurrentSession();
session.beginTransaction();
authenticationBuilder.userDetailsService(this.userDetailsService()).passwordEncoder(this.passwordEncoder()).and()
.jdbcAuthentication().dataSource(this.dataSource)
.usersByUsernameQuery("select email, hashed_password as passw, true from user where email = ?")
.authoritiesByUsernameQuery("SELECT user.email, CONCAT(elementpermission.journal_id, '_', elementpermission.authority)\n" +
"FROM user, elementpermission\n" +
"WHERE elementpermission.user = user.uuid \n" +
"AND user.email = ?");
session.getTransaction().commit();
session.close();
}
TL;DR
Spring Security needs instances that fulfill the contract of the interface UserDetails because is the interface that Spring Security uses to obtain the relevant data for authentication.
The authentication manager builder is used to config howto obtain the data used for authentication.
You can check this links if you want better information:
https://www.baeldung.com/spring-security-jdbc-authentication
https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/jdbc.html
jdbcAuthentication() instead of inMemoryAuthentication() doesn't give access - Spring Security and Spring Data JPA

Spring Security - Authrozation on rest apis

I am trying to implement role based authorization on my rest apis.
I have been able to successfully implement and run the project but there are some things that I am confused about.
CustomeUserDetailsService.java:
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<UsersAuth> optionalUsers = usersAuthRepository.findByName(username);
optionalUsers
.orElseThrow(() -> new UsernameNotFoundException("Username not found"));
return optionalUsers
.map(CustomUserDetails::new).get();
}
CustomUserDeatils.java:
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return getRoles()
.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getRole()))
.collect(Collectors.toList());
}
I want to know what the above two code snippets are actually doing. can someone explain to me? I have searched alot but did not understand. Specially what is the use of Granted authority? I know for a fact that CustomUserDetailsService is taking a username and if it does not exist, it throws an exception. If anyone could elaborate in detail? It would help me alot.
Optional<UsersAuth> optionalUsers = usersAuthRepository.findByName(username);
It is looking for user by his name probably inside databse or other in-memory store.
optionalUsers
.orElseThrow(() -> new UsernameNotFoundException("Username not found"));
As you already know this line will throw exception if Optional is empty in other words user do not exist.
return optionalUsers
.map(CustomUserDetails::new).get();
This line creates new object of CustomUserDeatils class that implements UserDetails interface and returns it. CustomUserDeatils has constructor with UsersAuth parameter.
return getRoles()
.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getRole()))
.collect(Collectors.toList());
This is simple mapping of probably Role collection to SimpleGrantedAuthority list. As I mentioned earlier CustomUserDeatils class implements UserDetails. One of its method is Collection<? extends GrantedAuthority> getAuthorities() which is used to get info about authorities granted to user. AuthenticationProvider will use this info from UserDetailsService to create Authentication object. Spring Security will be checking if authenticated user has required roles etc using this mapped roles. SimpleGrantedAuthority is just implementation of GrantedAuthority. ROLE_ is default prefix used by RoleVoter when you are checking for roles in your controller method e.g #Secured("ROLE_ADMIN")
In the simplest way to answer, when you try to login with username and password, spring security calls loadUserByUsername with that username you provide. This method looks in the database if any user exists with the username. If doesnt throws exception. Otherwise returns the UserDetails object. Then spring security checks if the password of UserDetails object matched with the password you have provided. If doesnt throws exception. Otherwise provides you authentication token.
User can have role and this role can be used to permit/block apis for that user. For example in the database that user has role CUSTOMER and some other users have role ADMIN. Now you want certain api /abc to be exposed only to the users with CUSTOMER role. For this you have to do this
#PreAuthorize("hasAnyAuthority('CUSTOMER')")
#PostMapping(value = "/abc")
public ResponseEntity<Map<String, Integer>> create(#Valid #RequestBody DTO dto) {
...
The above api is accessible to only user with role CUSTOMER

spring security is remember me user authenticated by username

In short:
I want to know if a user - given by the username - is authenticated or not.
I want to know this for Username / password auths given by org.springframework.security.authentication.UsernamePasswordAuthenticationToken
but also for remember me users given by org.springframework.security.authentication.RememberMeAuthenticationToken
The username / password part is easy:
class someService {
def userDetailsService
def sessionRegistry
boolean hasUserUserNameAndPasswordAuth(String username){
def principal = userDetailsService.loadUserByUsername(theUser.email, false)
userSession = sessionRegistry.getAllSessions(principal, false).find { !it.isExpired() }
if (userSession)
return true
else
return false
}
}
All users which are authenticated by username and password will return true on the function above. But all remembermy users will return false.
Even this method call sessionRegistry.getAllPrincipals() will return a list which does not contains principals that are authenticated by remember me token.
Why does the sessionRegistry not return users with a remember me authentication? Don't they also have a session?!
To get remember me authentifications, I played around with the rememberMeServices class. This class has a method called autoLogin(request, response) that returns the authentication of the current request. But this doesn't help me because I want to know if a user given by a username has a valid authentification, not the current one)
I think I have 3 options:
a) Find you why sessionRegistry does not return sessions for remember me authentifications
b) find a service that returns all principals that have a valid (from the expire date point of view) token
c) write a filter that intercepts every request and creates a new session - that can be found by the sessionRegistry - when a user has a remember me auth based on calling rememberMeServices.autoLogin(request, response).
What do you think?

Spring security : User class without UserRole (roles). Authentication only no authorization needed

I'm using Spring-security with Spring-boot. My Application is a POC, for the moment there is no need to have Role.
I was wondering if it is possible to have a Custom UserDetailsService which return UserDetails in loadUserByUsername method but without the GrantedAuthority nor UserRole class.
I have googled all the day long for an example but I always get them with the UserRole.
Thanks in advance
This should work:
Let UserDetails return an empty collection in getAuthorities():
public class User implements UserDetails {
...
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return new HashSet<GrantedAuthority>();
}
}
Use just authenticated() while configuring security
http.authorizeRequests()
...
.antMatchers("/foo").authenticated()
...

Adding additional details to principal object stored in spring security context

I am using Spring 3.0 and Spring Security 3. I am able to authenticate a user against a database using Spring Security. Using:
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
I am able to retrieve username of the current logged in user. I wish to add additional details like user id and the module accesses to the principal object stored in Spring Security context so that I can retrieve it later. How can I add additional details to the principal object and then how can I retrieve it later on a jsp or java class. Please provide an appropriate code snippet if possible.
Edit: I am using JDBC to access my database.
Here is what you need:
Extend spring User (org.springframework.security.core.userdetails.User) class and what ever properties you need.
Extend spring UserDetailsService (org.springframework.security.core.userdetails.UserDetailsService) and fill the above object. Override loadUserByUsername and return your extended user class
Set your custom UserDetailsService in AuthenticationManagerBuilder
For example
public class CurrentUser extends User{
//This constructor is a must
public CurrentUser(String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
}
//Setter and getters are required
private String firstName;
private String lastName;
}
The Custom userdetails could be:
#Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
//Try to find user and its roles, for example here we try to get it from database via a DAO object
//Do not confuse this foo.bar.User with CurrentUser or spring User, this is a temporary object which holds user info stored in database
foo.bar.User user = userDao.findByUserName(username);
//Build user Authority. some how a convert from your custom roles which are in database to spring GrantedAuthority
List<GrantedAuthority> authorities = buildUserAuthority(user.getUserRole());
//The magic is happen in this private method !
return buildUserForAuthentication(user, authorities);
}
//Fill your extended User object (CurrentUser) here and return it
private User buildUserForAuthentication(foo.bar.User user,
List<GrantedAuthority> authorities) {
String username = user.getUsername();
String password = user.getPassword();
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
return new CurrentUser(username, password, enabled, accountNonExpired, credentialsNonExpired,
accountNonLocked, authorities);
//If your database has more information of user for example firstname,... You can fill it here
//CurrentUser currentUser = new CurrentUser(....)
//currentUser.setFirstName( user.getfirstName() );
//.....
//return currentUser ;
}
private List<GrantedAuthority> buildUserAuthority(Set<UserRole> userRoles) {
Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();
// Build user's authorities
for (UserRole userRole : userRoles) {
setAuths.add(new SimpleGrantedAuthority(userRole.getRole()));
}
return new ArrayList<GrantedAuthority>(setAuths);
}
}
Configure the spring security context
#Configuration
#EnableWebSecurity
#PropertySource("classpath://configs.properties")
public class SecurityContextConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("userDetailsService")
private UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
It's all done !
You can call (CurrentUser)getAuthentication().getPrincipal() to get you newly CurrentUser or set some properties.
In order to add more details to the authenticated user. You need to first create your own implementation of the User object which should extend the spring security User object. After that you can add the properties you want to add to the authenticated user. Once this is done you need to return your implementation of the user object in UserDetailService (If you are not using LDAP for authentication). This link provides the details for adding more details to the authenticated user--
http://javahotpot.blogspot.com/2013/12/spring-security-adding-more-information.html
(I will assume you have a basic Spring Security configuration working and know how the basic components work together)
The most "correct" way would be providing your own implementation of AuthenticationProvider, that return a custom Authentication implementation. Then you can fill in this Authentication instance with everything you need. For example:
public class MyAuthentication extends UsernamePasswordAuthenticationToken implements Authentication {
public MyAuthentication(Object principal, Object credentials, int moduleCode) {
super(principal, credentials);
this.moduleCode = moduleCode;
}
public MyAuthentication(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities,int moduleCode) {
super(principal, credentials, authorities);
this.moduleCode = moduleCode;
}
private int moduleCode;
public getModuleCode() {
return moduleCode;
}
}
public class MyAuthenticationProvider extends DaoAuthenticationProvider {
private Collection<GrantedAuthority> obtainAuthorities(UserDetails user) {
// return granted authorities for user, according to your requirements
}
private int obtainModuleCode(UserDetails user) {
// return moduleCode for user, according to your requirements
}
#Override
public Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
// Suppose this user implementation has a moduleCode property
MyAuthentication result = new MyAuthentication(authentication.getPrincipal(),
authentication.getCredentials(),
obtainAuthorities(user),
obtainModuleCode(user));
result.setDetails(authentication.getDetails());
return result;
}
}
And then, in applicationContext.xml:
<authentication-manager>
<authentication-provider ref="myAuthenticationProvider">
</authentication-manager>
<bean id="myAuthenticationProvider" class="MyAuthenticationProvider" scope="singleton">
...
</bean>
I guess you could get it working by providing custom implementations of AuthenticationDetails and AuthenticationDetailsSource, but I think that would be a less clean approach.
The "only" things you need to do is create your own UserDetailsService implementation which returns your own implementation of a UserDetails object.
See here for a tutorial which implements a JPA based UserDetailsService.

Categories