Extending SecurityExpressionRoot for Access in #Query - spring-security

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}")

Related

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...?

Principal not showing Authorities after implementing AuthoritiesExtractor

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);
}

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.
}
}

Grails Spring Security Get Roles for the Current Page

I was wondering if anyone knows an elegant way to get all the roles in the spring security plugin that have access to the current page.
I am using spring security and it's configured to use RequestMap domain objects.
The permissions in my app are pretty complex so I wanted to make a tag at the bottom of each page displaying the roles need to use the page.
I was doing a query for the request map but I want to make sure the way I match the url is the same as the way the plugin does.
Ideally I wouldn't have to run a query at all.
Grails version 2.2.1 Spring Security Plugin version 1.2.7.3
Thanks in advance
I got this to work by adding the following two classes to my src/java.
Class 1
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
FilterInvocationSecurityMetadataSource oldBean;
#Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
FilterInvocation filterInvocation = (FilterInvocation) o;
HttpServletRequest request = filterInvocation.getHttpRequest();
request.setAttribute("PAGEROLES", oldBean.getAttributes(filterInvocation));
return oldBean.getAttributes(o);
}
#Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return oldBean.getAllConfigAttributes();
}
#Override
public boolean supports(Class<?> aClass) {
return FilterInvocation.class.isAssignableFrom(aClass);
}
public Object getOldBean() { return oldBean; }
public void setOldBean(FilterInvocationSecurityMetadataSource oldBean) { this.oldBean = oldBean; }
}
Class 2
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
public class FilterSecurityMDSExtractor implements BeanPostProcessor, BeanFactoryAware {
private ConfigurableListableBeanFactory bf;
private FilterInvocationSecurityMetadataSource metadataSource = new MyFilterInvocationSecurityMetadataSource();
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof FilterInvocationSecurityMetadataSource) {
((MyFilterInvocationSecurityMetadataSource) metadataSource).setOldBean((FilterInvocationSecurityMetadataSource) bean);
return metadataSource;
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.bf = (ConfigurableListableBeanFactory)beanFactory;
}
}
I then added the following to resources.groovy
beans = {
filterSecurityMDSExtractor(FilterSecurityMDSExtractor)
}
Basically I am stuffing the user roles into the request
request.setAttribute("PAGEROLES", oldBean.getAttributes(filterInvocation));
then all I have to do is call the following
request.getAttribute("PAGEROLES");
to get the roles back out. I pieced together my solution by stealing from other great posts on Stackoverflow. Someone else might have a better solution but so far this is working for me.

Spring security dynamically add users and authorities

How to generate "intercept-url" dynamically. My user name and roles are stored in database,
I want to map all these users in to spring security.Is there any way to do this?
You'll have to provide your own implementation of com.icod.solapCore.spring.security.FilterInvocationSecurityMetadataSource.
This could look like this :
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
#Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
FilterInvocation filterInvocation = (FilterInvocation) object;
HttpServletRequest request = filterInvocation.getHttpRequest();
Collection<ConfigAttribute> result = new ArrayList<ConfigAttribute>();
// Find roles in database that secures the specified request
// ...
// For any role found, create a SecurityConfig object prefixed with "ROLE_" ex :
// for(String role : roles) {
// ConfigAttribute attribute = new SecurityConfig("ROLE_"+roleFound);
// result.add(attribute);
// }
return result;
}
#Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
#Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
And then you'll have to replace the default FilterInvocationSecurityMetadataSource with your own. I do it with a BeanPostProcessor, called after spring read the configuration file but before it makes the configuration official. Looks like this :
public class MyFilterInvocationSecurityMetadataSourceBeanPostProcessor implements BeanPostProcessor {
private FilterInvocationSecurityMetadataSource metadataSource = new MyFilterInvocationSecurityMetadataSource();
#Override
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
if (bean instanceof FilterInvocationSecurityMetadataSource) {
return metadataSource;
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String name) throws BeansException {
return bean;
}
}
Then you just have to configure the bean post processor :
<bean id="solapcoreFilterInvocationSecurityMetadataSourceBeanPostProcessor" class="foo.bar.MyFilterInvocationSecurityMetadataSourceBeanPostProcessor"/>
Hope this help.
Give all your users same role and operate with this role in config.
You can read abour roles here

Resources