|
| 1 | + |
| 2 | +[[authz-arch]] |
| 3 | +== Authorization Architecture |
| 4 | + |
| 5 | + |
| 6 | +[[authz-authorities]] |
| 7 | +=== Authorities |
| 8 | +As we saw in the <<tech-granted-authority,technical overview>>, all `Authentication` implementations store a list of `GrantedAuthority` objects. |
| 9 | +These represent the authorities that have been granted to the principal. |
| 10 | +the `GrantedAuthority` objects are inserted into the `Authentication` object by the `AuthenticationManager` and are later read by `AccessDecisionManager` s when making authorization decisions. |
| 11 | + |
| 12 | +`GrantedAuthority` is an interface with only one method: |
| 13 | + |
| 14 | +[source,java] |
| 15 | +---- |
| 16 | +
|
| 17 | +String getAuthority(); |
| 18 | +
|
| 19 | +---- |
| 20 | + |
| 21 | +This method allows |
| 22 | + `AccessDecisionManager` s to obtain a precise `String` representation of the `GrantedAuthority`. |
| 23 | +By returning a representation as a `String`, a `GrantedAuthority` can be easily "read" by most `AccessDecisionManager` s. |
| 24 | +If a `GrantedAuthority` cannot be precisely represented as a `String`, the `GrantedAuthority` is considered "complex" and `getAuthority()` must return `null`. |
| 25 | + |
| 26 | +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. |
| 27 | +Representing this complex `GrantedAuthority` as a `String` would be quite difficult, and as a result the `getAuthority()` method should return `null`. |
| 28 | +This will indicate to any `AccessDecisionManager` that it will need to specifically support the `GrantedAuthority` implementation in order to understand its contents. |
| 29 | + |
| 30 | +Spring Security includes one concrete `GrantedAuthority` implementation, `SimpleGrantedAuthority`. |
| 31 | +This allows any user-specified `String` to be converted into a `GrantedAuthority`. |
| 32 | +All `AuthenticationProvider` s included with the security architecture use `SimpleGrantedAuthority` to populate the `Authentication` object. |
| 33 | + |
| 34 | + |
| 35 | +[[authz-pre-invocation]] |
| 36 | +=== Pre-Invocation Handling |
| 37 | +As we've also seen in the <<secure-objects,Technical Overview>> chapter, Spring Security provides interceptors which control access to secure objects such as method invocations or web requests. |
| 38 | +A pre-invocation decision on whether the invocation is allowed to proceed is made by the `AccessDecisionManager`. |
| 39 | + |
| 40 | + |
| 41 | +[[authz-access-decision-manager]] |
| 42 | +==== The AccessDecisionManager |
| 43 | +The `AccessDecisionManager` is called by the `AbstractSecurityInterceptor` and is responsible for making final access control decisions. |
| 44 | +the `AccessDecisionManager` interface contains three methods: |
| 45 | + |
| 46 | +[source,java] |
| 47 | +---- |
| 48 | +void decide(Authentication authentication, Object secureObject, |
| 49 | + Collection<ConfigAttribute> attrs) throws AccessDeniedException; |
| 50 | +
|
| 51 | +boolean supports(ConfigAttribute attribute); |
| 52 | +
|
| 53 | +boolean supports(Class clazz); |
| 54 | +---- |
| 55 | + |
| 56 | +The ``AccessDecisionManager``'s `decide` method is passed all the relevant information it needs in order to make an authorization decision. |
| 57 | +In particular, passing the secure `Object` enables those arguments contained in the actual secure object invocation to be inspected. |
| 58 | +For example, let's assume the secure object was a `MethodInvocation`. |
| 59 | +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. |
| 60 | +Implementations are expected to throw an `AccessDeniedException` if access is denied. |
| 61 | + |
| 62 | +The `supports(ConfigAttribute)` method is called by the `AbstractSecurityInterceptor` at startup time to determine if the `AccessDecisionManager` can process the passed `ConfigAttribute`. |
| 63 | +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. |
| 64 | + |
| 65 | +[[authz-voting-based]] |
| 66 | +==== Voting-Based AccessDecisionManager Implementations |
| 67 | +Whilst users can implement their own `AccessDecisionManager` to control all aspects of authorization, Spring Security includes several `AccessDecisionManager` implementations that are based on voting. |
| 68 | +<<authz-access-voting>> illustrates the relevant classes. |
| 69 | + |
| 70 | +[[authz-access-voting]] |
| 71 | +.Voting Decision Manager |
| 72 | +image::images/access-decision-voting.png[] |
| 73 | + |
| 74 | + |
| 75 | + |
| 76 | +Using this approach, a series of `AccessDecisionVoter` implementations are polled on an authorization decision. |
| 77 | +The `AccessDecisionManager` then decides whether or not to throw an `AccessDeniedException` based on its assessment of the votes. |
| 78 | + |
| 79 | +The `AccessDecisionVoter` interface has three methods: |
| 80 | + |
| 81 | +[source,java] |
| 82 | +---- |
| 83 | +int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs); |
| 84 | +
|
| 85 | +boolean supports(ConfigAttribute attribute); |
| 86 | +
|
| 87 | +boolean supports(Class clazz); |
| 88 | +---- |
| 89 | + |
| 90 | +Concrete implementations return an `int`, with possible values being reflected in the `AccessDecisionVoter` static fields `ACCESS_ABSTAIN`, `ACCESS_DENIED` and `ACCESS_GRANTED`. |
| 91 | +A voting implementation will return `ACCESS_ABSTAIN` if it has no opinion on an authorization decision. |
| 92 | +If it does have an opinion, it must return either `ACCESS_DENIED` or `ACCESS_GRANTED`. |
| 93 | + |
| 94 | +There are three concrete `AccessDecisionManager` s provided with Spring Security that tally the votes. |
| 95 | +The `ConsensusBased` implementation will grant or deny access based on the consensus of non-abstain votes. |
| 96 | +Properties are provided to control behavior in the event of an equality of votes or if all votes are abstain. |
| 97 | +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). |
| 98 | +Like the `ConsensusBased` implementation, there is a parameter that controls the behavior if all voters abstain. |
| 99 | +The `UnanimousBased` provider expects unanimous `ACCESS_GRANTED` votes in order to grant access, ignoring abstains. |
| 100 | +It will deny access if there is any `ACCESS_DENIED` vote. |
| 101 | +Like the other implementations, there is a parameter that controls the behaviour if all voters abstain. |
| 102 | + |
| 103 | +It is possible to implement a custom `AccessDecisionManager` that tallies votes differently. |
| 104 | +For example, votes from a particular `AccessDecisionVoter` might receive additional weighting, whilst a deny vote from a particular voter may have a veto effect. |
| 105 | + |
| 106 | + |
| 107 | +[[authz-role-voter]] |
| 108 | +===== RoleVoter |
| 109 | +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. |
| 110 | + |
| 111 | +It will vote if any `ConfigAttribute` begins with the prefix `ROLE_`. |
| 112 | +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_`. |
| 113 | +If there is no exact match of any `ConfigAttribute` starting with `ROLE_`, the `RoleVoter` will vote to deny access. |
| 114 | +If no `ConfigAttribute` begins with `ROLE_`, the voter will abstain. |
| 115 | + |
| 116 | + |
| 117 | +[[authz-authenticated-voter]] |
| 118 | +===== AuthenticatedVoter |
| 119 | +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. |
| 120 | +Many sites allow certain limited access under remember-me authentication, but require a user to confirm their identity by logging in for full access. |
| 121 | + |
| 122 | +When we've used the attribute `IS_AUTHENTICATED_ANONYMOUSLY` to grant anonymous access, this attribute was being processed by the `AuthenticatedVoter`. |
| 123 | +See the Javadoc for this class for more information. |
| 124 | + |
| 125 | + |
| 126 | +[[authz-custom-voter]] |
| 127 | +===== Custom Voters |
| 128 | +Obviously, you can also implement a custom `AccessDecisionVoter` and you can put just about any access-control logic you want in it. |
| 129 | +It might be specific to your application (business-logic related) or it might implement some security administration logic. |
| 130 | +For example, you'll find a http://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. |
| 131 | + |
| 132 | + |
| 133 | +[[authz-after-invocation-handling]] |
| 134 | +=== After Invocation Handling |
| 135 | +Whilst the `AccessDecisionManager` is called by the `AbstractSecurityInterceptor` before proceeding with the secure object invocation, some applications need a way of modifying the object actually returned by the secure object invocation. |
| 136 | +Whilst you could easily implement your own AOP concern to achieve this, Spring Security provides a convenient hook that has several concrete implementations that integrate with its ACL capabilities. |
| 137 | + |
| 138 | +<<authz-after-invocation>> illustrates Spring Security's `AfterInvocationManager` and its concrete implementations. |
| 139 | + |
| 140 | +[[authz-after-invocation]] |
| 141 | +.After Invocation Implementation |
| 142 | +image::images/after-invocation.png[] |
| 143 | + |
| 144 | +Like many other parts of Spring Security, `AfterInvocationManager` has a single concrete implementation, `AfterInvocationProviderManager`, which polls a list of `AfterInvocationProvider` s. |
| 145 | +Each `AfterInvocationProvider` is allowed to modify the return object or throw an `AccessDeniedException`. |
| 146 | +Indeed multiple providers can modify the object, as the result of the previous provider is passed to the next in the list. |
| 147 | + |
| 148 | +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. |
| 149 | +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. |
| 150 | +In turn, if the `AccessDecisionManager` property "`allowIfAllAbstainDecisions`" is `false`, an `AccessDeniedException` will be thrown. |
| 151 | +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. |
| 152 | +This latter (recommended) approach is usually achieved through a `ROLE_USER` or `ROLE_AUTHENTICATED` configuration attribute. |
| 153 | + |
| 154 | + |
| 155 | +[[authz-hierarchical-roles]] |
| 156 | +=== Hierarchical Roles |
| 157 | +It is a common requirement that a particular role in an application should automatically "include" other roles. |
| 158 | +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. |
| 159 | +To achieve this, you can either make sure that all admin users are also assigned the "user" role. |
| 160 | +Alternatively, you can modify every access constraint which requires the "user" role to also include the "admin" role. |
| 161 | +This can get quite complicated if you have a lot of different roles in your application. |
| 162 | + |
| 163 | +The use of a role-hierarchy allows you to configure which roles (or authorities) should include others. |
| 164 | +An extended version of Spring Security's <<authz-role-voter,RoleVoter>>, `RoleHierarchyVoter`, is configured with a `RoleHierarchy`, from which it obtains all the "reachable authorities" which the user is assigned. |
| 165 | +A typical configuration might look like this: |
| 166 | + |
| 167 | +[source,xml] |
| 168 | +---- |
| 169 | +
|
| 170 | +<bean id="roleVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter"> |
| 171 | + <constructor-arg ref="roleHierarchy" /> |
| 172 | +</bean> |
| 173 | +<bean id="roleHierarchy" |
| 174 | + class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl"> |
| 175 | + <property name="hierarchy"> |
| 176 | + <value> |
| 177 | + ROLE_ADMIN > ROLE_STAFF |
| 178 | + ROLE_STAFF > ROLE_USER |
| 179 | + ROLE_USER > ROLE_GUEST |
| 180 | + </value> |
| 181 | + </property> |
| 182 | +</bean> |
| 183 | +---- |
| 184 | + |
| 185 | +Here we have four roles in a hierarchy `ROLE_ADMIN => ROLE_STAFF => ROLE_USER => ROLE_GUEST`. |
| 186 | +A user who is authenticated with `ROLE_ADMIN`, will behave as if they have all four roles when security constraints are evaluated against an `AccessDecisionManager` cconfigured with the above `RoleHierarchyVoter`. |
| 187 | +The `>` symbol can be thought of as meaning "includes". |
| 188 | + |
| 189 | +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. |
| 190 | +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. |
0 commit comments