authorize-http-requests.adoc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  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. Here is an example of mapping a custom authorization manager to the `my/authorized/endpoint`:
  121. .Custom Authorization Manager
  122. [tabs]
  123. ======
  124. Java::
  125. +
  126. [source,java,role="primary"]
  127. ----
  128. @Bean
  129. SecurityFilterChain web(HttpSecurity http) throws Exception {
  130. http
  131. .authorizeHttpRequests((authorize) -> authorize
  132. .requestMatchers("/my/authorized/endpoint").access(new CustomAuthorizationManager());
  133. )
  134. // ...
  135. return http.build();
  136. }
  137. ----
  138. ======
  139. Or you can provide it for all requests as seen below:
  140. .Custom Authorization Manager for All Requests
  141. [tabs]
  142. ======
  143. Java::
  144. +
  145. [source,java,role="primary"]
  146. ----
  147. @Bean
  148. SecurityFilterChain web(HttpSecurity http) throws Exception {
  149. http
  150. .authorizeHttpRequests((authorize) -> authorize
  151. .anyRequest().access(new CustomAuthorizationManager());
  152. )
  153. // ...
  154. return http.build();
  155. }
  156. ----
  157. ======
  158. By default, the `AuthorizationFilter` applies to all dispatcher types.
  159. We can configure Spring Security to not apply the authorization rules to all dispatcher types by using the `shouldFilterAllDispatcherTypes` method:
  160. .Set shouldFilterAllDispatcherTypes to false
  161. [tabs]
  162. ======
  163. Java::
  164. +
  165. [source,java,role="primary"]
  166. ----
  167. @Bean
  168. SecurityFilterChain web(HttpSecurity http) throws Exception {
  169. http
  170. .authorizeHttpRequests((authorize) -> authorize
  171. .shouldFilterAllDispatcherTypes(false)
  172. .anyRequest().authenticated()
  173. )
  174. // ...
  175. return http.build();
  176. }
  177. ----
  178. Kotlin::
  179. +
  180. [source,kotlin,role="secondary"]
  181. ----
  182. @Bean
  183. open fun web(http: HttpSecurity): SecurityFilterChain {
  184. http {
  185. authorizeHttpRequests {
  186. shouldFilterAllDispatcherTypes = false
  187. authorize(anyRequest, authenticated)
  188. }
  189. }
  190. return http.build()
  191. }
  192. ----
  193. ======
  194. Instead of setting `shouldFilterAllDispatcherTypes` to `false`, the recommended approach is to customize authorization on the dispatcher types.
  195. For example, you may want to grant all access on requests with dispatcher type `ASYNC` or `FORWARD`.
  196. .Permit ASYNC and FORWARD dispatcher type
  197. [tabs]
  198. ======
  199. Java::
  200. +
  201. [source,java,role="primary"]
  202. ----
  203. @Bean
  204. SecurityFilterChain web(HttpSecurity http) throws Exception {
  205. http
  206. .authorizeHttpRequests((authorize) -> authorize
  207. .dispatcherTypeMatchers(DispatcherType.ASYNC, DispatcherType.FORWARD).permitAll()
  208. .anyRequest().authenticated()
  209. )
  210. // ...
  211. return http.build();
  212. }
  213. ----
  214. Kotlin::
  215. +
  216. [source,kotlin,role="secondary"]
  217. ----
  218. @Bean
  219. open fun web(http: HttpSecurity): SecurityFilterChain {
  220. http {
  221. authorizeHttpRequests {
  222. authorize(DispatcherTypeRequestMatcher(DispatcherType.ASYNC, DispatcherType.FORWARD), permitAll)
  223. authorize(anyRequest, authenticated)
  224. }
  225. }
  226. return http.build()
  227. }
  228. ----
  229. ======
  230. You can also customize it to require a specific role for a dispatcher type:
  231. .Require ADMIN for Dispatcher Type ERROR
  232. [tabs]
  233. ======
  234. Java::
  235. +
  236. [source,java,role="primary"]
  237. ----
  238. @Bean
  239. SecurityFilterChain web(HttpSecurity http) throws Exception {
  240. http
  241. .authorizeHttpRequests((authorize) -> authorize
  242. .dispatcherTypeMatchers(DispatcherType.ERROR).hasRole("ADMIN")
  243. .anyRequest().authenticated()
  244. )
  245. // ...
  246. return http.build();
  247. }
  248. ----
  249. Kotlin::
  250. +
  251. [source,kotlin,role="secondary"]
  252. ----
  253. @Bean
  254. open fun web(http: HttpSecurity): SecurityFilterChain {
  255. http {
  256. authorizeHttpRequests {
  257. authorize(DispatcherTypeRequestMatcher(DispatcherType.ERROR), hasRole("ADMIN"))
  258. authorize(anyRequest, authenticated)
  259. }
  260. }
  261. return http.build()
  262. }
  263. ----
  264. ======
  265. == Request Matchers
  266. The `RequestMatcher` interface is used to determine if a request matches a given rule.
  267. We use `securityMatchers` to determine if a given `HttpSecurity` should be applied to a given request.
  268. The same way, we can use `requestMatchers` to determine the authorization rules that we should apply to a given request.
  269. Look at the following example:
  270. [tabs]
  271. ======
  272. Java::
  273. +
  274. [source,java,role="primary"]
  275. ----
  276. @Configuration
  277. @EnableWebSecurity
  278. public class SecurityConfig {
  279. @Bean
  280. public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  281. http
  282. .securityMatcher("/api/**") <1>
  283. .authorizeHttpRequests(authorize -> authorize
  284. .requestMatchers("/user/**").hasRole("USER") <2>
  285. .requestMatchers("/admin/**").hasRole("ADMIN") <3>
  286. .anyRequest().authenticated() <4>
  287. )
  288. .formLogin(withDefaults());
  289. return http.build();
  290. }
  291. }
  292. ----
  293. Kotlin::
  294. +
  295. [source,kotlin,role="secondary"]
  296. ----
  297. @Configuration
  298. @EnableWebSecurity
  299. open class SecurityConfig {
  300. @Bean
  301. open fun web(http: HttpSecurity): SecurityFilterChain {
  302. http {
  303. securityMatcher("/api/**") <1>
  304. authorizeHttpRequests {
  305. authorize("/user/**", hasRole("USER")) <2>
  306. authorize("/admin/**", hasRole("ADMIN")) <3>
  307. authorize(anyRequest, authenticated) <4>
  308. }
  309. }
  310. return http.build()
  311. }
  312. }
  313. ----
  314. ======
  315. <1> Configure `HttpSecurity` to only be applied to URLs that start with `/api/`
  316. <2> Allow access to URLs that start with `/user/` to users with the `USER` role
  317. <3> Allow access to URLs that start with `/admin/` to users with the `ADMIN` role
  318. <4> Any other request that doesn't match the rules above, will require authentication
  319. 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.
  320. You can read more about the Spring MVC integration xref:servlet/integrations/mvc.adoc[here].
  321. If you want to use a specific `RequestMatcher`, just pass an implementation to the `securityMatcher` and/or `requestMatcher` methods:
  322. [tabs]
  323. ======
  324. Java::
  325. +
  326. [source,java,role="primary"]
  327. ----
  328. import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; <1>
  329. import static org.springframework.security.web.util.matcher.RegexRequestMatcher.regexMatcher;
  330. @Configuration
  331. @EnableWebSecurity
  332. public class SecurityConfig {
  333. @Bean
  334. public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  335. http
  336. .securityMatcher(antMatcher("/api/**")) <2>
  337. .authorizeHttpRequests(authorize -> authorize
  338. .requestMatchers(antMatcher("/user/**")).hasRole("USER") <3>
  339. .requestMatchers(regexMatcher("/admin/.*")).hasRole("ADMIN") <4>
  340. .requestMatchers(new MyCustomRequestMatcher()).hasRole("SUPERVISOR") <5>
  341. .anyRequest().authenticated()
  342. )
  343. .formLogin(withDefaults());
  344. return http.build();
  345. }
  346. }
  347. public class MyCustomRequestMatcher implements RequestMatcher {
  348. @Override
  349. public boolean matches(HttpServletRequest request) {
  350. // ...
  351. }
  352. }
  353. ----
  354. Kotlin::
  355. +
  356. [source,kotlin,role="secondary"]
  357. ----
  358. import org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher <1>
  359. import org.springframework.security.web.util.matcher.RegexRequestMatcher.regexMatcher
  360. @Configuration
  361. @EnableWebSecurity
  362. open class SecurityConfig {
  363. @Bean
  364. open fun web(http: HttpSecurity): SecurityFilterChain {
  365. http {
  366. securityMatcher(antMatcher("/api/**")) <2>
  367. authorizeHttpRequests {
  368. authorize(antMatcher("/user/**"), hasRole("USER")) <3>
  369. authorize(regexMatcher("/admin/**"), hasRole("ADMIN")) <4>
  370. authorize(MyCustomRequestMatcher(), hasRole("SUPERVISOR")) <5>
  371. authorize(anyRequest, authenticated)
  372. }
  373. }
  374. return http.build()
  375. }
  376. }
  377. ----
  378. ======
  379. <1> Import the static factory methods from `AntPathRequestMatcher` and `RegexRequestMatcher` to create `RequestMatcher` instances.
  380. <2> Configure `HttpSecurity` to only be applied to URLs that start with `/api/`, using `AntPathRequestMatcher`
  381. <3> Allow access to URLs that start with `/user/` to users with the `USER` role, using `AntPathRequestMatcher`
  382. <4> Allow access to URLs that start with `/admin/` to users with the `ADMIN` role, using `RegexRequestMatcher`
  383. <5> Allow access to URLs that match the `MyCustomRequestMatcher` to users with the `SUPERVISOR` role, using a custom `RequestMatcher`
  384. == Expressions
  385. It is recommended that you use type-safe authorization managers instead of SpEL.
  386. However, `WebExpressionAuthorizationManager` is available to help migrate legacy SpEL.
  387. To use `WebExpressionAuthorizationManager`, you can construct one with the expression you are trying to migrate, like so:
  388. [tabs]
  389. ======
  390. Java::
  391. +
  392. [source,java,role="primary"]
  393. ----
  394. .requestMatchers("/test/**").access(new WebExpressionAuthorizationManager("hasRole('ADMIN') && hasRole('USER')"))
  395. ----
  396. Kotlin::
  397. +
  398. [source,kotlin,role="secondary"]
  399. ----
  400. .requestMatchers("/test/**").access(WebExpressionAuthorizationManager("hasRole('ADMIN') && hasRole('USER')"))
  401. ----
  402. ======
  403. 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:
  404. [tabs]
  405. ======
  406. Java::
  407. +
  408. [source,java,role="primary"]
  409. ----
  410. .requestMatchers("/test/**").access((authentication, context) ->
  411. new AuthorizationDecision(webSecurity.check(authentication.get(), context.getRequest())))
  412. ----
  413. Kotlin::
  414. +
  415. [source,kotlin,role="secondary"]
  416. ----
  417. .requestMatchers("/test/**").access((authentication, context): AuthorizationManager<RequestAuthorizationContext> ->
  418. AuthorizationDecision(webSecurity.check(authentication.get(), context.getRequest())))
  419. ----
  420. ======
  421. 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)`.
  422. If you are not able to do that, you can configure a `DefaultHttpSecurityExpressionHandler` with a bean resolver and supply that to `WebExpressionAuthorizationManager#setExpressionhandler`.