package com.ankurm.methodsec; import java.util.List; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.aopalliance.intercept.MethodInvocation; import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.authorization.AuthorizationManagerFactory; import org.springframework.security.authorization.DefaultAuthorizationManagerFactory; import org.springframework.security.core.Authentication; /** * Demo 4 -- the SpEL surface of method security, evaluated for real. * *
Every row this prints is a real annotated method on a real proxied bean, invoked twice * under two different identities. The reference table in the article is generated from this * output, so if Spring Security changes one of these, the table is wrong on the next run * rather than wrong forever. * *
Chapter:
* docs/02-spel-reference.md.
*/
public class Demo4SpelReference {
public static void main(String[] args) {
Support.banner("Demo 4 -- what you can actually write inside @PreAuthorize / @PostAuthorize");
try (var ctx = new AnnotationConfigApplicationContext(Config.class)) {
Spel s = ctx.getBean(Spel.class);
Support.heading("alice: ROLE_USER, ROLE_AUDITOR, plus the authority 'report:read'");
Support.login("alice", "ROLE_USER", "ROLE_AUDITOR", "report:read");
runAll(s);
Support.heading("root: ROLE_ADMIN only (RoleHierarchy says ADMIN > USER > GUEST)");
Support.login("root", "ROLE_ADMIN");
runAll(s);
Support.heading("Role prefix and hierarchy");
Support.login("alice", "ROLE_USER", "ROLE_AUDITOR", "report:read");
Support.attempt("hasRole('USER') -> ROLE_USER", () -> s.hasRoleUser());
Support.attempt("hasAuthority('USER') -> literal 'USER'", () -> s.hasAuthorityUserNoPrefix());
Support.attempt("hasAuthority('ROLE_USER')", () -> s.hasAuthorityRoleUser());
Support.login("root", "ROLE_ADMIN");
Support.attempt("root hasRole('GUEST') via RoleHierarchy", () -> s.hasRoleGuest());
Support.heading("Method arguments, the return value, and bean references");
Support.login("alice", "ROLE_USER", "ROLE_AUDITOR", "report:read");
Support.attempt("#owner == authentication.name (\"alice\")", () -> s.byParameterName("alice"));
Support.attempt("#owner == authentication.name (\"bob\")", () -> s.byParameterName("bob"));
Support.attempt("@P(\"o\") alias, #o == ...name (\"alice\")", () -> s.byParameterAlias("alice"));
Support.attempt("#root.this (the target object)", () -> s.byRootThis());
Support.attempt("#root.args[0] -- no such property", () -> s.byPositionalArg("alice"));
Support.attempt("@policy.canRead(authentication, #id) id=1", () -> s.byBeanReference(1));
Support.attempt("@policy.canRead(authentication, #id) id=9", () -> s.byBeanReference(9));
Support.attempt("hasPermission(#id, 'account', 'read') id=1", () -> s.byPermissionEvaluator(1));
Support.attempt("hasPermission(#id, 'account', 'read') id=9", () -> s.byPermissionEvaluator(9));
Support.attempt("T(java.time.LocalDate) type reference", () -> s.byTypeReference());
Support.attempt("@PostAuthorize returnObject.owner == ...name", () -> s.postAuthorizeReturnObject("alice"));
Support.attempt("@PostAuthorize returnObject.owner == ...name", () -> s.postAuthorizeReturnObject("bob"));
Support.heading("The literal constants on SecurityExpressionRoot");
System.out.println(" permitAll / denyAll exist as BOTH a boolean field and a no-arg method,");
System.out.println(" and read/write/create/delete/admin are String constants meant for");
System.out.println(" hasPermission(..) -- e.g. hasPermission(#id, 'account', read).");
Support.attempt("hasPermission(#id, 'account', read) id=1", () -> s.byPermissionConstant(1));
}
finally {
Support.logout();
}
}
private static void runAll(Spel s) {
Support.attempt("permitAll", () -> s.permitAll());
Support.attempt("denyAll", () -> s.denyAll());
Support.attempt("isAuthenticated()", () -> s.isAuthenticated());
Support.attempt("isAnonymous()", () -> s.isAnonymous());
Support.attempt("isFullyAuthenticated()", () -> s.isFullyAuthenticated());
Support.attempt("isRememberMe()", () -> s.isRememberMe());
Support.attempt("hasRole('ADMIN')", () -> s.hasRoleAdmin());
Support.attempt("hasAnyRole('ADMIN','AUDITOR')", () -> s.hasAnyRole());
Support.attempt("hasAllRoles('USER','AUDITOR')", () -> s.hasAllRoles());
Support.attempt("hasAuthority('report:read')", () -> s.hasAuthorityReportRead());
Support.attempt("hasAnyAuthority('report:read','x')", () -> s.hasAnyAuthority());
Support.attempt("hasAllAuthorities('report:read','x')", () -> s.hasAllAuthorities());
Support.attempt("authentication.name == 'alice'", () -> s.authenticationName());
Support.attempt("principal == 'alice'", () -> s.principalEquals());
}
@Configuration
@EnableMethodSecurity
static class Config {
@Bean
Spel spel() {
return new Spel();
}
@Bean
AccountPolicy policy() {
return new AccountPolicy();
}
/**
* Spring Security 7.1 routes {@code hasRole}, {@code hasAuthority},
* {@code authenticated()} and friends through an
* {@link AuthorizationManagerFactory}. Role hierarchy and role prefix are set here,
* NOT on the expression handler -- {@code AbstractSecurityExpressionHandler}'s
* {@code setRoleHierarchy(..)} is deprecated in 7.1 (the compiler says so; that is
* how this demo found out).
*/
@Bean
static AuthorizationManagerFactory