Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use custom expressions in Spring Security @PreAuthorize/@PostAuthorize annotations

Is there a way to create more expressive statements in @Preauthorize blocks? Here's an example of something I find myself repeating, because the @Preauthorize is not terribly smart out of the box.

@RequestMapping(value = "{id}", method = RequestMethod.DELETE) public void deleteGame(@PathVariable int id, @ModelAttribute User authenticatingUser) {     Game currentGame = gameService.findById(id);     if(authenticatingUser.isAdmin() || currentGame.getOwner().equals(authenticatingUser)) {         gameService.delete(gameService.findById(id));     } else {         throw new SecurityException("Only an admin, or an owner can delete a game.");     } } 

What I would prefer is something like.

@RequestMapping(value = "{id}", method = RequestMethod.DELETE) @Preauthorize(isAdmin(authenicatingUser) OR isOwner(authenicatingUser, id) public void deleteGame(@PathVariable int id, @ModelAttribute User authenticatingUser, @ModelAttribute currentGame ) { //I'm not sure how to add this either :(    gameService.delete(gameService.findById(id)); } 

Part of the problem is that I need to make a query to the database to fetch some of this stuff to verify permissions, such as querying the database to get a copy of the game, and then comparing the owner of the game to the person making the request. I'm not really sure how all of that operates within the context of a @Preauthorize annotation processor, or how I add things to the collection of objects made available in the @Preauthorize("") value attribute.

like image 594
Jazzepi Avatar asked Nov 05 '14 00:11

Jazzepi


People also ask

Which annotation can be used within Spring Security?

We can enable annotation-based security using the @EnableGlobalMethodSecurity annotation on any @Configuration instance. For example, the following would enable Spring Security's @Secured annotation.

What is @PreAuthorize annotation in spring boot?

Method-level security is implemented by placing the @PreAuthorize annotation on controller methods (actually one of a set of annotations available, but the most commonly used). This annotation contains a Spring Expression Language (SpEL) snippet that is assessed to determine if the request should be authenticated.

How do I PreAuthorize in spring boot?

@PreAuthorize annotation is used on a method level. For example, you can add the @PreAuthorize annotation above the @RequestMapping method that handles HTTP DELETE requests to allow only those users who have an ADMIN Role to invoke this method. @PreAuthorize annotation supports method security expressions.

What is hasRole and hasAnyRole?

hasRole, hasAnyRole. These expressions are responsible for defining the access control or authorization to specific URLs and methods in our application: @Override protected void configure(final HttpSecurity http) throws Exception { ... . antMatchers("/auth/admin/*").


2 Answers

Since @PreAuthorize evaluates SpEl-expressions, the easiest way is just to point to a bean:

    @PreAuthorize("@mySecurityService.someFunction()") 

MySecurityService.someFunction should have return type boolean.

Spring-security will automatically provide a variable named authentication if you want to pass the Authentication-object. You can also use any valid SpEl-expressions to access any arguments passed to your secure method, evaluate regular expressions, call static methods, etc. E.g:

    @PreAuthorize("@mySecurityService.someFunction(authentication, #someParam)") 
like image 60
gogstad Avatar answered Oct 02 '22 23:10

gogstad


1) First you have to reimplement MethodSecurityExpressionRoot which contains extra method-specific functionality. The original Spring Security implementation is package private and hence it is not possible to just extend it. I suggest checking the source code for the given class.

public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {      // copy everything from the original Spring Security MethodSecurityExpressionRoot      // add your custom methods      public boolean isAdmin() {         // do whatever you need to do, e.g. delegate to other components          // hint: you can here directly access Authentication object          // via inherited authentication field     }      public boolean isOwner(Long id) {         // do whatever you need to do, e.g. delegate to other components     } } 

2) Next you have to implement custom MethodSecurityExpressionHandler that will use the above defined CustomMethodSecurityExpressionRoot.

public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {      private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();      @Override     public void setReturnObject(Object returnObject, EvaluationContext ctx) {         ((MethodSecurityExpressionRoot) ctx.getRootObject().getValue()).setReturnObject(returnObject);     }      @Override     protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,         MethodInvocation invocation) {         final CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication);         root.setThis(invocation.getThis());         root.setPermissionEvaluator(getPermissionEvaluator());         root.setTrustResolver(this.trustResolver);         root.setRoleHierarchy(getRoleHierarchy());          return root;     } } 

3) Define expression handler bean in your context, e.g. via XML you can do it as follows

<bean id="methodSecurityExpressionHandler"     class="my.package.CustomMethodSecurityExpressionHandler">     <property name="roleHierarchy" ref="roleHierarchy" />     <property name="permissionEvaluator" ref="permissionEvaluator" /> </bean> 

4) Register the above defined handler

<security:global-method-security pre-post-annotations="enabled">     <security:expression-handler ref="methodSecurityExpressionHandler"/> </security:global-method-security> 

5) Then just use the defined expressions in your @PreAuthorize and/or @PostAuthorize annotations

@PreAuthorize("isAdmin() or isOwner(#id)") public void deleteGame(@PathVariable int id, @ModelAttribute currentGame) {     // do whatever needed } 

And one more thing. It is not very common to use method level security to secure controller methods but rather to secure methods with business logic (a.k.a. your service layer methods). Then you could use something like the below.

public interface GameService {      // rest omitted      @PreAuthorize("principal.admin or #game.owner = principal.username")     public void delete(@P("game") Game game); } 

But keep in mind that this is just an example. It expects that the actual principal has isAdmin() method and that the game has getOwner() method returning username of the owner.

like image 34
pgiecek Avatar answered Oct 02 '22 23:10

pgiecek