authorize-http-requests.adoc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. [[servlet-authorization-authorizationfilter]]
  2. = Authorize HttpServletRequests with AuthorizationFilter
  3. :figures: servlet/authorization
  4. This section builds on xref:servlet/architecture.adoc#servlet-architecture[Servlet Architecture and Implementation] by digging deeper into how xref:servlet/authorization/index.adoc#servlet-authorization[authorization] works within Servlet-based applications.
  5. [NOTE]
  6. `AuthorizationFilter` supersedes xref:servlet/authorization/authorize-requests.adoc#servlet-authorization-filtersecurityinterceptor[`FilterSecurityInterceptor`].
  7. To remain backward compatible, `FilterSecurityInterceptor` remains the default.
  8. This section discusses how `AuthorizationFilter` works and how to override the default configuration.
  9. The {security-api-url}org/springframework/security/web/access/intercept/AuthorizationFilter.html[`AuthorizationFilter`] provides xref:servlet/authorization/index.adoc#servlet-authorization[authorization] for ``HttpServletRequest``s.
  10. It is inserted into the xref:servlet/architecture.adoc#servlet-filterchainproxy[FilterChainProxy] as one of the xref:servlet/architecture.adoc#servlet-security-filters[Security Filters].
  11. You can override the default when you declare a `SecurityFilterChain`.
  12. Instead of using xref:servlet/authorization/authorize-http-requests.adoc#servlet-authorize-requests-defaults[`authorizeRequests`], use `authorizeHttpRequests`, like so:
  13. .Use authorizeHttpRequests
  14. [tabs]
  15. ======
  16. Java::
  17. +
  18. [source,java,role="primary"]
  19. ----
  20. @Bean
  21. SecurityFilterChain web(HttpSecurity http) throws AuthenticationException {
  22. http
  23. .authorizeHttpRequests((authorize) -> authorize
  24. .anyRequest().authenticated();
  25. )
  26. // ...
  27. return http.build();
  28. }
  29. ----
  30. ======
  31. This improves on `authorizeRequests` in a number of ways:
  32. 1. Uses the simplified `AuthorizationManager` API instead of metadata sources, config attributes, decision managers, and voters.
  33. This simplifies reuse and customization.
  34. 2. Delays `Authentication` lookup.
  35. Instead of the authentication needing to be looked up for every request, it will only look it up in requests where an authorization decision requires authentication.
  36. 3. Bean-based configuration support.
  37. When `authorizeHttpRequests` is used instead of `authorizeRequests`, then {security-api-url}org/springframework/security/web/access/intercept/AuthorizationFilter.html[`AuthorizationFilter`] is used instead of xref:servlet/authorization/authorize-requests.adoc#servlet-authorization-filtersecurityinterceptor[`FilterSecurityInterceptor`].
  38. .Authorize HttpServletRequest
  39. image::{figures}/authorizationfilter.png[]
  40. * image:{icondir}/number_1.png[] First, the `AuthorizationFilter` obtains an xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[Authentication] from the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder].
  41. It wraps this in an `Supplier` in order to delay lookup.
  42. * image:{icondir}/number_2.png[] Second, it passes the `Supplier<Authentication>` and the `HttpServletRequest` to the xref:servlet/architecture.adoc#authz-authorization-manager[`AuthorizationManager`].
  43. ** image:{icondir}/number_3.png[] If authorization is denied, an `AccessDeniedException` is thrown.
  44. In this case the xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] handles the `AccessDeniedException`.
  45. ** image:{icondir}/number_4.png[] If access is granted, `AuthorizationFilter` continues with the xref:servlet/architecture.adoc#servlet-filters-review[FilterChain] which allows the application to process normally.
  46. We can configure Spring Security to have different rules by adding more rules in order of precedence.
  47. .Authorize Requests
  48. [tabs]
  49. ======
  50. Java::
  51. +
  52. [source,java,role="primary"]
  53. ----
  54. @Bean
  55. SecurityFilterChain web(HttpSecurity http) throws Exception {
  56. http
  57. // ...
  58. .authorizeHttpRequests(authorize -> authorize // <1>
  59. .requestMatchers("/resources/**", "/signup", "/about").permitAll() // <2>
  60. .requestMatchers("/admin/**").hasRole("ADMIN") // <3>
  61. .requestMatchers("/db/**").access(new WebExpressionAuthorizationManager("hasRole('ADMIN') and hasRole('DBA')")) // <4>
  62. // .requestMatchers("/db/**").access(AuthorizationManagers.allOf(AuthorityAuthorizationManager.hasRole("ADMIN"), AuthorityAuthorizationManager.hasRole("DBA"))) // <5>
  63. .anyRequest().denyAll() // <6>
  64. );
  65. return http.build();
  66. }
  67. ----
  68. ======
  69. <1> There are multiple authorization rules specified.
  70. Each rule is considered in the order they were declared.
  71. <2> We specified multiple URL patterns that any user can access.
  72. Specifically, any user can access a request if the URL starts with "/resources/", equals "/signup", or equals "/about".
  73. <3> Any URL that starts with "/admin/" will be restricted to users who have the role "ROLE_ADMIN".
  74. You will notice that since we are invoking the `hasRole` method we do not need to specify the "ROLE_" prefix.
  75. <4> Any URL that starts with "/db/" requires the user to have both "ROLE_ADMIN" and "ROLE_DBA".
  76. You will notice that since we are using the `hasRole` expression we do not need to specify the "ROLE_" prefix.
  77. <5> The same rule from 4, could be written by combining multiple `AuthorizationManager`.
  78. <6> Any URL that has not already been matched on is denied access.
  79. This is a good strategy if you do not want to accidentally forget to update your authorization rules.
  80. You can take a bean-based approach by constructing your own xref:servlet/authorization/architecture.adoc#authz-delegate-authorization-manager[`RequestMatcherDelegatingAuthorizationManager`] like so:
  81. .Configure RequestMatcherDelegatingAuthorizationManager
  82. [tabs]
  83. ======
  84. Java::
  85. +
  86. [source,java,role="primary"]
  87. ----
  88. @Bean
  89. SecurityFilterChain web(HttpSecurity http, AuthorizationManager<RequestAuthorizationContext> access)
  90. throws AuthenticationException {
  91. http
  92. .authorizeHttpRequests((authorize) -> authorize
  93. .anyRequest().access(access)
  94. )
  95. // ...
  96. return http.build();
  97. }
  98. @Bean
  99. AuthorizationManager<RequestAuthorizationContext> requestMatcherAuthorizationManager(HandlerMappingIntrospector introspector) {
  100. MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector);
  101. RequestMatcher permitAll =
  102. new AndRequestMatcher(
  103. mvcMatcherBuilder.pattern("/resources/**"),
  104. mvcMatcherBuilder.pattern("/signup"),
  105. mvcMatcherBuilder.pattern("/about"));
  106. RequestMatcher admin = mvcMatcherBuilder.pattern("/admin/**");
  107. RequestMatcher db = mvcMatcherBuilder.pattern("/db/**");
  108. RequestMatcher any = AnyRequestMatcher.INSTANCE;
  109. AuthorizationManager<HttpServletRequest> manager = RequestMatcherDelegatingAuthorizationManager.builder()
  110. .add(permitAll, (context) -> new AuthorizationDecision(true))
  111. .add(admin, AuthorityAuthorizationManager.hasRole("ADMIN"))
  112. .add(db, AuthorityAuthorizationManager.hasRole("DBA"))
  113. .add(any, new AuthenticatedAuthorizationManager())
  114. .build();
  115. return (context) -> manager.check(context.getRequest());
  116. }
  117. ----
  118. ======
  119. You can also wire xref:servlet/authorization/architecture.adoc#authz-custom-authorization-manager[your own custom authorization managers] for any request matcher.
  120. [[custom-authorization-manager]]
  121. Here is an example of mapping a custom authorization manager to the `my/authorized/endpoint`:
  122. .Custom Authorization Manager
  123. [tabs]
  124. ======
  125. Java::
  126. +
  127. [source,java,role="primary"]
  128. ----
  129. @Bean
  130. SecurityFilterChain web(HttpSecurity http) throws Exception {
  131. http
  132. .authorizeHttpRequests((authorize) -> authorize
  133. .requestMatchers("/my/authorized/endpoint").access(new CustomAuthorizationManager());
  134. )
  135. // ...
  136. return http.build();
  137. }
  138. ----
  139. ======
  140. Or you can provide it for all requests as seen below:
  141. .Custom Authorization Manager for All Requests
  142. [tabs]
  143. ======
  144. Java::
  145. +
  146. [source,java,role="primary"]
  147. ----
  148. @Bean
  149. SecurityFilterChain web(HttpSecurity http) throws Exception {
  150. http
  151. .authorizeHttpRequests((authorize) -> authorize
  152. .anyRequest().access(new CustomAuthorizationManager());
  153. )
  154. // ...
  155. return http.build();
  156. }
  157. ----
  158. ======
  159. By default, the `AuthorizationFilter` does not apply to `DispatcherType.ERROR` and `DispatcherType.ASYNC`.
  160. We can configure Spring Security to apply the authorization rules to all dispatcher types by using the `shouldFilterAllDispatcherTypes` method:
  161. .Set shouldFilterAllDispatcherTypes to true
  162. [tabs]
  163. ======
  164. Java::
  165. +
  166. [source,java,role="primary"]
  167. ----
  168. @Bean
  169. SecurityFilterChain web(HttpSecurity http) throws Exception {
  170. http
  171. .authorizeHttpRequests((authorize) -> authorize
  172. .shouldFilterAllDispatcherTypes(true)
  173. .anyRequest.authenticated()
  174. )
  175. // ...
  176. return http.build();
  177. }
  178. ----
  179. Kotlin::
  180. +
  181. [source,kotlin,role="secondary"]
  182. ----
  183. @Bean
  184. open fun web(http: HttpSecurity): SecurityFilterChain {
  185. http {
  186. authorizeHttpRequests {
  187. shouldFilterAllDispatcherTypes = true
  188. authorize(anyRequest, authenticated)
  189. }
  190. }
  191. return http.build()
  192. }
  193. ----
  194. ======
  195. Now with the authorization rules applying to all dispatcher types, you have more control of the authorization on them.
  196. For example, you may want to configure `shouldFilterAllDispatcherTypes` to `true` but not apply authorization on requests with dispatcher type `ASYNC` or `FORWARD`.
  197. .Permit ASYNC and FORWARD dispatcher type
  198. [tabs]
  199. ======
  200. Java::
  201. +
  202. [source,java,role="primary"]
  203. ----
  204. @Bean
  205. SecurityFilterChain web(HttpSecurity http) throws Exception {
  206. http
  207. .authorizeHttpRequests((authorize) -> authorize
  208. .shouldFilterAllDispatcherTypes(true)
  209. .dispatcherTypeMatchers(DispatcherType.ASYNC, DispatcherType.FORWARD).permitAll()
  210. .anyRequest().authenticated()
  211. )
  212. // ...
  213. return http.build();
  214. }
  215. ----
  216. Kotlin::
  217. +
  218. [source,kotlin,role="secondary"]
  219. ----
  220. @Bean
  221. open fun web(http: HttpSecurity): SecurityFilterChain {
  222. http {
  223. authorizeHttpRequests {
  224. shouldFilterAllDispatcherTypes = true
  225. authorize(DispatcherTypeRequestMatcher(DispatcherType.ASYNC, DispatcherType.FORWARD), permitAll)
  226. authorize(anyRequest, authenticated)
  227. }
  228. }
  229. return http.build()
  230. }
  231. ----
  232. ======
  233. You can also customize it to require a specific role for a dispatcher type:
  234. .Require ADMIN for Dispatcher Type ERROR
  235. [tabs]
  236. ======
  237. Java::
  238. +
  239. [source,java,role="primary"]
  240. ----
  241. @Bean
  242. SecurityFilterChain web(HttpSecurity http) throws Exception {
  243. http
  244. .authorizeHttpRequests((authorize) -> authorize
  245. .shouldFilterAllDispatcherTypes(true)
  246. .dispatcherTypeMatchers(DispatcherType.ERROR).hasRole("ADMIN")
  247. .anyRequest().authenticated()
  248. )
  249. // ...
  250. return http.build();
  251. }
  252. ----
  253. Kotlin::
  254. +
  255. [source,kotlin,role="secondary"]
  256. ----
  257. @Bean
  258. open fun web(http: HttpSecurity): SecurityFilterChain {
  259. http {
  260. authorizeHttpRequests {
  261. shouldFilterAllDispatcherTypes = true
  262. authorize(DispatcherTypeRequestMatcher(DispatcherType.ERROR), hasRole("ADMIN"))
  263. authorize(anyRequest, authenticated)
  264. }
  265. }
  266. return http.build()
  267. }
  268. ----
  269. ======
  270. == Request Matchers
  271. The `RequestMatcher` interface is used to determine if a request matches a given rule.
  272. We use `securityMatchers` to determine if a given `HttpSecurity` should be applied to a given request.
  273. The same way, we can use `requestMatchers` to determine the authorization rules that we should apply to a given request.
  274. Look at the following example:
  275. [tabs]
  276. ======
  277. Java::
  278. +
  279. [source,java,role="primary"]
  280. ----
  281. @Configuration
  282. @EnableWebSecurity
  283. public class SecurityConfig {
  284. @Bean
  285. public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  286. http
  287. .securityMatcher("/api/**") <1>
  288. .authorizeHttpRequests(authorize -> authorize
  289. .requestMatchers("/user/**").hasRole("USER") <2>
  290. .requestMatchers("/admin/**").hasRole("ADMIN") <3>
  291. .anyRequest().authenticated() <4>
  292. )
  293. .formLogin(withDefaults());
  294. return http.build();
  295. }
  296. }
  297. ----
  298. Kotlin::
  299. +
  300. [source,kotlin,role="secondary"]
  301. ----
  302. @Configuration
  303. @EnableWebSecurity
  304. open class SecurityConfig {
  305. @Bean
  306. open fun web(http: HttpSecurity): SecurityFilterChain {
  307. http {
  308. securityMatcher("/api/**") <1>
  309. authorizeHttpRequests {
  310. authorize("/user/**", hasRole("USER")) <2>
  311. authorize("/admin/**", hasRole("ADMIN")) <3>
  312. authorize(anyRequest, authenticated) <4>
  313. }
  314. }
  315. return http.build()
  316. }
  317. }
  318. ----
  319. ======
  320. <1> Configure `HttpSecurity` to only be applied to URLs that start with `/api/`
  321. <2> Allow access to URLs that start with `/user/` to users with the `USER` role
  322. <3> Allow access to URLs that start with `/admin/` to users with the `ADMIN` role
  323. <4> Any other request that doesn't match the rules above, will require authentication
  324. The `securityMatcher(s)` and `requestMatcher(s)` methods will decide which `RequestMatcher` implementation fits best for your application: If Spring MVC is in the classpath, then `MvcRequestMatcher` will be used, otherwise, `AntPathRequestMatcher` will be used.
  325. You can read more about the Spring MVC integration xref:servlet/integrations/mvc.adoc[here].
  326. If you want to use a specific `RequestMatcher`, just pass an implementation to the `securityMatcher` and/or `requestMatcher` methods:
  327. [tabs]
  328. ======
  329. Java::
  330. +
  331. [source,java,role="primary"]
  332. ----
  333. import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; <1>
  334. import static org.springframework.security.web.util.matcher.RegexRequestMatcher.regexMatcher;
  335. @Configuration
  336. @EnableWebSecurity
  337. public class SecurityConfig {
  338. @Bean
  339. public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  340. http
  341. .securityMatcher(antMatcher("/api/**")) <2>
  342. .authorizeHttpRequests(authorize -> authorize
  343. .requestMatchers(antMatcher("/user/**")).hasRole("USER") <3>
  344. .requestMatchers(regexMatcher("/admin/.*")).hasRole("ADMIN") <4>
  345. .requestMatchers(new MyCustomRequestMatcher()).hasRole("SUPERVISOR") <5>
  346. .anyRequest().authenticated()
  347. )
  348. .formLogin(withDefaults());
  349. return http.build();
  350. }
  351. }
  352. public class MyCustomRequestMatcher implements RequestMatcher {
  353. @Override
  354. public boolean matches(HttpServletRequest request) {
  355. // ...
  356. }
  357. }
  358. ----
  359. Kotlin::
  360. +
  361. [source,kotlin,role="secondary"]
  362. ----
  363. import org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher <1>
  364. import org.springframework.security.web.util.matcher.RegexRequestMatcher.regexMatcher
  365. @Configuration
  366. @EnableWebSecurity
  367. open class SecurityConfig {
  368. @Bean
  369. open fun web(http: HttpSecurity): SecurityFilterChain {
  370. http {
  371. securityMatcher(antMatcher("/api/**")) <2>
  372. authorizeHttpRequests {
  373. authorize(antMatcher("/user/**"), hasRole("USER")) <3>
  374. authorize(regexMatcher("/admin/**"), hasRole("ADMIN")) <4>
  375. authorize(MyCustomRequestMatcher(), hasRole("SUPERVISOR")) <5>
  376. authorize(anyRequest, authenticated)
  377. }
  378. }
  379. return http.build()
  380. }
  381. }
  382. ----
  383. ======
  384. <1> Import the static factory methods from `AntPathRequestMatcher` and `RegexRequestMatcher` to create `RequestMatcher` instances.
  385. <2> Configure `HttpSecurity` to only be applied to URLs that start with `/api/`, using `AntPathRequestMatcher`
  386. <3> Allow access to URLs that start with `/user/` to users with the `USER` role, using `AntPathRequestMatcher`
  387. <4> Allow access to URLs that start with `/admin/` to users with the `ADMIN` role, using `RegexRequestMatcher`
  388. <5> Allow access to URLs that match the `MyCustomRequestMatcher` to users with the `SUPERVISOR` role, using a custom `RequestMatcher`
  389. == Expressions
  390. It is recommended that you use type-safe authorization managers instead of SpEL.
  391. However, `WebExpressionAuthorizationManager` is available to help migrate legacy SpEL.
  392. To use `WebExpressionAuthorizationManager`, you can construct one with the expression you are trying to migrate, like so:
  393. [tabs]
  394. ======
  395. Java::
  396. +
  397. [source,java,role="primary"]
  398. ----
  399. .requestMatchers("/test/**").access(new WebExpressionAuthorizationManager("hasRole('ADMIN') && hasRole('USER')"))
  400. ----
  401. Kotlin::
  402. +
  403. [source,kotlin,role="secondary"]
  404. ----
  405. .requestMatchers("/test/**").access(WebExpressionAuthorizationManager("hasRole('ADMIN') && hasRole('USER')"))
  406. ----
  407. ======
  408. If you are referring to a bean in your expression like so: `@webSecurity.check(authentication, request)`, it's recommended that you instead call the bean directly, which will look something like the following:
  409. [tabs]
  410. ======
  411. Java::
  412. +
  413. [source,java,role="primary"]
  414. ----
  415. .requestMatchers("/test/**").access((authentication, context) ->
  416. new AuthorizationDecision(webSecurity.check(authentication.get(), context.getRequest())))
  417. ----
  418. Kotlin::
  419. +
  420. [source,kotlin,role="secondary"]
  421. ----
  422. .requestMatchers("/test/**").access((authentication, context): AuthorizationManager<RequestAuthorizationContext> ->
  423. AuthorizationDecision(webSecurity.check(authentication.get(), context.getRequest())))
  424. ----
  425. ======
  426. For complex instructions that include bean references as well as other expressions, it is recommended that you change those to implement `AuthorizationManager` and refer to them by calling `.access(AuthorizationManager)`.
  427. If you are not able to do that, you can configure a `DefaultHttpSecurityExpressionHandler` with a bean resolver and supply that to `WebExpressionAuthorizationManager#setExpressionhandler`.