123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- // from the original documentation
- [[authz-arch]]
- = Authorization Architecture
- :figures: servlet/authorization
- [[authz-authorities]]
- == Authorities
- xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[`Authentication`], discusses how all `Authentication` implementations store a list of `GrantedAuthority` objects.
- These represent the authorities that have been granted to the principal.
- The `GrantedAuthority` objects are inserted into the `Authentication` object by the `AuthenticationManager` and are later read by either the `AuthorizationManager` when making authorization decisions.
- `GrantedAuthority` is an interface with only one method:
- [source,java]
- ----
- String getAuthority();
- ----
- This method allows ``AuthorizationManager``s to obtain a precise `String` representation of the `GrantedAuthority`.
- By returning a representation as a `String`, a `GrantedAuthority` can be easily "read" by most ``AuthorizationManager``s and ``AccessDecisionManager``s.
- If a `GrantedAuthority` cannot be precisely represented as a `String`, the `GrantedAuthority` is considered "complex" and `getAuthority()` must return `null`.
- An example of a "complex" `GrantedAuthority` would be an implementation that stores a list of operations and authority thresholds that apply to different customer account numbers.
- Representing this complex `GrantedAuthority` as a `String` would be quite difficult, and as a result the `getAuthority()` method should return `null`.
- This will indicate to any `AuthorizationManager` that it will need to specifically support the `GrantedAuthority` implementation in order to understand its contents.
- Spring Security includes one concrete `GrantedAuthority` implementation, `SimpleGrantedAuthority`.
- This allows any user-specified `String` to be converted into a `GrantedAuthority`.
- All ``AuthenticationProvider``s included with the security architecture use `SimpleGrantedAuthority` to populate the `Authentication` object.
- [[authz-pre-invocation]]
- == Pre-Invocation Handling
- Spring Security provides interceptors which control access to secure objects such as method invocations or web requests.
- A pre-invocation decision on whether the invocation is allowed to proceed is made by the `AccessDecisionManager`.
- === The AuthorizationManager
- `AuthorizationManager` supersedes both <<authz-legacy-note,`AccessDecisionManager` and `AccessDecisionVoter`>>.
- Applications that customize an `AccessDecisionManager` or `AccessDecisionVoter` are encouraged to <<authz-voter-adaptation,change to using `AuthorizationManager`>>.
- ``AuthorizationManager``s are called by the xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`] and are responsible for making final access control decisions.
- The `AuthorizationManager` interface contains two methods:
- [source,java]
- ----
- AuthorizationDecision check(Supplier<Authentication> authentication, Object secureObject);
- default AuthorizationDecision verify(Supplier<Authentication> authentication, Object secureObject)
- throws AccessDeniedException {
- // ...
- }
- ----
- The ``AuthorizationManager``'s `check` method is passed all the relevant information it needs in order to make an authorization decision.
- In particular, passing the secure `Object` enables those arguments contained in the actual secure object invocation to be inspected.
- For example, let's assume the secure object was a `MethodInvocation`.
- It would be easy to query the `MethodInvocation` for any `Customer` argument, and then implement some sort of security logic in the `AuthorizationManager` to ensure the principal is permitted to operate on that customer.
- Implementations are expected to return a positive `AuthorizationDecision` if access is granted, negative `AuthorizationDecision` if access is denied, and a null `AuthorizationDecision` when abstaining from making a decision.
- `verify` calls `check` and subsequently throws an `AccessDeniedException` in the case of a negative `AuthorizationDecision`.
- [[authz-delegate-authorization-manager]]
- === Delegate-based AuthorizationManager Implementations
- Whilst users can implement their own `AuthorizationManager` to control all aspects of authorization, Spring Security ships with a delegating `AuthorizationManager` that can collaborate with individual ``AuthorizationManager``s.
- `RequestMatcherDelegatingAuthorizationManager` will match the request with the most appropriate delegate `AuthorizationManager`.
- For method security, you can use `AuthorizationManagerBeforeMethodInterceptor` and `AuthorizationManagerAfterMethodInterceptor`.
- <<authz-authorization-manager-implementations>> illustrates the relevant classes.
- [[authz-authorization-manager-implementations]]
- .Authorization Manager Implementations
- image::{figures}/authorizationhierarchy.png[]
- Using this approach, a composition of `AuthorizationManager` implementations can be polled on an authorization decision.
- [[authz-authority-authorization-manager]]
- ==== AuthorityAuthorizationManager
- The most common `AuthorizationManager` provided with Spring Security is `AuthorityAuthorizationManager`.
- It is configured with a given set of authorities to look for on the current `Authentication`.
- It will return positive `AuthorizationDecision` should the `Authentication` contain any of the configured authorities.
- It will return a negative `AuthorizationDecision` otherwise.
- [[authz-authenticated-authorization-manager]]
- ==== AuthenticatedAuthorizationManager
- Another manager is the `AuthenticatedAuthorizationManager`.
- It can be used to differentiate between anonymous, fully-authenticated and remember-me authenticated users.
- Many sites allow certain limited access under remember-me authentication, but require a user to confirm their identity by logging in for full access.
- [[authz-custom-authorization-manager]]
- ==== Custom Authorization Managers
- Obviously, you can also implement a custom `AuthorizationManager` and you can put just about any access-control logic you want in it.
- It might be specific to your application (business-logic related) or it might implement some security administration logic.
- For example, you can create an implementation that can query Open Policy Agent or your own authorization database.
- [TIP]
- You'll find a https://spring.io/blog/2009/01/03/spring-security-customization-part-2-adjusting-secured-session-in-real-time[blog article] on the Spring web site which describes how to use the legacy `AccessDecisionVoter` to deny access in real-time to users whose accounts have been suspended.
- You can achieve the same outcome by implementing `AuthorizationManager` instead.
- [[authz-voter-adaptation]]
- == Adapting AccessDecisionManager and AccessDecisionVoters
- Previous to `AuthorizationManager`, Spring Security published <<authz-legacy-note,`AccessDecisionManager` and `AccessDecisionVoter`>>.
- In some cases, like migrating an older application, it may be desirable to introduce an `AuthorizationManager` that invokes an `AccessDecisionManager` or `AccessDecisionVoter`.
- To call an existing `AccessDecisionManager`, you can do:
- .Adapting an AccessDecisionManager
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Component
- public class AccessDecisionManagerAuthorizationManagerAdapter implements AuthorizationManager {
- private final AccessDecisionManager accessDecisionManager;
- private final SecurityMetadataSource securityMetadataSource;
- @Override
- public AuthorizationDecision check(Supplier<Authentication> authentication, Object object) {
- try {
- Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
- this.accessDecisionManager.decide(authentication.get(), object, attributes);
- return new AuthorizationDecision(true);
- } catch (AccessDeniedException ex) {
- return new AuthorizationDecision(false);
- }
- }
- @Override
- public void verify(Supplier<Authentication> authentication, Object object) {
- Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
- this.accessDecisionManager.decide(authentication.get(), object, attributes);
- }
- }
- ----
- ======
- And then wire it into your `SecurityFilterChain`.
- Or to only call an `AccessDecisionVoter`, you can do:
- .Adapting an AccessDecisionVoter
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Component
- public class AccessDecisionVoterAuthorizationManagerAdapter implements AuthorizationManager {
- private final AccessDecisionVoter accessDecisionVoter;
- private final SecurityMetadataSource securityMetadataSource;
- @Override
- public AuthorizationDecision check(Supplier<Authentication> authentication, Object object) {
- Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
- int decision = this.accessDecisionVoter.vote(authentication.get(), object, attributes);
- switch (decision) {
- case ACCESS_GRANTED:
- return new AuthorizationDecision(true);
- case ACCESS_DENIED:
- return new AuthorizationDecision(false);
- }
- return null;
- }
- }
- ----
- ======
- And then wire it into your `SecurityFilterChain`.
- [[authz-hierarchical-roles]]
- == Hierarchical Roles
- It is a common requirement that a particular role in an application should automatically "include" other roles.
- For example, in an application which has the concept of an "admin" and a "user" role, you may want an admin to be able to do everything a normal user can.
- To achieve this, you can either make sure that all admin users are also assigned the "user" role.
- Alternatively, you can modify every access constraint which requires the "user" role to also include the "admin" role.
- This can get quite complicated if you have a lot of different roles in your application.
- The use of a role-hierarchy allows you to configure which roles (or authorities) should include others.
- An extended version of Spring Security's `RoleVoter`, `RoleHierarchyVoter`, is configured with a `RoleHierarchy`, from which it obtains all the "reachable authorities" which the user is assigned.
- A typical configuration might look like this:
- .Hierarchical Roles Configuration
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Bean
- AccessDecisionVoter hierarchyVoter() {
- RoleHierarchy hierarchy = new RoleHierarchyImpl();
- hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" +
- "ROLE_STAFF > ROLE_USER\n" +
- "ROLE_USER > ROLE_GUEST");
- return new RoleHierarchyVoter(hierarchy);
- }
- ----
- Xml::
- +
- [source,java,role="secondary"]
- ----
- <bean id="roleVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter">
- <constructor-arg ref="roleHierarchy" />
- </bean>
- <bean id="roleHierarchy"
- class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
- <property name="hierarchy">
- <value>
- ROLE_ADMIN > ROLE_STAFF
- ROLE_STAFF > ROLE_USER
- ROLE_USER > ROLE_GUEST
- </value>
- </property>
- </bean>
- ----
- ======
- Here we have four roles in a hierarchy `ROLE_ADMIN => ROLE_STAFF => ROLE_USER => ROLE_GUEST`.
- A user who is authenticated with `ROLE_ADMIN`, will behave as if they have all four roles when security constraints are evaluated against an `AuthorizationManager` adapted to call the above `RoleHierarchyVoter`.
- The `>` symbol can be thought of as meaning "includes".
- Role hierarchies offer a convenient means of simplifying the access-control configuration data for your application and/or reducing the number of authorities which you need to assign to a user.
- For more complex requirements you may wish to define a logical mapping between the specific access-rights your application requires and the roles that are assigned to users, translating between the two when loading the user information.
- [[authz-legacy-note]]
- == Legacy Authorization Components
- [NOTE]
- Spring Security contains some legacy components.
- Since they are not yet removed, documentation is included for historical purposes.
- Their recommended replacements are above.
- [[authz-access-decision-manager]]
- === The AccessDecisionManager
- The `AccessDecisionManager` is called by the `AbstractSecurityInterceptor` and is responsible for making final access control decisions.
- The `AccessDecisionManager` interface contains three methods:
- [source,java]
- ----
- void decide(Authentication authentication, Object secureObject,
- Collection<ConfigAttribute> attrs) throws AccessDeniedException;
- boolean supports(ConfigAttribute attribute);
- boolean supports(Class clazz);
- ----
- The ``AccessDecisionManager``'s `decide` method is passed all the relevant information it needs in order to make an authorization decision.
- In particular, passing the secure `Object` enables those arguments contained in the actual secure object invocation to be inspected.
- For example, let's assume the secure object was a `MethodInvocation`.
- It would be easy to query the `MethodInvocation` for any `Customer` argument, and then implement some sort of security logic in the `AccessDecisionManager` to ensure the principal is permitted to operate on that customer.
- Implementations are expected to throw an `AccessDeniedException` if access is denied.
- The `supports(ConfigAttribute)` method is called by the `AbstractSecurityInterceptor` at startup time to determine if the `AccessDecisionManager` can process the passed `ConfigAttribute`.
- The `supports(Class)` method is called by a security interceptor implementation to ensure the configured `AccessDecisionManager` supports the type of secure object that the security interceptor will present.
- [[authz-voting-based]]
- === Voting-Based AccessDecisionManager Implementations
- Whilst users can implement their own `AccessDecisionManager` to control all aspects of authorization, Spring Security includes several `AccessDecisionManager` implementations that are based on voting.
- <<authz-access-voting>> illustrates the relevant classes.
- [[authz-access-voting]]
- .Voting Decision Manager
- image::{figures}/access-decision-voting.png[]
- Using this approach, a series of `AccessDecisionVoter` implementations are polled on an authorization decision.
- The `AccessDecisionManager` then decides whether or not to throw an `AccessDeniedException` based on its assessment of the votes.
- The `AccessDecisionVoter` interface has three methods:
- [source,java]
- ----
- int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);
- boolean supports(ConfigAttribute attribute);
- boolean supports(Class clazz);
- ----
- Concrete implementations return an `int`, with possible values being reflected in the `AccessDecisionVoter` static fields `ACCESS_ABSTAIN`, `ACCESS_DENIED` and `ACCESS_GRANTED`.
- A voting implementation will return `ACCESS_ABSTAIN` if it has no opinion on an authorization decision.
- If it does have an opinion, it must return either `ACCESS_DENIED` or `ACCESS_GRANTED`.
- There are three concrete ``AccessDecisionManager``s provided with Spring Security that tally the votes.
- The `ConsensusBased` implementation will grant or deny access based on the consensus of non-abstain votes.
- Properties are provided to control behavior in the event of an equality of votes or if all votes are abstain.
- The `AffirmativeBased` implementation will grant access if one or more `ACCESS_GRANTED` votes were received (i.e. a deny vote will be ignored, provided there was at least one grant vote).
- Like the `ConsensusBased` implementation, there is a parameter that controls the behavior if all voters abstain.
- The `UnanimousBased` provider expects unanimous `ACCESS_GRANTED` votes in order to grant access, ignoring abstains.
- It will deny access if there is any `ACCESS_DENIED` vote.
- Like the other implementations, there is a parameter that controls the behaviour if all voters abstain.
- It is possible to implement a custom `AccessDecisionManager` that tallies votes differently.
- For example, votes from a particular `AccessDecisionVoter` might receive additional weighting, whilst a deny vote from a particular voter may have a veto effect.
- [[authz-role-voter]]
- ==== RoleVoter
- The most commonly used `AccessDecisionVoter` provided with Spring Security is the simple `RoleVoter`, which treats configuration attributes as simple role names and votes to grant access if the user has been assigned that role.
- It will vote if any `ConfigAttribute` begins with the prefix `ROLE_`.
- It will vote to grant access if there is a `GrantedAuthority` which returns a `String` representation (via the `getAuthority()` method) exactly equal to one or more `ConfigAttributes` starting with the prefix `ROLE_`.
- If there is no exact match of any `ConfigAttribute` starting with `ROLE_`, the `RoleVoter` will vote to deny access.
- If no `ConfigAttribute` begins with `ROLE_`, the voter will abstain.
- [[authz-authenticated-voter]]
- ==== AuthenticatedVoter
- Another voter which we've implicitly seen is the `AuthenticatedVoter`, which can be used to differentiate between anonymous, fully-authenticated and remember-me authenticated users.
- Many sites allow certain limited access under remember-me authentication, but require a user to confirm their identity by logging in for full access.
- When we've used the attribute `IS_AUTHENTICATED_ANONYMOUSLY` to grant anonymous access, this attribute was being processed by the `AuthenticatedVoter`.
- See the Javadoc for this class for more information.
- [[authz-custom-voter]]
- ==== Custom Voters
- Obviously, you can also implement a custom `AccessDecisionVoter` and you can put just about any access-control logic you want in it.
- It might be specific to your application (business-logic related) or it might implement some security administration logic.
- For example, you'll find a https://spring.io/blog/2009/01/03/spring-security-customization-part-2-adjusting-secured-session-in-real-time[blog article] on the Spring web site which describes how to use a voter to deny access in real-time to users whose accounts have been suspended.
- [[authz-after-invocation]]
- .After Invocation Implementation
- image::{figures}/after-invocation.png[]
- Like many other parts of Spring Security, `AfterInvocationManager` has a single concrete implementation, `AfterInvocationProviderManager`, which polls a list of ``AfterInvocationProvider``s.
- Each `AfterInvocationProvider` is allowed to modify the return object or throw an `AccessDeniedException`.
- Indeed multiple providers can modify the object, as the result of the previous provider is passed to the next in the list.
- Please be aware that if you're using `AfterInvocationManager`, you will still need configuration attributes that allow the ``MethodSecurityInterceptor``'s `AccessDecisionManager` to allow an operation.
- If you're using the typical Spring Security included `AccessDecisionManager` implementations, having no configuration attributes defined for a particular secure method invocation will cause each `AccessDecisionVoter` to abstain from voting.
- In turn, if the `AccessDecisionManager` property "`allowIfAllAbstainDecisions`" is `false`, an `AccessDeniedException` will be thrown.
- You may avoid this potential issue by either (i) setting "`allowIfAllAbstainDecisions`" to `true` (although this is generally not recommended) or (ii) simply ensure that there is at least one configuration attribute that an `AccessDecisionVoter` will vote to grant access for.
- This latter (recommended) approach is usually achieved through a `ROLE_USER` or `ROLE_AUTHENTICATED` configuration attribute.
|