architecture.adoc 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. // from the original documentation
  2. [[authz-arch]]
  3. = Authorization Architecture
  4. :figures: servlet/authorization
  5. This section describes the Spring Security architecture that applies to authorization.
  6. [[authz-authorities]]
  7. == Authorities
  8. xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[`Authentication`] discusses how 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` instances when making authorization decisions.
  11. The `GrantedAuthority` interface has only one method:
  12. [source,java]
  13. ----
  14. String getAuthority();
  15. ----
  16. This method is used by an
  17. `AuthorizationManager` instance to obtain a precise `String` representation of the `GrantedAuthority`.
  18. By returning a representation as a `String`, a `GrantedAuthority` can be easily "read" by most `AuthorizationManager` implementations.
  19. If a `GrantedAuthority` cannot be precisely represented as a `String`, the `GrantedAuthority` is considered "complex" and `getAuthority()` must return `null`.
  20. 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.
  21. Representing this complex `GrantedAuthority` as a `String` would be quite difficult. As a result, the `getAuthority()` method should return `null`.
  22. This indicates to any `AuthorizationManager` that it needs to support the specific `GrantedAuthority` implementation to understand its contents.
  23. Spring Security includes one concrete `GrantedAuthority` implementation: `SimpleGrantedAuthority`.
  24. This implementation lets any user-specified `String` be converted into a `GrantedAuthority`.
  25. All `AuthenticationProvider` instances included with the security architecture use `SimpleGrantedAuthority` to populate the `Authentication` object.
  26. [[jc-method-security-custom-granted-authority-defaults]]
  27. By default, role-based authorization rules include `ROLE_` as a prefix.
  28. This means that if there is an authorization rule that requires a security context to have a role of "USER", Spring Security will by default look for a `GrantedAuthority#getAuthority` that returns "ROLE_USER".
  29. You can customize this with `GrantedAuthorityDefaults`.
  30. `GrantedAuthorityDefaults` exists to allow customizing the prefix to use for role-based authorization rules.
  31. You can configure the authorization rules to use a different prefix by exposing a `GrantedAuthorityDefaults` bean, like so:
  32. .Custom MethodSecurityExpressionHandler
  33. [tabs]
  34. ======
  35. Java::
  36. +
  37. [source,java,role="primary"]
  38. ----
  39. @Bean
  40. static GrantedAuthorityDefaults grantedAuthorityDefaults() {
  41. return new GrantedAuthorityDefaults("MYPREFIX_");
  42. }
  43. ----
  44. Kotlin::
  45. +
  46. [source,kotlin,role="secondary"]
  47. ----
  48. companion object {
  49. @Bean
  50. fun grantedAuthorityDefaults() : GrantedAuthorityDefaults {
  51. return GrantedAuthorityDefaults("MYPREFIX_");
  52. }
  53. }
  54. ----
  55. Xml::
  56. +
  57. [source,xml,role="secondary"]
  58. ----
  59. <bean id="grantedAuthorityDefaults" class="org.springframework.security.config.core.GrantedAuthorityDefaults">
  60. <constructor-arg value="MYPREFIX_"/>
  61. </bean>
  62. ----
  63. ======
  64. [TIP]
  65. ====
  66. You expose `GrantedAuthorityDefaults` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes
  67. ====
  68. [[authz-pre-invocation]]
  69. == Invocation Handling
  70. Spring Security provides interceptors that control access to secure objects, such as method invocations or web requests.
  71. A pre-invocation decision on whether the invocation is allowed to proceed is made by `AuthorizationManager` instances.
  72. Also post-invocation decisions on whether a given value may be returned is made by `AuthorizationManager` instances.
  73. === The AuthorizationManager
  74. `AuthorizationManager` supersedes both <<authz-legacy-note,`AccessDecisionManager` and `AccessDecisionVoter`>>.
  75. Applications that customize an `AccessDecisionManager` or `AccessDecisionVoter` are encouraged to <<authz-voter-adaptation,change to using `AuthorizationManager`>>.
  76. ``AuthorizationManager``s are called by Spring Security's xref:servlet/authorization/authorize-http-requests.adoc[request-based], xref:servlet/authorization/method-security.adoc[method-based], and xref:servlet/integrations/websocket.adoc[message-based] authorization components and are responsible for making final access control decisions.
  77. The `AuthorizationManager` interface contains two methods:
  78. [source,java]
  79. ----
  80. AuthorizationDecision check(Supplier<Authentication> authentication, Object secureObject);
  81. default AuthorizationDecision verify(Supplier<Authentication> authentication, Object secureObject)
  82. throws AccessDeniedException {
  83. // ...
  84. }
  85. ----
  86. The ``AuthorizationManager``'s `check` method is passed all the relevant information it needs in order to make an authorization decision.
  87. In particular, passing the secure `Object` enables those arguments contained in the actual secure object invocation to be inspected.
  88. For example, let's assume the secure object was a `MethodInvocation`.
  89. 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.
  90. 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.
  91. `verify` calls `check` and subsequently throws an `AccessDeniedException` in the case of a negative `AuthorizationDecision`.
  92. [[authz-delegate-authorization-manager]]
  93. === Delegate-based AuthorizationManager Implementations
  94. 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.
  95. `RequestMatcherDelegatingAuthorizationManager` will match the request with the most appropriate delegate `AuthorizationManager`.
  96. For method security, you can use `AuthorizationManagerBeforeMethodInterceptor` and `AuthorizationManagerAfterMethodInterceptor`.
  97. <<authz-authorization-manager-implementations>> illustrates the relevant classes.
  98. [[authz-authorization-manager-implementations]]
  99. .Authorization Manager Implementations
  100. image::{figures}/authorizationhierarchy.png[]
  101. Using this approach, a composition of `AuthorizationManager` implementations can be polled on an authorization decision.
  102. [[authz-authority-authorization-manager]]
  103. ==== AuthorityAuthorizationManager
  104. The most common `AuthorizationManager` provided with Spring Security is `AuthorityAuthorizationManager`.
  105. It is configured with a given set of authorities to look for on the current `Authentication`.
  106. It will return positive `AuthorizationDecision` should the `Authentication` contain any of the configured authorities.
  107. It will return a negative `AuthorizationDecision` otherwise.
  108. [[authz-authenticated-authorization-manager]]
  109. ==== AuthenticatedAuthorizationManager
  110. Another manager is the `AuthenticatedAuthorizationManager`.
  111. It can be used to differentiate between anonymous, fully-authenticated and remember-me authenticated users.
  112. Many sites allow certain limited access under remember-me authentication, but require a user to confirm their identity by logging in for full access.
  113. [[authz-authorization-managers]]
  114. ==== AuthorizationManagers
  115. There are also helpful static factories in `AuthenticationManagers` for composing individual ``AuthenticationManager``s into more sophisticated expressions.
  116. [[authz-custom-authorization-manager]]
  117. ==== Custom Authorization Managers
  118. Obviously, you can also implement a custom `AuthorizationManager` and you can put just about any access-control logic you want in it.
  119. It might be specific to your application (business-logic related) or it might implement some security administration logic.
  120. For example, you can create an implementation that can query Open Policy Agent or your own authorization database.
  121. [TIP]
  122. 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.
  123. You can achieve the same outcome by implementing `AuthorizationManager` instead.
  124. [[authz-voter-adaptation]]
  125. == Adapting AccessDecisionManager and AccessDecisionVoters
  126. Previous to `AuthorizationManager`, Spring Security published <<authz-legacy-note,`AccessDecisionManager` and `AccessDecisionVoter`>>.
  127. In some cases, like migrating an older application, it may be desirable to introduce an `AuthorizationManager` that invokes an `AccessDecisionManager` or `AccessDecisionVoter`.
  128. To call an existing `AccessDecisionManager`, you can do:
  129. .Adapting an AccessDecisionManager
  130. [tabs]
  131. ======
  132. Java::
  133. +
  134. [source,java,role="primary"]
  135. ----
  136. @Component
  137. public class AccessDecisionManagerAuthorizationManagerAdapter implements AuthorizationManager {
  138. private final AccessDecisionManager accessDecisionManager;
  139. private final SecurityMetadataSource securityMetadataSource;
  140. @Override
  141. public AuthorizationDecision check(Supplier<Authentication> authentication, Object object) {
  142. try {
  143. Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
  144. this.accessDecisionManager.decide(authentication.get(), object, attributes);
  145. return new AuthorizationDecision(true);
  146. } catch (AccessDeniedException ex) {
  147. return new AuthorizationDecision(false);
  148. }
  149. }
  150. @Override
  151. public void verify(Supplier<Authentication> authentication, Object object) {
  152. Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
  153. this.accessDecisionManager.decide(authentication.get(), object, attributes);
  154. }
  155. }
  156. ----
  157. ======
  158. And then wire it into your `SecurityFilterChain`.
  159. Or to only call an `AccessDecisionVoter`, you can do:
  160. .Adapting an AccessDecisionVoter
  161. [tabs]
  162. ======
  163. Java::
  164. +
  165. [source,java,role="primary"]
  166. ----
  167. @Component
  168. public class AccessDecisionVoterAuthorizationManagerAdapter implements AuthorizationManager {
  169. private final AccessDecisionVoter accessDecisionVoter;
  170. private final SecurityMetadataSource securityMetadataSource;
  171. @Override
  172. public AuthorizationDecision check(Supplier<Authentication> authentication, Object object) {
  173. Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
  174. int decision = this.accessDecisionVoter.vote(authentication.get(), object, attributes);
  175. switch (decision) {
  176. case ACCESS_GRANTED:
  177. return new AuthorizationDecision(true);
  178. case ACCESS_DENIED:
  179. return new AuthorizationDecision(false);
  180. }
  181. return null;
  182. }
  183. }
  184. ----
  185. ======
  186. And then wire it into your `SecurityFilterChain`.
  187. [[authz-hierarchical-roles]]
  188. == Hierarchical Roles
  189. It is a common requirement that a particular role in an application should automatically "include" other roles.
  190. 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.
  191. To achieve this, you can either make sure that all admin users are also assigned the "user" role.
  192. Alternatively, you can modify every access constraint which requires the "user" role to also include the "admin" role.
  193. This can get quite complicated if you have a lot of different roles in your application.
  194. The use of a role-hierarchy allows you to configure which roles (or authorities) should include others.
  195. 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.
  196. A typical configuration might look like this:
  197. .Hierarchical Roles Configuration
  198. [tabs]
  199. ======
  200. Java::
  201. +
  202. [source,java,role="primary"]
  203. ----
  204. @Bean
  205. static RoleHierarchy roleHierarchy() {
  206. var hierarchy = new RoleHierarchyImpl();
  207. hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" +
  208. "ROLE_STAFF > ROLE_USER\n" +
  209. "ROLE_USER > ROLE_GUEST");
  210. }
  211. // and, if using method security also add
  212. @Bean
  213. static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
  214. DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
  215. expressionHandler.setRoleHierarchy(roleHierarchy);
  216. return expressionHandler;
  217. }
  218. ----
  219. Xml::
  220. +
  221. [source,java,role="secondary"]
  222. ----
  223. <bean id="roleHierarchy"
  224. class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
  225. <property name="hierarchy">
  226. <value>
  227. ROLE_ADMIN > ROLE_STAFF
  228. ROLE_STAFF > ROLE_USER
  229. ROLE_USER > ROLE_GUEST
  230. </value>
  231. </property>
  232. </bean>
  233. <!-- and, if using method security also add -->
  234. <bean id="methodSecurityExpressionHandler"
  235. class="org.springframework.security.access.expression.method.MethodSecurityExpressionHandler">
  236. <property ref="roleHierarchy"/>
  237. </bean>
  238. ----
  239. ======
  240. [NOTE]
  241. `RoleHierarchy` bean configuration is not yet ported over to `@EnableMethodSecurity`.
  242. As such this example is using `AccessDecisionVoter`.
  243. If you need `RoleHierarchy` support for method security, please continue using `@EnableGlobalMethodSecurity` until https://github.com/spring-projects/spring-security/issues/12783 is complete.
  244. Here we have four roles in a hierarchy `ROLE_ADMIN => ROLE_STAFF => ROLE_USER => ROLE_GUEST`.
  245. 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`.
  246. The `>` symbol can be thought of as meaning "includes".
  247. 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.
  248. 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.
  249. [[authz-legacy-note]]
  250. == Legacy Authorization Components
  251. [NOTE]
  252. Spring Security contains some legacy components.
  253. Since they are not yet removed, documentation is included for historical purposes.
  254. Their recommended replacements are above.
  255. [[authz-access-decision-manager]]
  256. === The AccessDecisionManager
  257. The `AccessDecisionManager` is called by the `AbstractSecurityInterceptor` and is responsible for making final access control decisions.
  258. The `AccessDecisionManager` interface contains three methods:
  259. [source,java]
  260. ----
  261. void decide(Authentication authentication, Object secureObject,
  262. Collection<ConfigAttribute> attrs) throws AccessDeniedException;
  263. boolean supports(ConfigAttribute attribute);
  264. boolean supports(Class clazz);
  265. ----
  266. The `decide` method of the `AccessDecisionManager` is passed all the relevant information it needs to make an authorization decision.
  267. In particular, passing the secure `Object` lets those arguments contained in the actual secure object invocation be inspected.
  268. For example, assume the secure object is a `MethodInvocation`.
  269. You can 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.
  270. Implementations are expected to throw an `AccessDeniedException` if access is denied.
  271. The `supports(ConfigAttribute)` method is called by the `AbstractSecurityInterceptor` at startup time to determine if the `AccessDecisionManager` can process the passed `ConfigAttribute`.
  272. 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 presents.
  273. [[authz-voting-based]]
  274. === Voting-Based AccessDecisionManager Implementations
  275. While users can implement their own `AccessDecisionManager` to control all aspects of authorization, Spring Security includes several `AccessDecisionManager` implementations that are based on voting.
  276. <<authz-access-voting>> describes the relevant classes.
  277. The following image shows the `AccessDecisionManager` interface:
  278. [[authz-access-voting]]
  279. .Voting Decision Manager
  280. image::{figures}/access-decision-voting.png[]
  281. By using this approach, a series of `AccessDecisionVoter` implementations are polled on an authorization decision.
  282. The `AccessDecisionManager` then decides whether or not to throw an `AccessDeniedException` based on its assessment of the votes.
  283. The `AccessDecisionVoter` interface has three methods:
  284. [source,java]
  285. ----
  286. int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);
  287. boolean supports(ConfigAttribute attribute);
  288. boolean supports(Class clazz);
  289. ----
  290. Concrete implementations return an `int`, with possible values being reflected in the `AccessDecisionVoter` static fields named `ACCESS_ABSTAIN`, `ACCESS_DENIED` and `ACCESS_GRANTED`.
  291. A voting implementation returns `ACCESS_ABSTAIN` if it has no opinion on an authorization decision.
  292. If it does have an opinion, it must return either `ACCESS_DENIED` or `ACCESS_GRANTED`.
  293. There are three concrete `AccessDecisionManager` implementations provided with Spring Security to tally the votes.
  294. The `ConsensusBased` implementation grants or denies access based on the consensus of non-abstain votes.
  295. Properties are provided to control behavior in the event of an equality of votes or if all votes are abstain.
  296. The `AffirmativeBased` implementation grants access if one or more `ACCESS_GRANTED` votes were received (in other words, a deny vote will be ignored, provided there was at least one grant vote).
  297. Like the `ConsensusBased` implementation, there is a parameter that controls the behavior if all voters abstain.
  298. The `UnanimousBased` provider expects unanimous `ACCESS_GRANTED` votes in order to grant access, ignoring abstains.
  299. It denies access if there is any `ACCESS_DENIED` vote.
  300. Like the other implementations, there is a parameter that controls the behavior if all voters abstain.
  301. You can implement a custom `AccessDecisionManager` that tallies votes differently.
  302. For example, votes from a particular `AccessDecisionVoter` might receive additional weighting, while a deny vote from a particular voter may have a veto effect.
  303. [[authz-role-voter]]
  304. ==== RoleVoter
  305. The most commonly used `AccessDecisionVoter` provided with Spring Security is the `RoleVoter`, which treats configuration attributes as role names and votes to grant access if the user has been assigned that role.
  306. It votes if any `ConfigAttribute` begins with the `ROLE_` prefix.
  307. It votes to grant access if there is a `GrantedAuthority` that returns a `String` representation (from the `getAuthority()` method) exactly equal to one or more `ConfigAttributes` that start with the `ROLE_` prefix.
  308. If there is no exact match of any `ConfigAttribute` starting with `ROLE_`, `RoleVoter` votes to deny access.
  309. If no `ConfigAttribute` begins with `ROLE_`, the voter abstains.
  310. [[authz-authenticated-voter]]
  311. ==== AuthenticatedVoter
  312. Another voter which we have implicitly seen is the `AuthenticatedVoter`, which can be used to differentiate between anonymous, fully-authenticated, and remember-me authenticated users.
  313. Many sites allow certain limited access under remember-me authentication but require a user to confirm their identity by logging in for full access.
  314. When we have used the `IS_AUTHENTICATED_ANONYMOUSLY` attribute to grant anonymous access, this attribute was being processed by the `AuthenticatedVoter`.
  315. For more information, see
  316. {security-api-url}org/springframework/security/access/vote/AuthenticatedVoter.html[`AuthenticatedVoter`].
  317. [[authz-custom-voter]]
  318. ==== Custom Voters
  319. You can also implement a custom `AccessDecisionVoter` and put just about any access-control logic you want in it.
  320. It might be specific to your application (business-logic related) or it might implement some security administration logic.
  321. For example, on the Spring web site, you can find a https://spring.io/blog/2009/01/03/spring-security-customization-part-2-adjusting-secured-session-in-real-time[blog article] that describes how to use a voter to deny access in real-time to users whose accounts have been suspended.
  322. [[authz-after-invocation]]
  323. .After Invocation Implementation
  324. image::{figures}/after-invocation.png[]
  325. Like many other parts of Spring Security, `AfterInvocationManager` has a single concrete implementation, `AfterInvocationProviderManager`, which polls a list of ``AfterInvocationProvider``s.
  326. Each `AfterInvocationProvider` is allowed to modify the return object or throw an `AccessDeniedException`.
  327. Indeed multiple providers can modify the object, as the result of the previous provider is passed to the next in the list.
  328. 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.
  329. 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.
  330. In turn, if the `AccessDecisionManager` property "`allowIfAllAbstainDecisions`" is `false`, an `AccessDeniedException` will be thrown.
  331. 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.
  332. This latter (recommended) approach is usually achieved through a `ROLE_USER` or `ROLE_AUTHENTICATED` configuration attribute.