After upgrading from Spring Boot 2.3.0.RELEASE -> 2.7.8 and upgrading Spring Security from 5.7.6 -> 5.8.1, I only fixed the following deprecation - #EnableGlobalMethodSecurity(prePostEnabled = true) to #EnableMethodSecurity
After making just that one change and re-running my Spock unit tests, I am continually getting the following root exception:
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1004E: Method call: Method internalUser(java.lang.String) cannot be found on type org.springframework.security.access.expression.method.MethodSecurityExpressionRoot
For some odd reason, the Spring Security Framework is no longer using my own CustomMethodSecurityExpressionRoot and seems to be defaulting back to its own org.springframework.security.access.expression.method.MethodSecurityExpressionRoot
Here is my CustomMethodSecurityExpressionRoot.java:
package com.wellframe.excalibur.auth.accesscontrol;
import com.wellframe.excalibur.auth.AuthenticationHolder;
import com.wellframe.excalibur.auth.UserType;
import com.wellframe.excalibur.services.GambitService;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations;
import org.springframework.security.core.Authentication;
/\*\*
* Custom methods defined here will be accessible by spring security method annotations like #PreAuthorize.
\*/
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements
MethodSecurityExpressionOperations {
private final GambitService gambitService;
public CustomMethodSecurityExpressionRoot(Authentication authentication, ApplicationContext context) {
super(authentication);
gambitService = context.getBean(GambitService.class);
}
public boolean gambitAuthorize(AccessControlPolicy... policies) {
return gambitService.authorizeRequest((AuthenticationHolder) super.getAuthentication(), List.of(policies));
}
public AccessControlPolicy internalUser(String methodName, Object... args) {
return new AccessControlPolicy(UserType.INTERNAL_USER, methodName, args);
}
public AccessControlPolicy careManager(String methodName, Object... args) {
return new AccessControlPolicy(UserType.DASH_USER, methodName, args);
}
public AccessControlPolicy programUser(String methodName, Object... args) {
return new AccessControlPolicy(UserType.MOBILE_USER, methodName, args);
}
// Copied from Spring Security's access/expression/method/MethodSecurityExpressionRoot.java
private Object filterObject;
private Object returnObject;
private Object target;
#Override
public void setFilterObject(Object filterObject) {
this.filterObject = filterObject;
}
#Override
public Object getFilterObject() {
return filterObject;
}
#Override
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
#Override
public Object getReturnObject() {
return returnObject;
}
void setThis(Object target) {
this.target = target;
}
#Override
public Object getThis() {
return target;
}
}
Here is my CustomCustomMethodSecurityExpressionHandler.java:
package com.wellframe.excalibur.auth.accesscontrol;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.context.ApplicationContext;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations;
import org.springframework.security.core.Authentication;
/\*\*
* Used to provide custom security expressions in the #PreAuthorize annotation.
\*/
public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
private ApplicationContext context;
public CustomMethodSecurityExpressionHandler() {
String stopHere = "stopHere";
}
#Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
MethodInvocation invocation) {
CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication, this.context);
root.setThis(invocation.getThis());
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(getTrustResolver());
root.setRoleHierarchy(getRoleHierarchy());
root.setDefaultRolePrefix(getDefaultRolePrefix());
return root;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) {
super.setApplicationContext(applicationContext);
this.context = applicationContext;
}
}
and finally, here is my MethodSecurityConfig.java:
#EnableMethodSecurity
//#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
#Autowired
ApplicationContext context;
#Bean
protected MethodSecurityExpressionHandler createExpressionHandler() {
CustomMethodSecurityExpressionHandler expressionHandler = new CustomMethodSecurityExpressionHandler();
expressionHandler.setApplicationContext(context);
return expressionHandler;
}
}
The only change that I made was fix the deprecation. #EnableGlobalMethodSecurity(prePostEnabled = true) to #EnableMethodSecurity.
After making that change, and stepping through the code, I realized that the Spring Security framework is no longer calling my #Override method in my CustomMethodSecurityExpressionHandler.java:
#Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
MethodInvocation invocation) {
CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication, this.context);
root.setThis(invocation.getThis());
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(getTrustResolver());
root.setRoleHierarchy(getRoleHierarchy());
root.setDefaultRolePrefix(getDefaultRolePrefix());
return root;
}
I have my service class which does a post call. I would like to instantiate that bean/ autowire it to create a object in another class which is not a component or configuration class.
#Service
public class SavePayload {
// Rest Post Call implementation
}
public class PayloadRecord
implements Record {
private String payload;
PayloadProcessor payloadProcessor = new PayloadProcessor();
public PayloadRecord(String payload) {
this.payload = payload;
}
#SneakyThrows
#Override
public boolean isValid() throws ValidationException {
payloadProcessor.savePayload(payload);
return true;
}
#Override
public byte[] getBytes(Charset charset) {
return payload.getBytes(StandardCharsets.UTF_8);
}
#Override
public String getID() {
return payload;
}
#Override
public String toString() {
return payload;
}
private static class PayloadProcessor {
#Autowired
private SavePayload savePayload;
}
}
I'm using a template which will do the record processing. As soon as I got message received I'm assigning it to Payload in Payload Record which is non component class. I would like to initialize the SavePayload service. Save payload service is returning null.
Create an application context aware class so you can get the current context, something like:
#Component
public class ContextAwareClass implements ApplicationContextAware {
private static ApplicationContext ctx;
public static ApplicationContext getApplicationContext() {
return ctx;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) {
ctx = applicationContext;
}
}
Then, just get the context and get the bean like:
public class YourRegularNoSpringComponentClass {
public void doSomething() {
System.out.println(ContextAwareClass
.getApplicationContext()
.getBean("savePayload")
);
}
}
Above will print the bean if it exist in your context. In your case you would simple use it rather than print it.
Hope this helps!
You will have to create an instance of ApplicationContext
You can explore
AnnotationConfigApplicationContext applicationContext= new AnnotationConfigApplicationContext();
and then use.
SavePayload savePayload = applicationContext.getBean("savePayload");
My Spring Boot application implements the TenantStore example for storing data in ThreadLocalTargetSource detailed in this link
#Bean(destroyMethod = "destroy")
public ThreadLocalTargetSource threadLocalTenantStore() {
ThreadLocalTargetSource result = new ThreadLocalTargetSource();
result.setTargetBeanName("tenantStore");
return result;
}
The working example allows for the TenantStore object to be set and injected by the Spring Framework. My version of the TenantFilter class described in that article sets the properties of the TenantStore object whenever a Servlet request is made
#Autowired
private TenantStore tenantStore;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
String token = (String) request.getAttribute(ACCESS_TOKEN_VALUE);
if (token != null) {
OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(token);
if (oAuth2AccessToken.getAdditionalInformation() != null) {
String tenantName = (String) oAuth2AccessToken.getAdditionalInformation().get("tenant");
storeTenantInThread(tenantName);
}
}
}
chain.doFilter(request, response);
} catch (ResourceNotFoundException e) {
log.error(e.getMessage());
} finally {
clearTenant();
}
}
private void storeTenantInThread(String tenantName) {
tenantStore.setName(tenantName);
}
private void clearTenant() {
tenantStore.clear();
}
I then have a number of services where TenantStore is autowired and in each of these services the TenantStore contains the information that was populated in the doFilter() method. Except for one class. For some reason the properties of the TenantStore in this class are still null. The name of the class affected is MyCacheService and the architecture is as follows:
#RestController
#RequestMapping("/here")
public class MyController {
#Autowired
private MyService myService
#GetMapping
public ResponseEntity myGetMethod(#RequestParam("text") String text) {
myService.myMethod(text);
return new ResponseEntity(Http.OK);
}
}
#Service
public class MyService {
#Autowired
private TenantStore tenantStore;
#Autowired
private MyOtherService myOtherService;
public void myMethod(String text) {
System.out.println(tenantStore.getName()); //works - prints name
myOtherService.myOtherMethod(text);
}
}
#Service
public class MyOtherService {
#Autowired
private TenantStore tenantStore;
#Autowired
private Map<String, MyComponent> myComponents;
public void myOtherMethod(String text) {
System.out.println(tenantStore.getName()); //works - prints name
MyComponent useThisComponent = myComponents.get("componentName");
useThisComponent.myComponentMethod(text);
}
}
#Component("componentName")
public class MyComponent {
#Autowired
private TenantStore tenantStore;
#Autowired
private MyCacheService myCacheService;
public void myComponentMethod(String text) {
System.out.println(tenantStore.getName()); //works - prints name
entityAliasCacheService.myCacheMethod(String text);
}
}
#Service
public class MyCacheService {
#Autowired
private TenantStore tenantStore;
public void myCacheMethod(String text) {
System.out.println(tenantStore.getName()); //DOES NOT WORK - tenantStore object is not null but the name property is
}
}
From what I can guess, for some reason the TenantStore in MyCacheService is being populated in a different thread, though I've no idea why.
I noticed similar behaviour. I fixed the issue by adding a bean dependancy
#Service
#DependsOn("proxiedThreadLocalTargetSource") // asks Spring to first load proxy bean
public class MyCacheService {
where proxiedThreadLocalTargetSource bean is defined like in the OP's example -
#Primary
#Bean(name = "proxiedThreadLocalTargetSource")
public ProxyFactoryBean proxiedThreadLocalTargetSource(ThreadLocalTargetSource threadLocalTargetSource) {
ProxyFactoryBean result = new ProxyFactoryBean();
result.setTargetSource(threadLocalTargetSource);
return result;
}
So, by adding the dependancy, Spring knows that it should load MyCacheService bean after the proxiedThreadLocalTargetSource. Without this dependancy, I noticed that TenantStore got injected instead of the proxy bean.
Getting instance of TenantStore from org.springframework.context.ApplicationContext
First implement ApplicationContextAware like as below
#Component
public class ApplicationContextUtil implements ApplicationContextAware {
private static ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static ApplicationContext context() {
return context;
}
}
And your MyCacheService Will be like this:
public class MyCacheService {
public void myCacheMethod(String text) {
TenantStore tenantStore = ApplicationContextUtil.context().getBean(TenantStore.class);
System.out.println(tenantStore.getName());
}
}
I have working application secured with method-level security:
RestController:
#PreAuthorize("hasPermission(#product, 'WRITE')")
#RequestMapping(value = "/save", method = RequestMethod.POST)
public Product save(#RequestBody Product product) {
return productService.save(product);
}
PermissionEvaluator:
public class SecurityPermissionEvaluator implements PermissionEvaluator {
private Logger log = LoggerFactory.getLogger(SecurityPermissionEvaluator.class);
private final PermissionService permissionService;
public SecurityPermissionEvaluator(PermissionService permissionService) {
this.permissionService = permissionService;
}
#Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
return permissionService.isAuthorized(userDetails.getUser(), targetDomainObject, permission.toString());
}
#Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
// almost the same implementation
}
}
And everything works fine until I implemented API which saves collection of objects. The logic of this service is to update existing entities and/or create new entities.
#PreAuthorize("hasPermission(#products, 'WRITE')")
#RequestMapping(value = "/saveCollection", method = RequestMethod.POST)
public Collection<Product> save(#RequestBody Collection<Product> products) {
return productService.save(products);
}
After this my permission service handles the collection object and looks like this now:
PemissionService:
public class PermissionService {
public boolean isAuthorized(User user, Object targetDomainObject, String permission) {
if (targetDomainObject instanceof TopAppEntity) {
if (((TopAppEntity) targetDomainObject).getId() == null) {
// check authorities and give response
} else {
// check ACL and give response
}
} else if(targetDomainObject instanceof Collection) {
boolean isAuthorized = false;
Collection targetDomainObjects = (Collection) targetDomainObject;
for (Object targetObject : targetDomainObjects) {
isAuthorized = isAuthorized(user, targetObject, permission);
if (!isAuthorized) break;
}
return isAuthorized;
}
}
}
My question is:
How I can handle collections using #PreAuthorize("hasPermission(#object, '...')") more elegant way? Is there some implementations in Spring Security for handling collections? At least, how can I optimize PemissionService for handling Collections?
I have a couple of workarounds.
1. The first one is to use my own MethodSecurityExpressionHandler and MethodSecurityExpressionRoot.
Creating a CustomMethodSecurityExpressionRoot and define a method which will be our new expression for Collection handling. It will extend SecurityExpressionRoot to include default expressions:
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
private final PermissionEvaluator permissionEvaluator;
private final Authentication authentication;
private Object filterObject;
private Object returnObject;
private Object target;
public CustomMethodSecurityExpressionRoot(Authentication authentication, PermissionEvaluator permissionEvaluator) {
super(authentication);
this.authentication = authentication;
this.permissionEvaluator = permissionEvaluator;
super.setPermissionEvaluator(permissionEvaluator);
}
public boolean hasAccessToCollection(Collection<Object> collection, String permission) {
for (Object object : collection) {
if (!permissionEvaluator.hasPermission(authentication, object, permission))
return false;
}
return true;
}
#Override
public void setFilterObject(Object filterObject) {
this.filterObject = filterObject;
}
#Override
public Object getFilterObject() {
return filterObject;
}
#Override
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
#Override
public Object getReturnObject() {
return returnObject;
}
#Override
public Object getThis() {
return target;
}
}
Create custom expression handler and inject CustomMethodSecurityExpressionRoot:
public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
private final PermissionEvaluator permissionEvaluator;
public CustomMethodSecurityExpressionHandler(PermissionEvaluator permissionEvaluator) {
this.permissionEvaluator = permissionEvaluator;
super.setPermissionEvaluator(permissionEvaluator);
}
#Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(
Authentication authentication, MethodInvocation invocation) {
CustomMethodSecurityExpressionRoot root =
new CustomMethodSecurityExpressionRoot(authentication, permissionEvaluator);
root.setTrustResolver(new AuthenticationTrustResolverImpl());
root.setRoleHierarchy(getRoleHierarchy());
return root;
}
}
I also injected SecurityPermissionEvaluator used in question, so it will be a single point of entry for custom and default expressions. As an alternate option we could inject and use PermissionService directly.
Configuring our method-level security:
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Autowired
private PermissionService permissionService;
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
PermissionEvaluator permissionEvaluator = new SecurityPermissionEvaluator(permissionService);
return new CustomMethodSecurityExpressionHandler(permissionEvaluator);
}
}
Now we can use new expression in RestController:
#PreAuthorize("hasAccessToCollection(#products, 'WRITE')")
#RequestMapping(value = "/saveCollection", method = RequestMethod.POST)
public Collection<Product> save(#RequestBody Collection<Product> products) {
return productService.save(products);
}
As a result a part with handling collection in PermissionService could be omitted as we took out this logic to custom expression.
2. The second workaround is to call method directly using SpEL.
Now I'm using PermissionEvaluator as Spring bean (any service could be used here, but I'm preferring single point of entry again)
#Component
public class SecurityPermissionEvaluator implements PermissionEvaluator {
#Autowired
private PermissionService permissionService;
#Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
if (!(targetDomainObject instanceof TopAppEntity))
throw new IllegalArgumentException();
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
return permissionService.isAuthorized(userDetails.getUser(), targetDomainObject, permission.toString());
}
#Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
try {
return permissionService.isAuthorized(userDetails.getUser(), targetId,
Class.forName(targetType), String.valueOf(permission));
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("No class found " + targetType);
}
}
public boolean hasPermission(Authentication authentication, Collection<Object> targetDomainObjects, Object permission) {
for (Object targetDomainObject : targetDomainObjects) {
if (!hasPermission(authentication, targetDomainObject, permission))
return false;
}
return true;
}
}
Configuring method security:
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Autowired
private PermissionEvaluator permissionEvaluator;
#Autowired
private ApplicationContext applicationContext;
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(permissionEvaluator);
// Pay attention here, or Spring will not be able to resolve bean
expressionHandler.setApplicationContext(applicationContext);
return expressionHandler;
}
}
Usage of the service in expression:
#PreAuthorize("#securityPermissionEvaluator.hasPermission(authentication, #products, 'WRITE')")
#RequestMapping(value = "/saveCollection", method = RequestMethod.POST)
public Collection<Product> save(#RequestBody Collection<Product> products) {
return productService.save(products);
}
Spring beans created by default with class name if no other name specified.
Summary: both approaches based on using custom services calling them directly or registering them as expressions and could handle the logic of collection before it will be sent to authority checking service, so we can omit the part of it:
#Service
public class PermissionService {
public boolean isAuthorized(User user, TopAppEntity domainEntity, String permission) {
// removed instanceof checks and can operate on domainEntity directly
if (domainEntity.getId() == null) {
// check authorities and give response
} else {
// check ACL and give response
}
}
}
Yes, there is a smart way. I can tell you what I did.
#Component("MySecurityPermissionEvaluator ")
#Scope(value = "session")
public class PermissionService {
#Autowired
private PermissionEvaluator permissionEvaluator;
public boolean myPermission(Object obj, String permission) {
boolean isAuthorized = false;
Authentication a = SecurityContextHolder.getContext()
.getAuthentication();
if (null == obj) {
return isAuthorized;
}
if (a.getAuthorities().size() == 0) {
logger.error("For this authenticated object, no authorities could be found !");
return isAuthorized;
} else {
logger.error("Authorities found " + a.getAuthorities());
}
try {
isAuthorized = myPermissionEval
.hasPermission(a, obj, permission);
} catch (Exception e) {
logger.error("exception while analysisng permissions");
}
return isAuthorized;
}
Please do not use hard coded permissions, Use this way instead,
import org.springframework.security.acls.domain.DefaultPermissionFactory;
public class MyPermissionFactory extends DefaultPermissionFactory {
public MyPermissionFactory() {
registerPublicPermissions(MyPermission.class);
}
}
To make custom permissions,
import org.springframework.security.acls.domain.BasePermission;
public class MyPermission extends BasePermission { //use this class for creating custom permissions
private static Map<String, Integer> customPerMap = new HashMap<String, Integer>();
static {
customPerMap.put("READ", 1);
customPerMap.put("WRITE", 2);
customPerMap.put("DELETE", 4);
customPerMap.put("PUT", 8);
}
/**
*Use the function while saving/ getting permission code
**/
public static Integer getCode(String permName) {
return customPerMap.get(permName.toUpperCase());
}
If you need to authenticate urls based on admin users or role hierarchy, use tag in Spring Authentication not Authorization.
Rest, you are using correctly, #PreAuthorize and #PreFilter both are correct and used acco to requirements.
You can use the #PreFilter annotation.
So #PreFilter("hasPermission(filterTarget, '...')") will call your PermissionService for each element of the Collection.
public class PermissionService() {
public boolean isAuthorized(User user, Object targetDomainObject, String permission) {
if (targetDomainObject instanceof TopAppEntity) {
if (((TopAppEntity) targetDomainObject).getId() == null) {
// check authorities and give response
} else {
// check ACL and give response
}
}
}
}
Note: this will not prevent a call of your controller method. It only gets an empty Collection.
In some cases it's enough a default implementation of SecurityExpressionRoot.
If your permission evaluation is based on only analyzing, for example, an Owner of Product you could use the next expressions:
#GetMapping("")
#PostAuthorize("hasPermission(returnObject.![#this.owner],'ProductOwner','READ')")
public Collection<Product> getAllFiltering(<filters>) {...
#PostMapping("/collection")
#PreAuthorize("hasPermission(#products.![#this.owner],'ProductOwner','WRITE')")
public Collection<Product> save(#RequestBody Collection<Product> products) {...
#PutMapping("/collection")
#PreAuthorize("hasPermission(#productRepository.findByIds(#products.![#this.id]).![#this.owner],'ProductOwner','WRITE')")
public Collection<Product> update(#RequestBody Collection<Product> products) {...
In these cases your PermissionEvaluator must be able to process collection.You could also continue using your PermissionEvaluator for a single Product:
#GetMapping("/{id}")
#PostAuthorize("hasPermission({ returnObject.owner },'ProductOwner','READ')")
public Product getById(#PathVariable int id) {...
or make an implementation of PermissionEvaluator which analyzes whether an array or a single value was passed.
#products.![#this.owner] - see "6.5.17 Collection Projection"; { returnObject.owner } - see "6.5.3 Inline lists"
here: https://docs.spring.io/spring/docs/3.0.x/reference/expressions.html
I am investigating how does FactoryBean works in spring framework.
As I understand it allow configure instantiation process.
I have the following beans:
#Component
public class MyInjectionClass {
String name;
Integer age;
//get and set methods
}
and
#Component
public class MyComponent {
#Autowired
MyInjectionClass myInjectionClass;
public MyInjectionClass getMyInjectionClass() {
return myInjectionClass;
}
}
and following cutom FactoryBean:
#Component
public class MyInjectionClassFactoryBean implements FactoryBean<MyInjectionClass> {
#Override
public MyInjectionClass getObject() throws Exception {
MyInjectionClass myInjectionClass = new MyInjectionClass();
myInjectionClass.setName("name");
myInjectionClass.setAge(12);
return myInjectionClass;
}
#Override
public Class<?> getObjectType() {
return MyInjectionClass.class;
}
#Override
public boolean isSingleton() {
return false;
}
}
Also I have wrote following code in my main method:
MyComponent bean = context.getBean(MyComponent.class);
System.out.println(bean.getMyInjectionClass().getAge());
It returns null.
What did I forget to do ?
P.S.
I use #ComponentScan("com.example.domain")
All beans and FactoryBean located there.
Solution
remove #Component above MyInjectionClass