Principal not showing Authorities after implementing AuthoritiesExtractor - spring-security

I am extending AuthoritiesExtractor. In my implementation I am adding a new authority called "MYROLE". I have created a bean (#Bean) as shown below in SecurityConfig.java to initialize this extractor as shown below.
#Configuration
#EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public AuthoritiesExtractor userAuthorityExtractor() {
return new UserAuthorityExtractor();
}
My controller method /me returns the principal object shown below.
#RestController
public class UserController {
#GetMapping("/me")
public Principal currentUser (Principal principal) {
return principal;
}
The returned object does not display the authority "MYROLE". What am I missing?

First things first, set a break point in your extractor. Does your code even execute? I would set the break point here:
#Bean
public AuthoritiesExtractor userAuthorityExtractor() {
return new UserAuthorityExtractor();
}
If that's a no, then you need to figure out why your bean is not being invoked. I would make sure that your SecurityConfig is actually picked up by the component scanner.
As for your controller.
In Spring Security, there is no guarantee that the Principal object also holds the authorities. The principal itself is a very simple interface
/**
* Returns the name of this principal.
*
* #return the name of this principal.
*/
public String getName();
So what you want, is the Authentication object, because that interface exposes getAuthorities
#RestController
public class UserController {
#GetMapping("/me")
public Principal currentUser (Authentication authentication) {
//do what you need with authorities
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
return authentication.getPrincipal();
}
You can also review the default implementation
public List<GrantedAuthority> extractAuthorities(Map<String, Object> map) {
String authorities = "ROLE_USER";
if (map.containsKey("authorities")) {
authorities = this.asAuthorities(map.get("authorities"));
}
return AuthorityUtils.commaSeparatedStringToAuthorityList(authorities);
}

Related

Extending SecurityExpressionRoot for Access in #Query

My rest API is secured with OAuth2 and I want to be able to write #Queries so that only entities owned by the user are displayed. Actually, the use is part of a tenant and the entities are owned by the tenant rather than the user. The tenant identifier can be derived from the scopes in the JWT token.
My thinking was, I should be able to provide a custom SecurityExpressionRoot that takes care of deriving that tenant from the scopes and providing the value for use in the #Query annotation. This is the EvaluationContextExtension and SecurityExpressionRoot I made for this:
#Component
public class SecurityEvaluationContextExtension implements EvaluationContextExtension {
#Override
public String getExtensionId() {
return "security";
}
#Override
public SecurityExpressionRoot getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new CustomSecurityExpressionRoot(authentication);
}
public static class CustomSecurityExpressionRoot extends SecurityExpressionRoot {
public CustomSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
public String getTenant() {
return "foo";
}
}
}
In the Repository, I want to be able to access the tenant property and construct the query with it:
public interface SubscriptionRepo extends CrudRepository<Subscription, Long> {
#PreAuthorize("isFullyAuthenticated()")
#Query("SELECT a FROM Subscription a WHERE a.owner = HOW_TO_ACCESS_THE_TENANT?")
#Override
Iterable<Subscription> findAll();
}
I put "HOW_TO_ACCESS_THE_TENANT?" because that's where I currently struggle. I have tried many things I found on the internet like ?#{#security.tenant}, ?#{tenant}, ?#{getTenant()}, ?#{#security.getTenant()} but nothing seems to work.
?#{#security.tenant} => SpelEvaluationException: EL1007E: Property or field 'tenant' cannot be found on null
?#{#security.getTenant()} => SpelEvaluationException: EL1011E: Method call: Attempted to call method getTenant() on null context object
?#{tenant} => SpelEvaluationException: EL1008E: Property or field 'tenant' cannot be found on object of type 'java.lang.Object[]' - maybe not public or not valid?
I am not sure if I did something wrong implementing that custom security root, or my query is wrong or maybe it just doesn't work at all that way. Can someone provide direction?
Found out properties need to be explicitly listed in a Map exposed via EvaluationContextExtension#getProperties. I have never seen this in any documentation but came across it reading one of the error messages. So the working implementation of EvaluationContextExtension with a custom SecurityExpressionRoot looks like this:
#Component
public class EditTenantEvaluationContextExtension implements EvaluationContextExtension {
#Override
public Map<String, Object> getProperties() {
Map<String, Object> properties = new HashMap<>(EvaluationContextExtension.super.getProperties());
properties.put("tenants", getRootObject().getTenants());
return properties;
}
#Override
public String getExtensionId() {
return "security";
}
#Override
public CustomSecurityExpressionRoot getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new CustomSecurityExpressionRoot(authentication);
}
public static class CustomSecurityExpressionRoot extends SecurityExpressionRoot {
public CustomSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
public Set<String> getTenants() {
return SecurityUtils.getTenants();
}
}
}
The query needs to look like this:
#Query("SELECT a FROM Subscription a WHERE a.owner = ?#{security.tenants}")

CustomPermissionEvaluator with database

CustomPermissionEvaluator with database.
On a REST Controller or Service I would like to set a custim Spring Security #preauthorize in order to check access on method according user rights.
User right is my application to access to a resource (page, service etc...) are specific and handled by an habilitation service call database....
They are not loaded in userDetails.
So, how could I do to configure CustomPermissionEvaluator and do I need to load permissions on login or can call my services in the evaluator.
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class ContextSecurityConfiguration {
#Bean
public PermissionEvaluator functionPermissionEvaluator() {
return new XxxPermissionEvaluator ();
}
#Bean
protected DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(this.functionPermissionEvaluator());
return expressionHandler;
}
}
Evaluator
#Component
public class XxxPermissionEvaluator implements PermissionEvaluator {
#Autowired
private ApplicationContext applicationContext;
#Override
public boolean hasPermission(Authentication authentication, Object entity, Object permission) {
Optional<String> optionalUserId = SecurityUtils.getCurrentUserLogin(authentication);
return optionalUserId.map(userId -> {
EntityValidatorFactory entityValidatorFactory = applicationContext.getBean(EntityValidatorFactory.class);
EntityValidator entityValidator = entityValidatorFactory.get(entity);
return entityValidator.isUserAllowedToEntity(userId, entity, (String) permission);
}).orElse(false);
}
#Override
public boolean hasPermission(Authentication authentication, Serializable serializable, String s, Object o) {
throw new PrimaClaimsRuntimeException("hasPermission is not implemented.");
}
}
My pb is my custom evaluator call a database service it has to inject it.
Or it's a bit weird in a configuration class to injects service etc... scanned else where by the same configuration
So calling a service is it a good way you should I load permission on Login in userDetail...?

Spring OAuth with JWT custom UserDetails - Set Principal inside JwtAccessTokenConverter

Some additional info is sent from OAuth Authorization Server that is needed inside a custom UserDetails class on Resource Server, and preferably inside SpringSecurity Principal.
Current approach is setting a username as Principal and adding additional info as an additional details of Authentication object like this.
public class CustomAccessTokenConverter extends JwtAccessTokenConverter{
#Override
public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
OAuth2Authentication authentication = super.extractAuthentication(claims);
CustomUserDetails userDetails = new CustomUserDetails ();
userDetails.setUserId(((Integer)claims.get("id")).longValue());
userDetails.setName((String) claims.get("name"));
userDetails.setLastName((String) claims.get("lastName"));
authentication.setDetails(userDetails);
return authentication;
}
}
The good thing about this approach is we can access custom UserDetails from anywhere inside the app. The bad thing is that Pricipal object is stuck on being only users username, and we need a lot more code to access custom UserDetails.
// preferable way
(UserAuthDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
// current solution
(UserAuthDetails) ((OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails()).getDecodedDetails();
Is there a cleaner solution to use JwtAccessTokenConverter but still be able to set Principal as custom UserDetails instead of setting it to (useless) username and sending additional info as details of Authentication object?
I can not say if this is the preferred solution, but after trying to solve the same thing myself, I ended up extending the DefaultUserAuthenticationConverter.
So you can do something like this
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
DefaultAccessTokenConverter defaultConverter = new DefaultAccessTokenConverter();
defaultConverter.setUserTokenConverter(new CustomUserAuthenticationConverter());
JwtAccessTokenConverter jwtConverter = new JwtAccessTokenConverter();
converter.setAccessTokenConverter(defaultConverter);
return converter;
}
Then the DefaultUserAuthenticationConverter is not very extendable since most methods and properties are private. But here is an example
public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
private static final String CUST_PROP = "custProp";
#Override
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(USERNAME) && map.containsKey(CUST_PROP)) {
String username = (String) map.get(USERNAME);
String custProp = (String) map.get(CUST_PROP);
CustomPrincipal principal = new CustomPrincipal();
pricipal.setUsername(username);
pricipal.setCustomProp(custProp);
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
return new UsernamePasswordAuthenticationToken(user, "N/A", authorities);
}
return null;
}
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
//Copy this method from DefaultUserAuthenticationConverter or create your own.
}
}

Is it possible to load granted authorities and other user information AFTER authentication succeeds when using a UserDetailsService?

I have been able to get a Spring Security based application up and running, and it has been satisfying all my requirements until now.
I do have 1 doubt regarding how UserDetailsService is used in Spring Security. I have a custom 'UserDetailsService' implementation, which goes like this -
public class CustomUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<UserDetailsDto> userDetailsByEmail = // Load userDetailsDto from database
if (!userDetailsByEmail.isPresent()) {
throw new UsernameNotFoundException("Username does not exists");
}
UserDetailsDto userDetailsDto = userDetailsByEmail.get();
List<Role> roles = roleService.listByEmail(username);
List<ModulePermission> modulePermissions = modulePermissionService.listByUserId(userDetailsDto.getId());
UserType userType = userTypeService.getByUserId(userDetailsDto.getId());
return new LoggedInUser(userDetailsDto, roles, modulePermissions, userType);
}
}
The class LoggedInUser is an extension of Spring Security's org.springframework.security.core.userdetails.User class, which goes like this -
public class LoggedInUser extends User {
private static final long serialVersionUID = -1L;
private Long userId;
private boolean firstLogin;
private UserType userType;
private List<ModulePermission> modulePermissions;
private String firstName;
private String lastName;
private String contactNo;
public LoggedInUser(UserDetailsDto userDetailsDto, List<Role> roles, List<ModulePermission> modulePermissions,
UserType userType) {
super(userDetailsDto.getEmail(), userDetailsDto.getPassword(), userDetailsDto.getEnabledStatus().getValue(),
userDetailsDto.getAccountNonExpiredStatus().getValue(), true,
userDetailsDto.getAccountNonLockedStatus().getValue(),
roles.stream().map(role -> new SimpleGrantedAuthority(role.getId())).collect(Collectors.toList()));
this.modulePermissions = modulePermissions;
this.userType = userType;
this.userId = userDetailsDto.getId();
this.firstLogin = userDetailsDto.getIsFirstLoginStatus().getValue();
this.firstName = userDetailsDto.getFirstName();
this.lastName = userDetailsDto.getLastName();
this.contactNo = userDetailsDto.getContactNo();
}
public List<ModulePermission> getModulePermissions() {
return Collections.unmodifiableList(modulePermissions);
}
public UserType getUserType() {
return userType;
}
public Long getUserId() {
return userId;
}
public boolean isFirstLogin() {
return firstLogin;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getContactNo() {
return contactNo;
}
public void setFirstLogin(boolean firstLogin) {
this.firstLogin = firstLogin;
}
}
Now, to configure Spring Security to use my CustomUserDetailsService, I do the following in security configuration -
#Bean
public UserDetailsService customUserDetailsService() {
return new CustomUserDetailsService();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher(SuperAdminConstant.UrlConstant.ANT_MATCHER_PATH)
.userDetailsService(customUserDetailsService())
.formLogin(// further configuration)
}
And this works without any problems.
But notice that in CustomUserDetailsService, several database queries are executed even before the user has been authenticated successfully (This is because Spring Security has created a DaoAuthenticationProvider, which loads a UserDetails implementation (in my case, LoggedInUser), and perform various checks on that object AFTER it has been retrieved from a UserDetailsService (in my case , CustomUserDetailsService)).
Consider that a user has entered the correct username, but a wrong password. In that case, the high-level authentication flow would be -
CustomUserDetailsService would be called
First query is executed to verify username and load user details (UsernameNotFoundException is not thrown as username is correct)
Second query is executed to retrieve the roles
Third query is executed to retrieve module permissions
Fourth query is executed to retrieve user types
DaoAuthenticationProvider checks the password, finds it to be incorrect, and throws a BadCredentialsException.
So as can be seen, total 4 queries are executed EVEN BEFORE authentication process has completed, out of which only 1 is essential at this stage (the first query to verify username).
One solution to this problem can be to eliminate the use of UserDetailsService altogeather, and use a custom AuthenticationProvider instead.
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// Customize the authentication logic here, and retrieve
// user information only if everything is correct.
}
}
But going by this approach also means that I have to duplicate code and functionality provided by DaoAuthenticationProvider and AbstractUserDetailsAuthenticationProvider, which includes checking user account status flags manually (accountNonExpired, accountNonLocked etc.), and throwing exceptions.
So I was wondering weather it is possible to perform authentication logic in such a way that user information is retrieved only AFTER authentication succeeds, and most of the authentication logic provided by Spring Security can be resused.
Any ideas will be deeply appreciated.
You can write implementation of AuthenticationSuccessHandler:
#Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
LoggedInUser loggedInUser = (LoggedInUser)authentication.getPrincipal();
List<Role> roles = roleService.listByEmail(username);
List<ModulePermission> modulePermissions = modulePermissionService.listByUserId(userDetailsDto.getId());
UserType userType = userTypeService.getByUserId(userDetailsDto.getId());
// Set roles after authentication succeeds
loggedInUser.setRoles(roles);
loggedInUser.setModulePermissions(modulePermissions);
loggedInUser.setUserType(userType);
}
}
After the authentication succeeds, you can obtain logged in user from security context and set additional properties.

Grails Spring Security get user by id

I'm writing Grails application with Spring Security plugin.
I have enabled queries generated by GORM into console and I have noticed that every request Security query the database about users, selecting them by it's username.
My intention is load user's by it's ID's, not usernames for increase performance.
I am aware that there is a possibility to overwrite UserDetailsService method loadUserByUsername(String username), but this method is used both for refreshing user's credential during the session and in login form, where in fact I want to authenticate user by it's username.
I have three questions:
How to load user by id? Should I inject user ID instead of username in GrailsUser (implementation of UserDetails) instead of regular username and use long selectById = Long.valueOf(String username)?
How to create different user provider for refreshing session to grab user by ID and different for user login (when I want to grab user by it's username/email)?
There is possibility to fetch user credentials not every request, but every X seconds?
Finally i managed to solve this problem. The queries are generated by:
springSecurityService.getCurrentUser()
Unfortunatelly, this method fetches User model class by username (from Principal object) and maps it into database field, at most configured by:
grails.plugin.springsecurity.userLookup.usernamePropertyName
as mentioned in documentation.
I have tried
grails.plugin.springsecurity.userLookup.usernamePropertyName = 'id'
but i received class-cast exception from String to Long.
Workaround is simple - create own Principle with username field typed as Long.
See PrincipalProxy in my solution:
package com.selly.util.security
import java.security.Principal;
import grails.plugin.springsecurity.userdetails.GrailsUser
import org.springframework.security.core.Authentication
import org.springframework.security.core.GrantedAuthority
class AppMetadataAuthenticationToken implements Authentication, Principal {
private boolean authenticated
private GrailsUser userDetails
private Principal principal
public AppMetadataAuthenticationToken(GrailsUser userDetails) {
this.userDetails = userDetails
this.principal = new PrincipalProxy(userDetails)
}
public GrailsUser getUser() {
return userDetails
}
public String getUsername() {
return userDetails.getUsername()
}
#Override
public String getName() {
return userDetails.getUsername()
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return userDetails.getAuthorities()
}
#Override
public Object getCredentials() {
return userDetails.password
}
#Override
public Object getDetails() {
return getUser()
}
#Override
public Object getPrincipal() {
return principal
}
#Override
public boolean isAuthenticated() {
return authenticated
}
#Override
public void setAuthenticated(boolean authenticated) throws IllegalArgumentException {
this.authenticated = authenticated
}
static class PrincipalProxy implements Principal {
GrailsUser grailsUser
Long username
public PrincipalProxy(GrailsUser grailsUser) {
this.grailsUser = grailsUser
this.username = grailsUser.id
}
#Override
public String getName() {
return grailsUser.id
}
}
}
To return this Token, just register your own AuthenticationProvider:
package com.selly.util.security;
import grails.plugin.springsecurity.SpringSecurityService
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.authentication.dao.DaoAuthenticationProvider
import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
public class AppUsernamePasswordAuthenticationProvider extends DaoAuthenticationProvider implements AuthenticationProvider {
SpringSecurityService springSecurityService
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
def token = (UsernamePasswordAuthenticationToken) authentication
def user = userDetailsService.loadUserByUsername(authentication.principal)
if(!user)
throw new UsernameNotFoundException("Cannot find user", authentication.principal)
if(!passwordEncoder.isPasswordValid(user.password, authentication.credentials, null))
throw new BadCredentialsException("Invalid password")
return new AppMetadataAuthenticationToken(user)
}
#Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
package com.selly.util.security;
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
public class AppMetadataAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// TODO Auto-generated method stub
return authentication;
}
#Override
public boolean supports(Class<?> authentication) {
// TODO Auto-generated method stub
return AppMetadataAuthenticationToken.class.isAssignableFrom(authentication);
}
}
Register it in resources.groovy
appUsernamePasswordAuthenticationProvider(AppUsernamePasswordAuthenticationProvider) {
userDetailsService = ref('userDetailsService')
passwordEncoder = ref('passwordEncoder')
userCache = ref('userCache')
saltSource = ref('saltSource')
preAuthenticationChecks = ref('preAuthenticationChecks')
postAuthenticationChecks = ref('postAuthenticationChecks')
springSecurityService = ref('springSecurityService')
}
And in Config.groovy:
grails.plugin.springsecurity.providerNames = [
'appMetadataAuthenticationProvider',
'appUsernamePasswordAuthenticationProvider',
// 'daoAuthenticationProvider',
// 'anonymousAuthenticationProvider',
// 'rememberMeAuthenticationProvider'
]
Now all works perfectly:
Hibernate: select this_.id as id13_0_, this_.account_expired as account2_13_0_, this_.account_locked as account3_13_0_, this_.enabled as enabled13_0_, this_."password" as password5_13_0_, this_.password_expired as password6_13_0_, this_.username as username13_0_, this_.workspace as workspace13_0_ from users this_ where (**this_.id=?**) limit ?
Instead of using getCurrentUser() you can also getPrincipal() and cast to your previously populated object with more data than Principal interface offers.

Resources