25

I'd like to make use of spring-security with ROLE_ADMIN and ROLE_USER roles.

I therefore try to create a typesafe enum class, but the @Secured annotation requires a constant String, which I cannot achieve by using an enum class.

What could I change in the following code?

public enum UserRole {
    ADMIN("ROLE_ADMIN");

    private String role;

    public UserRole(String role) {  
        this.role = role;
    }
}

//error: The value for annotation attribute Secured.value must be a constant expression
@Secured(USerRole.ADMIN.value())
public class SecuredView {

}
Raedwald
  • 46,613
  • 43
  • 151
  • 237
membersound
  • 81,582
  • 193
  • 585
  • 1,120

3 Answers3

23

This question is a bit old, but this is my take on it:

public enum Role implements GrantedAuthority {
    ROLE_USER, ROLE_ADMIN;

    @Override
    public String getAuthority() {
        return name();
    }
}

You can then use this together with @PreAuthorize and Spring Expression Language to authorize your methods and classes like so:

@PreAuthorize("hasRole(T(<package name>.Role).ROLE_ADMIN)")
public void doSomeThing() {
    ...
}

Note: The package name has to be the entire package name (org.company.project) and without the < and >.

As you can see, this isn't type safe per definition, as SpEL expressions are still strings, but IDEs like IntelliJ recognizes them, and will let you know of any errors.

You can use @PreAuthorize with multiple roles using hasAnyRole().

Of course, this may become a bit verbose with many roles, but you can make it prettier by creating your own annotation like this:

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@PreAuthorize("hasRole(T(<package name>.Role).ROLE_ADMIN)")
public @interface AdminAuthorization {
}

Following this, you can authorize your methods like so:

@AdminAuthorization
public void doSomething() {
    ...
}
Håkon M. T.
  • 362
  • 3
  • 10
  • I want to ask a question about the answer. I use the solution in the answer. What I want to ask; How do I apply the Enum type in the hierarchy. For example, ROLE_ADMIN> ROLE_USER – Erdem Aydemir Nov 18 '18 at 15:57
  • 1
    @forguta It's not very pretty, but you could annotate your `@AdminAuthorization` annotation with `@PreAuthorize("hasAnyRole(T(.Role).ROLE_ADMIN, T(.Role).ROLE_USER)")` – Håkon M. T. Dec 23 '18 at 10:21
19

Partial solution:

public enum Role implements GrantedAuthority {

    ADMIN(Code.ADMIN),
    USER(Code.USER);

    private final String authority;

    Role(String authority) {
        this.authority = authority;
    }

    @Override
    public String getAuthority() {
        return authority;
    }

    public class Code {
        public static final String ADMIN = "ROLE_ADMIN";
        public static final String USER = "ROLE_USER";
    }
}

Results in:

@Secured(Role.Code.ADMIN)
Terran
  • 1,091
  • 18
  • 29
  • IMO, this is definitely the most "type-safe" solution as the other proposed options pass a string of "hasRole('MY_ROLE')" which I would hardly consider to be type safe. – Levi Fuller Jan 22 '21 at 21:39
1

If you are using Lombok you can use the @FieldNameConstants annotation:

@FieldNameConstants
public class UserRoles {
    private String ROLE_USER, ROLE_ADMIN;
}

And then use it like this:

@Secured(UserRoles.Fields.ROLE_ADMIN)

Maybe in the future, there will be the @EnumNameConstants annotation, which would fit even better.

Alwin
  • 786
  • 9
  • 19