2
0

method-security.adoc 96 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295
  1. [[jc-method]]
  2. = Method Security
  3. :figures: servlet/authorization
  4. In addition to xref:servlet/authorization/authorize-http-requests.adoc[modeling authorization at the request level], Spring Security also supports modeling at the method level.
  5. [[activate-method-security]]
  6. You can activate it in your application by annotating any `@Configuration` class with `@EnableMethodSecurity` or adding `<method-security>` to any XML configuration file, like so:
  7. [tabs]
  8. ======
  9. Java::
  10. +
  11. [source,java,role="primary"]
  12. ----
  13. @EnableMethodSecurity
  14. ----
  15. Kotlin::
  16. +
  17. [source,kotlin,role="secondary"]
  18. ----
  19. @EnableMethodSecurity
  20. ----
  21. Xml::
  22. +
  23. [source,xml,role="secondary"]
  24. ----
  25. <sec:method-security/>
  26. ----
  27. ======
  28. Then, you are immediately able to annotate any Spring-managed class or method with <<use-preauthorize, `@PreAuthorize`>>, <<use-postauthorize,`@PostAuthorize`>>, <<use-prefilter,`@PreFilter`>>, and <<use-postfilter,`@PostFilter`>> to authorize method invocations, including the input parameters and return values.
  29. [NOTE]
  30. {spring-boot-reference-url}reference/using/build-systems.html#using.build-systems.starters[Spring Boot Starter Security] does not activate method-level authorization by default.
  31. Method Security supports many other use cases as well including <<use-aspectj, AspectJ support>>, <<use-programmatic-authorization,custom annotations>>, and several configuration points.
  32. Consider learning about the following use cases:
  33. * <<migration-enableglobalmethodsecurity, Migrating from `@EnableGlobalMethodSecurity`>>
  34. * Understanding <<method-security-architecture,how method security works>> and reasons to use it
  35. * Comparing <<request-vs-method,request-level and method-level authorization>>
  36. * Authorizing methods with <<use-preauthorize,`@PreAuthorize`>> and <<use-postauthorize,`@PostAuthorize`>>
  37. * Providing <<fallback-values-authorization-denied,fallback values when authorization is denied>>
  38. * Filtering methods with <<use-prefilter,`@PreFilter`>> and <<use-postfilter,`@PostFilter`>>
  39. * Authorizing methods with <<use-jsr250,JSR-250 annotations>>
  40. * Authorizing methods with <<use-aspectj,AspectJ expressions>>
  41. * Integrating with <<weave-aspectj,AspectJ byte-code weaving>>
  42. * Coordinating with <<changing-the-order,@Transactional and other AOP-based annotations>>
  43. * Customizing <<customizing-expression-handling,SpEL expression handling>>
  44. * Integrating with <<custom-authorization-managers,custom authorization systems>>
  45. [[method-security-architecture]]
  46. == How Method Security Works
  47. Spring Security's method authorization support is handy for:
  48. * Extracting fine-grained authorization logic; for example, when the method parameters and return values contribute to the authorization decision.
  49. * Enforcing security at the service layer
  50. * Stylistically favoring annotation-based over `HttpSecurity`-based configuration
  51. And since Method Security is built using {spring-framework-reference-url}core.html#aop-api[Spring AOP], you have access to all its expressive power to override Spring Security's defaults as needed.
  52. As already mentioned, you begin by adding `@EnableMethodSecurity` to a `@Configuration` class or `<sec:method-security/>` in a Spring XML configuration file.
  53. [[use-method-security]]
  54. [NOTE]
  55. ====
  56. This annotation and XML element supercede `@EnableGlobalMethodSecurity` and `<sec:global-method-security/>`, respectively.
  57. They offer the following improvements:
  58. 1. Uses the simplified `AuthorizationManager` API instead of metadata sources, config attributes, decision managers, and voters.
  59. This simplifies reuse and customization.
  60. 2. Favors direct bean-based configuration, instead of requiring extending `GlobalMethodSecurityConfiguration` to customize beans
  61. 3. Is built using native Spring AOP, removing abstractions and allowing you to use Spring AOP building blocks to customize
  62. 4. Checks for conflicting annotations to ensure an unambiguous security configuration
  63. 5. Complies with JSR-250
  64. 6. Enables `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFilter` by default
  65. If you are using `@EnableGlobalMethodSecurity` or `<global-method-security/>`, these are now deprecated, and you are encouraged to migrate.
  66. ====
  67. Method authorization is a combination of before- and after-method authorization.
  68. Consider a service bean that is annotated in the following way:
  69. [tabs]
  70. ======
  71. Java::
  72. +
  73. [source,java,role="primary"]
  74. ----
  75. @Service
  76. public class MyCustomerService {
  77. @PreAuthorize("hasAuthority('permission:read')")
  78. @PostAuthorize("returnObject.owner == authentication.name")
  79. public Customer readCustomer(String id) { ... }
  80. }
  81. ----
  82. Kotlin::
  83. +
  84. [source,kotlin,role="secondary"]
  85. ----
  86. @Service
  87. open class MyCustomerService {
  88. @PreAuthorize("hasAuthority('permission:read')")
  89. @PostAuthorize("returnObject.owner == authentication.name")
  90. fun readCustomer(id: String): Customer { ... }
  91. }
  92. ----
  93. ======
  94. A given invocation to `MyCustomerService#readCustomer` may look something like this when Method Security <<activate-method-security,is activated>>:
  95. [.invert-dark]
  96. image::{figures}/methodsecurity.png[]
  97. 1. Spring AOP invokes its proxy method for `readCustomer`. Among the proxy's other advisors, it invokes an javadoc:org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor[] that matches <<annotation-method-pointcuts,the `@PreAuthorize` pointcut>>
  98. 2. The interceptor invokes javadoc:org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager[`PreAuthorizeAuthorizationManager#check`]
  99. 3. The authorization manager uses a `MethodSecurityExpressionHandler` to parse the annotation's <<authorization-expressions,SpEL expression>> and constructs a corresponding `EvaluationContext` from a `MethodSecurityExpressionRoot` containing xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[a `Supplier<Authentication>`] and `MethodInvocation`.
  100. 4. The interceptor uses this context to evaluate the expression; specifically, it reads xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[the `Authentication`] from the `Supplier` and checks whether it has `permission:read` in its collection of xref:servlet/authorization/architecture.adoc#authz-authorities[authorities]
  101. 5. If the evaluation passes, then Spring AOP proceeds to invoke the method.
  102. 6. If not, the interceptor publishes an `AuthorizationDeniedEvent` and throws an javadoc:org.springframework.security.access.AccessDeniedException[] which xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[the `ExceptionTranslationFilter`] catches and returns a 403 status code to the response
  103. 7. After the method returns, Spring AOP invokes an javadoc:org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor[] that matches <<annotation-method-pointcuts,the `@PostAuthorize` pointcut>>, operating the same as above, but with javadoc:org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager[]
  104. 8. If the evaluation passes (in this case, the return value belongs to the logged-in user), processing continues normally
  105. 9. If not, the interceptor publishes an `AuthorizationDeniedEvent` and throws an javadoc:org.springframework.security.access.AccessDeniedException[], which xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[the `ExceptionTranslationFilter`] catches and returns a 403 status code to the response
  106. [NOTE]
  107. If the method is not being called in the context of an HTTP request, you will likely need to handle the `AccessDeniedException` yourself
  108. [[unanimous-based-authorization-decisions]]
  109. === Multiple Annotations Are Computed In Series
  110. As demonstrated above, if a method invocation involves multiple <<authorizing-with-annotations,Method Security annotations>>, each of those is processed one at a time.
  111. This means that they can collectively be thought of as being "anded" together.
  112. In other words, for an invocation to be authorized, all annotation inspections need to pass authorization.
  113. [[repeated-annotations]]
  114. === Repeated Annotations Are Not Supported
  115. That said, it is not supported to repeat the same annotation on the same method.
  116. For example, you cannot place `@PreAuthorize` twice on the same method.
  117. Instead, use SpEL's boolean support or its support for delegating to a separate bean.
  118. [[annotation-method-pointcuts]]
  119. === Each Annotation Has Its Own Pointcut
  120. Each annotation has its own pointcut instance that looks for that annotation or its <<meta-annotations,meta-annotation>> counterparts across the entire object hierarchy, starting at <<class-or-interface-annotations,the method and its enclosing class>>.
  121. // FIXME: AuthorizationMethodPointcuts is package private and Javadoc is not published You can see the specifics of this in javadoc:org.springframework.security.authorization.method.AuthorizationMethodPointcuts[].
  122. [[annotation-method-interceptors]]
  123. === Each Annotation Has Its Own Method Interceptor
  124. Each annotation has its own dedicated method interceptor.
  125. The reason for this is to make things more composable.
  126. For example, if needed, you can disable the Spring Security defaults and <<_enabling_certain_annotations,publish only the `@PostAuthorize` method interceptor>>.
  127. The method interceptors are as follows:
  128. * For <<use-preauthorize,`@PreAuthorize`>>, Spring Security uses javadoc:org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor[`AuthorizationManagerBeforeMethodInterceptor#preAuthorize`], which in turn uses javadoc:org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager[]
  129. * For <<use-postauthorize,`@PostAuthorize`>>, Spring Security uses javadoc:org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor[`AuthorizationManagerAfterMethodInterceptor#postAuthorize`], which in turn uses javadoc:org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager[]
  130. * For <<use-prefilter,`@PreFilter`>>, Spring Security uses javadoc:org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor[]
  131. * For <<use-postfilter,`@PostFilter`>>, Spring Security uses javadoc:org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor[]
  132. * For <<use-secured,`@Secured`>>, Spring Security uses javadoc:org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor[`AuthorizationManagerBeforeMethodInterceptor#secured`], which in turn uses javadoc:org.springframework.security.authorization.method.SecuredAuthorizationManager[]
  133. * For JSR-250 annotations, Spring Security uses javadoc:org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor[`AuthorizationManagerBeforeMethodInterceptor#jsr250`], which in turn uses javadoc:org.springframework.security.authorization.method.Jsr250AuthorizationManager[]
  134. Generally speaking, you can consider the following listing as representative of what interceptors Spring Security publishes when you add `@EnableMethodSecurity`:
  135. [tabs]
  136. ======
  137. Java::
  138. +
  139. [source,java,role="primary"]
  140. ----
  141. @Bean
  142. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  143. static Advisor preAuthorizeMethodInterceptor() {
  144. return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
  145. }
  146. @Bean
  147. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  148. static Advisor postAuthorizeMethodInterceptor() {
  149. return AuthorizationManagerAfterMethodInterceptor.postAuthorize();
  150. }
  151. @Bean
  152. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  153. static Advisor preFilterMethodInterceptor() {
  154. return AuthorizationManagerBeforeMethodInterceptor.preFilter();
  155. }
  156. @Bean
  157. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  158. static Advisor postFilterMethodInterceptor() {
  159. return AuthorizationManagerAfterMethodInterceptor.postFilter();
  160. }
  161. ----
  162. ======
  163. [[favor-granting-authorities]]
  164. === Favor Granting Authorities Over Complicated SpEL Expressions
  165. Quite often it can be tempting to introduce a complicated SpEL expression like the following:
  166. [tabs]
  167. ======
  168. Java::
  169. +
  170. [source,java,role="primary"]
  171. ----
  172. @PreAuthorize("hasAuthority('permission:read') || hasRole('ADMIN')")
  173. ----
  174. Kotlin::
  175. +
  176. [source,kotlin,role="secondary"]
  177. ----
  178. @PreAuthorize("hasAuthority('permission:read') || hasRole('ADMIN')")
  179. ----
  180. ======
  181. However, you could instead grant `permission:read` to those with `ROLE_ADMIN`.
  182. One way to do this is with a `RoleHierarchy` like so:
  183. [tabs]
  184. ======
  185. Java::
  186. +
  187. [source,java,role="primary"]
  188. ----
  189. @Bean
  190. static RoleHierarchy roleHierarchy() {
  191. return RoleHierarchyImpl.fromHierarchy("ROLE_ADMIN > permission:read");
  192. }
  193. ----
  194. Kotlin::
  195. +
  196. [source,kotlin,role="secondary"]
  197. ----
  198. companion object {
  199. @Bean
  200. fun roleHierarchy(): RoleHierarchy {
  201. return RoleHierarchyImpl.fromHierarchy("ROLE_ADMIN > permission:read")
  202. }
  203. }
  204. ----
  205. Xml::
  206. +
  207. [source,xml,role="secondary"]
  208. ----
  209. <bean id="roleHierarchy"
  210. class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl" factory-method="fromHierarchy">
  211. <constructor-arg value="ROLE_ADMIN > permission:read"/>
  212. </bean>
  213. ----
  214. ======
  215. and then <<customizing-expression-handling,set that in a `MethodSecurityExpressionHandler` instance>>.
  216. This then allows you to have a simpler <<use-preauthorize,`@PreAuthorize`>> expression like this one:
  217. [tabs]
  218. ======
  219. Java::
  220. +
  221. [source,java,role="primary"]
  222. ----
  223. @PreAuthorize("hasAuthority('permission:read')")
  224. ----
  225. Kotlin::
  226. +
  227. [source,kotlin,role="secondary"]
  228. ----
  229. @PreAuthorize("hasAuthority('permission:read')")
  230. ----
  231. ======
  232. Or, where possible, adapt application-specific authorization logic into granted authorities at login time.
  233. [[request-vs-method]]
  234. == Comparing Request-level vs Method-level Authorization
  235. When should you favor method-level authorization over xref:servlet/authorization/authorize-http-requests.adoc[request-level authorization]?
  236. Some of it comes down to taste; however, consider the following strengths list of each to help you decide.
  237. |===
  238. || *request-level* | *method-level*
  239. | *authorization type* | coarse-grained | fine-grained
  240. | *configuration location* | declared in a config class | local to method declaration
  241. | *configuration style* | DSL | Annotations
  242. | *authorization definitions* | programmatic | SpEL
  243. |===
  244. The main tradeoff seems to be where you want your authorization rules to live.
  245. [NOTE]
  246. It's important to remember that when you use annotation-based Method Security, then unannotated methods are not secured.
  247. To protect against this, declare xref:servlet/authorization/authorize-http-requests.adoc#activate-request-security[a catch-all authorization rule] in your xref:servlet/configuration/java.adoc#jc-httpsecurity[`HttpSecurity`] instance.
  248. [[authorizing-with-annotations]]
  249. == Authorizing with Annotations
  250. The primary way Spring Security enables method-level authorization support is through annotations that you can add to methods, classes, and interfaces.
  251. [[use-preauthorize]]
  252. === Authorizing Method Invocation with `@PreAuthorize`
  253. When <<activate-method-security,Method Security is active>>, you can annotate a method with the javadoc:org.springframework.security.access.prepost.PreAuthorize[format=annotation] annotation like so:
  254. [tabs]
  255. ======
  256. Java::
  257. +
  258. [source,java,role="primary"]
  259. ----
  260. @Component
  261. public class BankService {
  262. @PreAuthorize("hasRole('ADMIN')")
  263. public Account readAccount(Long id) {
  264. // ... is only invoked if the `Authentication` has the `ROLE_ADMIN` authority
  265. }
  266. }
  267. ----
  268. Kotlin::
  269. +
  270. [source,kotlin,role="secondary"]
  271. ----
  272. @Component
  273. open class BankService {
  274. @PreAuthorize("hasRole('ADMIN')")
  275. fun readAccount(id: Long): Account {
  276. // ... is only invoked if the `Authentication` has the `ROLE_ADMIN` authority
  277. }
  278. }
  279. ----
  280. ======
  281. This is meant to indicate that the method can only be invoked if the provided expression `hasRole('ADMIN')` passes.
  282. You can then xref:servlet/test/method.adoc[test the class] to confirm it is enforcing the authorization rule like so:
  283. [tabs]
  284. ======
  285. Java::
  286. +
  287. [source,java,role="primary"]
  288. ----
  289. @Autowired
  290. BankService bankService;
  291. @WithMockUser(roles="ADMIN")
  292. @Test
  293. void readAccountWithAdminRoleThenInvokes() {
  294. Account account = this.bankService.readAccount("12345678");
  295. // ... assertions
  296. }
  297. @WithMockUser(roles="WRONG")
  298. @Test
  299. void readAccountWithWrongRoleThenAccessDenied() {
  300. assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(
  301. () -> this.bankService.readAccount("12345678"));
  302. }
  303. ----
  304. Kotlin::
  305. +
  306. [source,kotlin,role="secondary"]
  307. ----
  308. @WithMockUser(roles="ADMIN")
  309. @Test
  310. fun readAccountWithAdminRoleThenInvokes() {
  311. val account: Account = this.bankService.readAccount("12345678")
  312. // ... assertions
  313. }
  314. @WithMockUser(roles="WRONG")
  315. @Test
  316. fun readAccountWithWrongRoleThenAccessDenied() {
  317. assertThatExceptionOfType(AccessDeniedException::class.java).isThrownBy {
  318. this.bankService.readAccount("12345678")
  319. }
  320. }
  321. ----
  322. ======
  323. [TIP]
  324. `@PreAuthorize` also can be a <<meta-annotations, meta-annotation>>, be defined <<class-or-interface-annotations,at the class or interface level>>, and use <<authorization-expressions, SpEL Authorization Expressions>>.
  325. While `@PreAuthorize` is quite helpful for declaring needed authorities, it can also be used to evaluate more complex <<using_method_parameters,expressions that involve the method parameters>>.
  326. [[use-postauthorize]]
  327. === Authorization Method Results with `@PostAuthorize`
  328. When Method Security is active, you can annotate a method with the javadoc:org.springframework.security.access.prepost.PostAuthorize[format=annotation] annotation like so:
  329. [tabs]
  330. ======
  331. Java::
  332. +
  333. [source,java,role="primary"]
  334. ----
  335. @Component
  336. public class BankService {
  337. @PostAuthorize("returnObject.owner == authentication.name")
  338. public Account readAccount(Long id) {
  339. // ... is only returned if the `Account` belongs to the logged in user
  340. }
  341. }
  342. ----
  343. Kotlin::
  344. +
  345. [source,kotlin,role="secondary"]
  346. ----
  347. @Component
  348. open class BankService {
  349. @PostAuthorize("returnObject.owner == authentication.name")
  350. fun readAccount(id: Long): Account {
  351. // ... is only returned if the `Account` belongs to the logged in user
  352. }
  353. }
  354. ----
  355. ======
  356. This is meant to indicate that the method can only return the value if the provided expression `returnObject.owner == authentication.name` passes.
  357. `returnObject` represents the `Account` object to be returned.
  358. You can then xref:servlet/test/method.adoc[test the class] to confirm it is enforcing the authorization rule:
  359. [tabs]
  360. ======
  361. Java::
  362. +
  363. [source,java,role="primary"]
  364. ----
  365. @Autowired
  366. BankService bankService;
  367. @WithMockUser(username="owner")
  368. @Test
  369. void readAccountWhenOwnedThenReturns() {
  370. Account account = this.bankService.readAccount("12345678");
  371. // ... assertions
  372. }
  373. @WithMockUser(username="wrong")
  374. @Test
  375. void readAccountWhenNotOwnedThenAccessDenied() {
  376. assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(
  377. () -> this.bankService.readAccount("12345678"));
  378. }
  379. ----
  380. Kotlin::
  381. +
  382. [source,kotlin,role="secondary"]
  383. ----
  384. @WithMockUser(username="owner")
  385. @Test
  386. fun readAccountWhenOwnedThenReturns() {
  387. val account: Account = this.bankService.readAccount("12345678")
  388. // ... assertions
  389. }
  390. @WithMockUser(username="wrong")
  391. @Test
  392. fun readAccountWhenNotOwnedThenAccessDenied() {
  393. assertThatExceptionOfType(AccessDeniedException::class.java).isThrownBy {
  394. this.bankService.readAccount("12345678")
  395. }
  396. }
  397. ----
  398. ======
  399. [TIP]
  400. `@PostAuthorize` also can be a <<meta-annotations,meta-annotation>>, be defined <<class-or-interface-annotations,at the class or interface level>>, and use <<authorization-expressions, SpEL Authorization Expressions>>.
  401. `@PostAuthorize` is particularly helpful when defending against https://cheatsheetseries.owasp.org/cheatsheets/Insecure_Direct_Object_Reference_Prevention_Cheat_Sheet.html[Insecure Direct Object Reference].
  402. In fact, it can be defined as a <<meta-annotations,meta-annotation>> like so:
  403. [tabs]
  404. ======
  405. Java::
  406. +
  407. [source,java,role="primary"]
  408. ----
  409. @Target({ ElementType.METHOD, ElementType.TYPE })
  410. @Retention(RetentionPolicy.RUNTIME)
  411. @PostAuthorize("returnObject.owner == authentication.name")
  412. public @interface RequireOwnership {}
  413. ----
  414. Kotlin::
  415. +
  416. [source,kotlin,role="secondary"]
  417. ----
  418. @Target(ElementType.METHOD, ElementType.TYPE)
  419. @Retention(RetentionPolicy.RUNTIME)
  420. @PostAuthorize("returnObject.owner == authentication.name")
  421. annotation class RequireOwnership
  422. ----
  423. ======
  424. Allowing you to instead annotate the service in the following way:
  425. [tabs]
  426. ======
  427. Java::
  428. +
  429. [source,java,role="primary"]
  430. ----
  431. @Component
  432. public class BankService {
  433. @RequireOwnership
  434. public Account readAccount(Long id) {
  435. // ... is only returned if the `Account` belongs to the logged in user
  436. }
  437. }
  438. ----
  439. Kotlin::
  440. +
  441. [source,kotlin,role="secondary"]
  442. ----
  443. @Component
  444. open class BankService {
  445. @RequireOwnership
  446. fun readAccount(id: Long): Account {
  447. // ... is only returned if the `Account` belongs to the logged in user
  448. }
  449. }
  450. ----
  451. ======
  452. The result is that the above method will only return the `Account` if its `owner` attribute matches the logged-in user's `name`.
  453. If not, Spring Security will throw an `AccessDeniedException` and return a 403 status code.
  454. [NOTE]
  455. =====
  456. Note that `@PostAuthorize` is not recommended for classes that perform database writes since that typically means that a database change was made before the security invariants were checked.
  457. A common example of doing this is if you have `@Transactional` and `@PostAuthorize` on the same method.
  458. Instead, read the value first, using `@PostAuthorize` on the read, and then perform the database write, should that read is authorized.
  459. If you must do something like this, you can <<changing-the-order, ensure that `@EnableTransactionManagement` comes before `@EnableMethodSecurity`>>.
  460. =====
  461. [[use-prefilter]]
  462. === Filtering Method Parameters with `@PreFilter`
  463. When Method Security is active, you can annotate a method with the javadoc:org.springframework.security.access.prepost.PreFilter[format=annotation] annotation like so:
  464. [tabs]
  465. ======
  466. Java::
  467. +
  468. [source,java,role="primary"]
  469. ----
  470. @Component
  471. public class BankService {
  472. @PreFilter("filterObject.owner == authentication.name")
  473. public Collection<Account> updateAccounts(Account... accounts) {
  474. // ... `accounts` will only contain the accounts owned by the logged-in user
  475. return updated;
  476. }
  477. }
  478. ----
  479. Kotlin::
  480. +
  481. [source,kotlin,role="secondary"]
  482. ----
  483. @Component
  484. open class BankService {
  485. @PreFilter("filterObject.owner == authentication.name")
  486. fun updateAccounts(vararg accounts: Account): Collection<Account> {
  487. // ... `accounts` will only contain the accounts owned by the logged-in user
  488. return updated
  489. }
  490. }
  491. ----
  492. ======
  493. This is meant to filter out any values from `accounts` where the expression `filterObject.owner == authentication.name` fails.
  494. `filterObject` represents each `account` in `accounts` and is used to test each `account`.
  495. You can then test the class in the following way to confirm it is enforcing the authorization rule:
  496. [tabs]
  497. ======
  498. Java::
  499. +
  500. [source,java,role="primary"]
  501. ----
  502. @Autowired
  503. BankService bankService;
  504. @WithMockUser(username="owner")
  505. @Test
  506. void updateAccountsWhenOwnedThenReturns() {
  507. Account ownedBy = ...
  508. Account notOwnedBy = ...
  509. Collection<Account> updated = this.bankService.updateAccounts(ownedBy, notOwnedBy);
  510. assertThat(updated).containsOnly(ownedBy);
  511. }
  512. ----
  513. Kotlin::
  514. +
  515. [source,kotlin,role="secondary"]
  516. ----
  517. @Autowired
  518. lateinit var bankService: BankService
  519. @WithMockUser(username="owner")
  520. @Test
  521. fun updateAccountsWhenOwnedThenReturns() {
  522. val ownedBy: Account = ...
  523. val notOwnedBy: Account = ...
  524. val updated: Collection<Account> = bankService.updateAccounts(ownedBy, notOwnedBy)
  525. assertThat(updated).containsOnly(ownedBy)
  526. }
  527. ----
  528. ======
  529. [TIP]
  530. `@PreFilter` also can be a <<meta-annotations,meta-annotation>>, be defined <<class-or-interface-annotations,at the class or interface level>>, and use <<authorization-expressions, SpEL Authorization Expressions>>.
  531. `@PreFilter` supports arrays, collections, maps, and streams (so long as the stream is still open).
  532. For example, the above `updateAccounts` declaration will function the same way as the following other four:
  533. [tabs]
  534. ======
  535. Java::
  536. +
  537. [source,java,role="primary"]
  538. ----
  539. @PreFilter("filterObject.owner == authentication.name")
  540. public Collection<Account> updateAccounts(Account[] accounts)
  541. @PreFilter("filterObject.owner == authentication.name")
  542. public Collection<Account> updateAccounts(Collection<Account> accounts)
  543. @PreFilter("filterObject.value.owner == authentication.name")
  544. public Collection<Account> updateAccounts(Map<String, Account> accounts)
  545. @PreFilter("filterObject.owner == authentication.name")
  546. public Collection<Account> updateAccounts(Stream<Account> accounts)
  547. ----
  548. Kotlin::
  549. +
  550. [source,kotlin,role="secondary"]
  551. ----
  552. @PreFilter("filterObject.owner == authentication.name")
  553. fun updateAccounts(accounts: Array<Account>): Collection<Account>
  554. @PreFilter("filterObject.owner == authentication.name")
  555. fun updateAccounts(accounts: Collection<Account>): Collection<Account>
  556. @PreFilter("filterObject.value.owner == authentication.name")
  557. fun updateAccounts(accounts: Map<String, Account>): Collection<Account>
  558. @PreFilter("filterObject.owner == authentication.name")
  559. fun updateAccounts(accounts: Stream<Account>): Collection<Account>
  560. ----
  561. ======
  562. The result is that the above method will only have the `Account` instances where their `owner` attribute matches the logged-in user's `name`.
  563. [[use-postfilter]]
  564. === Filtering Method Results with `@PostFilter`
  565. When Method Security is active, you can annotate a method with the javadoc:org.springframework.security.access.prepost.PostFilter[format=annotation] annotation like so:
  566. [tabs]
  567. ======
  568. Java::
  569. +
  570. [source,java,role="primary"]
  571. ----
  572. @Component
  573. public class BankService {
  574. @PostFilter("filterObject.owner == authentication.name")
  575. public Collection<Account> readAccounts(String... ids) {
  576. // ... the return value will be filtered to only contain the accounts owned by the logged-in user
  577. return accounts;
  578. }
  579. }
  580. ----
  581. Kotlin::
  582. +
  583. [source,kotlin,role="secondary"]
  584. ----
  585. @Component
  586. open class BankService {
  587. @PostFilter("filterObject.owner == authentication.name")
  588. fun readAccounts(vararg ids: String): Collection<Account> {
  589. // ... the return value will be filtered to only contain the accounts owned by the logged-in user
  590. return accounts
  591. }
  592. }
  593. ----
  594. ======
  595. This is meant to filter out any values from the return value where the expression `filterObject.owner == authentication.name` fails.
  596. `filterObject` represents each `account` in `accounts` and is used to test each `account`.
  597. You can then test the class like so to confirm it is enforcing the authorization rule:
  598. [tabs]
  599. ======
  600. Java::
  601. +
  602. [source,java,role="primary"]
  603. ----
  604. @Autowired
  605. BankService bankService;
  606. @WithMockUser(username="owner")
  607. @Test
  608. void readAccountsWhenOwnedThenReturns() {
  609. Collection<Account> accounts = this.bankService.updateAccounts("owner", "not-owner");
  610. assertThat(accounts).hasSize(1);
  611. assertThat(accounts.get(0).getOwner()).isEqualTo("owner");
  612. }
  613. ----
  614. Kotlin::
  615. +
  616. [source,kotlin,role="secondary"]
  617. ----
  618. @Autowired
  619. lateinit var bankService: BankService
  620. @WithMockUser(username="owner")
  621. @Test
  622. fun readAccountsWhenOwnedThenReturns() {
  623. val accounts: Collection<Account> = bankService.updateAccounts("owner", "not-owner")
  624. assertThat(accounts).hasSize(1)
  625. assertThat(accounts[0].owner).isEqualTo("owner")
  626. }
  627. ----
  628. ======
  629. [TIP]
  630. `@PostFilter` also can be a <<meta-annotations,meta-annotation>>, be defined <<class-or-interface-annotations,at the class or interface level>>, and use <<authorization-expressions, SpEL Authorization Expressions>>.
  631. `@PostFilter` supports arrays, collections, maps, and streams (so long as the stream is still open).
  632. For example, the above `readAccounts` declaration will function the same way as the following other three:
  633. [tabs]
  634. ======
  635. Java::
  636. +
  637. [source,java,role="primary"]
  638. ----
  639. @PostFilter("filterObject.owner == authentication.name")
  640. public Collection<Account> readAccounts(String... ids)
  641. @PostFilter("filterObject.owner == authentication.name")
  642. public Account[] readAccounts(String... ids)
  643. @PostFilter("filterObject.value.owner == authentication.name")
  644. public Map<String, Account> readAccounts(String... ids)
  645. @PostFilter("filterObject.owner == authentication.name")
  646. public Stream<Account> readAccounts(String... ids)
  647. ----
  648. Kotlin::
  649. +
  650. [source,kotlin,role="secondary"]
  651. ----
  652. @PostFilter("filterObject.owner == authentication.name")
  653. fun readAccounts(vararg ids: String): Collection<Account>
  654. @PostFilter("filterObject.owner == authentication.name")
  655. fun readAccounts(vararg ids: String): Array<Account>
  656. @PostFilter("filterObject.owner == authentication.name")
  657. fun readAccounts(vararg ids: String): Map<String, Account>
  658. @PostFilter("filterObject.owner == authentication.name")
  659. fun readAccounts(vararg ids: String): Stream<Account>
  660. ----
  661. ======
  662. The result is that the above method will return the `Account` instances where their `owner` attribute matches the logged-in user's `name`.
  663. [NOTE]
  664. In-memory filtering can obviously be expensive, and so be considerate of whether it is better to xref:servlet/integrations/data.adoc[filter the data in the data layer] instead.
  665. [[use-secured]]
  666. === Authorizing Method Invocation with `@Secured`
  667. javadoc:org.springframework.security.access.annotation.Secured[format=annotation] is a legacy option for authorizing invocations.
  668. <<use-preauthorize,`@PreAuthorize`>> supersedes it and is recommended instead.
  669. To use the `@Secured` annotation, you should first change your Method Security declaration to enable it like so:
  670. [tabs]
  671. ======
  672. Java::
  673. +
  674. [source,java,role="primary"]
  675. ----
  676. @EnableMethodSecurity(securedEnabled = true)
  677. ----
  678. Kotlin::
  679. +
  680. [source,kotlin,role="secondary"]
  681. ----
  682. @EnableMethodSecurity(securedEnabled = true)
  683. ----
  684. Xml::
  685. +
  686. [source,xml,role="secondary"]
  687. ----
  688. <sec:method-security secured-enabled="true"/>
  689. ----
  690. ======
  691. This will cause Spring Security to publish <<annotation-method-interceptors,the corresponding method interceptor>> that authorizes methods, classes, and interfaces annotated with `@Secured`.
  692. [[use-jsr250]]
  693. === Authorizing Method Invocation with JSR-250 Annotations
  694. In case you would like to use https://jcp.org/en/jsr/detail?id=250[JSR-250] annotations, Spring Security also supports that.
  695. <<use-preauthorize,`@PreAuthorize`>> has more expressive power and is thus recommended.
  696. To use the JSR-250 annotations, you should first change your Method Security declaration to enable them like so:
  697. [tabs]
  698. ======
  699. Java::
  700. +
  701. [source,java,role="primary"]
  702. ----
  703. @EnableMethodSecurity(jsr250Enabled = true)
  704. ----
  705. Kotlin::
  706. +
  707. [source,kotlin,role="secondary"]
  708. ----
  709. @EnableMethodSecurity(jsr250Enabled = true)
  710. ----
  711. Xml::
  712. +
  713. [source,xml,role="secondary"]
  714. ----
  715. <sec:method-security jsr250-enabled="true"/>
  716. ----
  717. ======
  718. This will cause Spring Security to publish <<annotation-method-interceptors,the corresponding method interceptor>> that authorizes methods, classes, and interfaces annotated with `@RolesAllowed`, `@PermitAll`, and `@DenyAll`.
  719. [[class-or-interface-annotations]]
  720. === Declaring Annotations at the Class or Interface Level
  721. It's also supported to have Method Security annotations at the class and interface level.
  722. If it is at the class level like so:
  723. [tabs]
  724. ======
  725. Java::
  726. +
  727. [source,java,role="primary"]
  728. ----
  729. @Controller
  730. @PreAuthorize("hasAuthority('ROLE_USER')")
  731. public class MyController {
  732. @GetMapping("/endpoint")
  733. public String endpoint() { ... }
  734. }
  735. ----
  736. Kotlin::
  737. +
  738. [source,kotlin,role="secondary"]
  739. ----
  740. @Controller
  741. @PreAuthorize("hasAuthority('ROLE_USER')")
  742. open class MyController {
  743. @GetMapping("/endpoint")
  744. fun endpoint(): String { ... }
  745. }
  746. ----
  747. ======
  748. then all methods inherit the class-level behavior.
  749. Or, if it's declared like the following at both the class and method level:
  750. [tabs]
  751. ======
  752. Java::
  753. +
  754. [source,java,role="primary"]
  755. ----
  756. @Controller
  757. @PreAuthorize("hasAuthority('ROLE_USER')")
  758. public class MyController {
  759. @GetMapping("/endpoint")
  760. @PreAuthorize("hasAuthority('ROLE_ADMIN')")
  761. public String endpoint() { ... }
  762. }
  763. ----
  764. Kotlin::
  765. +
  766. [source,kotlin,role="secondary"]
  767. ----
  768. @Controller
  769. @PreAuthorize("hasAuthority('ROLE_USER')")
  770. open class MyController {
  771. @GetMapping("/endpoint")
  772. @PreAuthorize("hasAuthority('ROLE_ADMIN')")
  773. fun endpoint(): String { ... }
  774. }
  775. ----
  776. ======
  777. then methods declaring the annotation override the class-level annotation.
  778. The same is true for interfaces, with the exception that if a class inherits the annotation from two different interfaces, then startup will fail.
  779. This is because Spring Security has no way to tell which one you want to use.
  780. In cases like this, you can resolve the ambiguity by adding the annotation to the concrete method.
  781. [[meta-annotations]]
  782. === Using Meta Annotations
  783. Method Security supports meta annotations.
  784. This means that you can take any annotation and improve readability based on your application-specific use cases.
  785. For example, you can simplify `@PreAuthorize("hasRole('ADMIN')")` to `@IsAdmin` like so:
  786. [tabs]
  787. ======
  788. Java::
  789. +
  790. [source,java,role="primary"]
  791. ----
  792. @Target({ ElementType.METHOD, ElementType.TYPE })
  793. @Retention(RetentionPolicy.RUNTIME)
  794. @PreAuthorize("hasRole('ADMIN')")
  795. public @interface IsAdmin {}
  796. ----
  797. Kotlin::
  798. +
  799. [source,kotlin,role="secondary"]
  800. ----
  801. @Target(ElementType.METHOD, ElementType.TYPE)
  802. @Retention(RetentionPolicy.RUNTIME)
  803. @PreAuthorize("hasRole('ADMIN')")
  804. annotation class IsAdmin
  805. ----
  806. ======
  807. And the result is that on your secured methods you can now do the following instead:
  808. [tabs]
  809. ======
  810. Java::
  811. +
  812. [source,java,role="primary"]
  813. ----
  814. @Component
  815. public class BankService {
  816. @IsAdmin
  817. public Account readAccount(Long id) {
  818. // ... is only returned if the `Account` belongs to the logged in user
  819. }
  820. }
  821. ----
  822. Kotlin::
  823. +
  824. [source,kotlin,role="secondary"]
  825. ----
  826. @Component
  827. open class BankService {
  828. @IsAdmin
  829. fun readAccount(id: Long): Account {
  830. // ... is only returned if the `Account` belongs to the logged in user
  831. }
  832. }
  833. ----
  834. ======
  835. This results in more readable method definitions.
  836. ==== Templating Meta-Annotation Expressions
  837. You can also opt into using meta-annotation templates, which allow for much more powerful annotation definitions.
  838. First, publish the following bean:
  839. [tabs]
  840. ======
  841. Java::
  842. +
  843. [source,java,role="primary"]
  844. ----
  845. @Bean
  846. static AnnotationTemplateExpressionDefaults templateExpressionDefaults() {
  847. return new AnnotationTemplateExpressionDefaults();
  848. }
  849. ----
  850. Kotlin::
  851. +
  852. [source,kotlin,role="secondary"]
  853. ----
  854. companion object {
  855. @Bean
  856. fun templateExpressionDefaults(): AnnotationTemplateExpressionDefaults {
  857. return AnnotationTemplateExpressionDefaults()
  858. }
  859. }
  860. ----
  861. ======
  862. Now instead of `@IsAdmin`, you can create something more powerful like `@HasRole` like so:
  863. [tabs]
  864. ======
  865. Java::
  866. +
  867. [source,java,role="primary"]
  868. ----
  869. @Target({ ElementType.METHOD, ElementType.TYPE })
  870. @Retention(RetentionPolicy.RUNTIME)
  871. @PreAuthorize("hasRole('{value}')")
  872. public @interface HasRole {
  873. String value();
  874. }
  875. ----
  876. Kotlin::
  877. +
  878. [source,kotlin,role="secondary"]
  879. ----
  880. @Target(ElementType.METHOD, ElementType.TYPE)
  881. @Retention(RetentionPolicy.RUNTIME)
  882. @PreAuthorize("hasRole('{value}')")
  883. annotation class HasRole(val value: String)
  884. ----
  885. ======
  886. And the result is that on your secured methods you can now do the following instead:
  887. [tabs]
  888. ======
  889. Java::
  890. +
  891. [source,java,role="primary"]
  892. ----
  893. @Component
  894. public class BankService {
  895. @HasRole("ADMIN")
  896. public Account readAccount(Long id) {
  897. // ... is only returned if the `Account` belongs to the logged in user
  898. }
  899. }
  900. ----
  901. Kotlin::
  902. +
  903. [source,kotlin,role="secondary"]
  904. ----
  905. @Component
  906. open class BankService {
  907. @HasRole("ADMIN")
  908. fun readAccount(id: Long): Account {
  909. // ... is only returned if the `Account` belongs to the logged in user
  910. }
  911. }
  912. ----
  913. ======
  914. Note that this works with method variables and all annotation types, too, though you will want to be careful to correctly take care of quotation marks so the resulting SpEL expression is correct.
  915. For example, consider the following `@HasAnyRole` annotation:
  916. [tabs]
  917. ======
  918. Java::
  919. +
  920. [source,java,role="primary"]
  921. ----
  922. @Target({ ElementType.METHOD, ElementType.TYPE })
  923. @Retention(RetentionPolicy.RUNTIME)
  924. @PreAuthorize("hasAnyRole({roles})")
  925. public @interface HasAnyRole {
  926. String[] roles();
  927. }
  928. ----
  929. Kotlin::
  930. +
  931. [source,kotlin,role="secondary"]
  932. ----
  933. @Target(ElementType.METHOD, ElementType.TYPE)
  934. @Retention(RetentionPolicy.RUNTIME)
  935. @PreAuthorize("hasAnyRole({roles})")
  936. annotation class HasAnyRole(val roles: Array<String>)
  937. ----
  938. ======
  939. In that case, you'll notice that you should not use the quotation marks in the expression, but instead in the parameter value like so:
  940. [tabs]
  941. ======
  942. Java::
  943. +
  944. [source,java,role="primary"]
  945. ----
  946. @Component
  947. public class BankService {
  948. @HasAnyRole(roles = { "'USER'", "'ADMIN'" })
  949. public Account readAccount(Long id) {
  950. // ... is only returned if the `Account` belongs to the logged in user
  951. }
  952. }
  953. ----
  954. Kotlin::
  955. +
  956. [source,kotlin,role="secondary"]
  957. ----
  958. @Component
  959. open class BankService {
  960. @HasAnyRole(roles = arrayOf("'USER'", "'ADMIN'"))
  961. fun readAccount(id: Long): Account {
  962. // ... is only returned if the `Account` belongs to the logged in user
  963. }
  964. }
  965. ----
  966. ======
  967. so that, once replaced, the expression becomes `@PreAuthorize("hasAnyRole('USER', 'ADMIN')")`.
  968. [[enable-annotation]]
  969. === Enabling Certain Annotations
  970. You can turn off ``@EnableMethodSecurity``'s pre-configuration and replace it with you own.
  971. You may choose to do this if you want to <<custom-authorization-managers,customize the `AuthorizationManager`>> or `Pointcut`.
  972. Or you may simply want to only enable a specific annotation, like `@PostAuthorize`.
  973. You can do this in the following way:
  974. .Only @PostAuthorize Configuration
  975. [tabs]
  976. ======
  977. Java::
  978. +
  979. [source,java,role="primary"]
  980. ----
  981. @Configuration
  982. @EnableMethodSecurity(prePostEnabled = false)
  983. class MethodSecurityConfig {
  984. @Bean
  985. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  986. Advisor postAuthorize() {
  987. return AuthorizationManagerAfterMethodInterceptor.postAuthorize();
  988. }
  989. }
  990. ----
  991. Kotlin::
  992. +
  993. [source,kotlin,role="secondary"]
  994. ----
  995. @Configuration
  996. @EnableMethodSecurity(prePostEnabled = false)
  997. class MethodSecurityConfig {
  998. @Bean
  999. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  1000. fun postAuthorize() : Advisor {
  1001. return AuthorizationManagerAfterMethodInterceptor.postAuthorize()
  1002. }
  1003. }
  1004. ----
  1005. Xml::
  1006. +
  1007. [source,xml,role="secondary"]
  1008. ----
  1009. <sec:method-security pre-post-enabled="false"/>
  1010. <aop:config/>
  1011. <bean id="postAuthorize"
  1012. class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor"
  1013. factory-method="postAuthorize"/>
  1014. ----
  1015. ======
  1016. The above snippet achieves this by first disabling Method Security's pre-configurations and then publishing <<annotation-method-interceptors, the `@PostAuthorize` interceptor>> itself.
  1017. [[use-intercept-methods]]
  1018. == Authorizing with `<intercept-methods>`
  1019. While using Spring Security's <<authorizing-with-annotations,annotation-based support>> is preferred for method security, you can also use XML to declare bean authorization rules.
  1020. If you need to declare it in your XML configuration instead, you can use xref:servlet/appendix/namespace/method-security.adoc#nsa-intercept-methods[`<intercept-methods>`] like so:
  1021. [tabs]
  1022. ======
  1023. Xml::
  1024. +
  1025. [source,xml,role="primary"]
  1026. ----
  1027. <bean class="org.mycompany.MyController">
  1028. <intercept-methods>
  1029. <protect method="get*" access="hasAuthority('read')"/>
  1030. <protect method="*" access="hasAuthority('write')"/>
  1031. </intercept-methods>
  1032. </bean>
  1033. ----
  1034. ======
  1035. [NOTE]
  1036. This only supports matching method by prefix or by name.
  1037. If your needs are more complex than that, <<authorizing-with-annotations,use annotation support>> instead.
  1038. [[use-programmatic-authorization]]
  1039. == Authorizing Methods Programmatically
  1040. As you've already seen, there are several ways that you can specify non-trivial authorization rules using <<authorization-expressions, Method Security SpEL expressions>>.
  1041. There are a number of ways that you can instead allow your logic to be Java-based instead of SpEL-based.
  1042. This gives use access the entire Java language for increased testability and flow control.
  1043. === Using a Custom Bean in SpEL
  1044. The first way to authorize a method programmatically is a two-step process.
  1045. First, declare a bean that has a method that takes a `MethodSecurityExpressionOperations` instance like the following:
  1046. [tabs]
  1047. ======
  1048. Java::
  1049. +
  1050. [source,java,role="primary"]
  1051. ----
  1052. @Component("authz")
  1053. public class AuthorizationLogic {
  1054. public boolean decide(MethodSecurityExpressionOperations operations) {
  1055. // ... authorization logic
  1056. }
  1057. }
  1058. ----
  1059. Kotlin::
  1060. +
  1061. [source,kotlin,role="secondary"]
  1062. ----
  1063. @Component("authz")
  1064. open class AuthorizationLogic {
  1065. fun decide(operations: MethodSecurityExpressionOperations): boolean {
  1066. // ... authorization logic
  1067. }
  1068. }
  1069. ----
  1070. ======
  1071. Then, reference that bean in your annotations in the following way:
  1072. [tabs]
  1073. ======
  1074. Java::
  1075. +
  1076. [source,java,role="primary"]
  1077. ----
  1078. @Controller
  1079. public class MyController {
  1080. @PreAuthorize("@authz.decide(#root)")
  1081. @GetMapping("/endpoint")
  1082. public String endpoint() {
  1083. // ...
  1084. }
  1085. }
  1086. ----
  1087. Kotlin::
  1088. +
  1089. [source,kotlin,role="secondary"]
  1090. ----
  1091. @Controller
  1092. open class MyController {
  1093. @PreAuthorize("@authz.decide(#root)")
  1094. @GetMapping("/endpoint")
  1095. fun String endpoint() {
  1096. // ...
  1097. }
  1098. }
  1099. ----
  1100. ======
  1101. Spring Security will invoke the given method on that bean for each method invocation.
  1102. What's nice about this is all your authorization logic is in a separate class that can be independently unit tested and verified for correctness.
  1103. It also has access to the full Java language.
  1104. [TIP]
  1105. In addition to returning a `Boolean`, you can also return `null` to indicate that the code abstains from making a decision.
  1106. If you want to include more information about the nature of the decision, you can instead return a custom `AuthorizationDecision` like this:
  1107. [tabs]
  1108. ======
  1109. Java::
  1110. +
  1111. [source,java,role="primary"]
  1112. ----
  1113. @Component("authz")
  1114. public class AuthorizationLogic {
  1115. public AuthorizationDecision decide(MethodSecurityExpressionOperations operations) {
  1116. // ... authorization logic
  1117. return new MyAuthorizationDecision(false, details);
  1118. }
  1119. }
  1120. ----
  1121. Kotlin::
  1122. +
  1123. [source,kotlin,role="secondary"]
  1124. ----
  1125. @Component("authz")
  1126. open class AuthorizationLogic {
  1127. fun decide(operations: MethodSecurityExpressionOperations): AuthorizationDecision {
  1128. // ... authorization logic
  1129. return MyAuthorizationDecision(false, details)
  1130. }
  1131. }
  1132. ----
  1133. ======
  1134. Or throw a custom `AuthorizationDeniedException` instance.
  1135. Note, though, that returning an object is preferred as this doesn't incur the expense of generating a stacktrace.
  1136. Then, you can access the custom details when you <<fallback-values-authorization-denied, customize how the authorization result is handled>>.
  1137. [[custom-authorization-managers]]
  1138. === Using a Custom Authorization Manager
  1139. The second way to authorize a method programmatically is to create a custom xref:servlet/authorization/architecture.adoc#_the_authorizationmanager[`AuthorizationManager`].
  1140. First, declare an authorization manager instance, perhaps like this one:
  1141. [tabs]
  1142. ======
  1143. Java::
  1144. +
  1145. [source,java,role="primary"]
  1146. ----
  1147. @Component
  1148. public class MyAuthorizationManager implements AuthorizationManager<MethodInvocation>, AuthorizationManager<MethodInvocationResult> {
  1149. @Override
  1150. public AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocation invocation) {
  1151. // ... authorization logic
  1152. }
  1153. @Override
  1154. public AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocationResult invocation) {
  1155. // ... authorization logic
  1156. }
  1157. }
  1158. ----
  1159. Kotlin::
  1160. +
  1161. [source,kotlin,role="secondary"]
  1162. ----
  1163. @Component
  1164. class MyAuthorizationManager : AuthorizationManager<MethodInvocation>, AuthorizationManager<MethodInvocationResult> {
  1165. override fun authorize(authentication: Supplier<Authentication>, invocation: MethodInvocation): AuthorizationResult {
  1166. // ... authorization logic
  1167. }
  1168. override fun authorize(authentication: Supplier<Authentication>, invocation: MethodInvocationResult): AuthorizationResult {
  1169. // ... authorization logic
  1170. }
  1171. }
  1172. ----
  1173. ======
  1174. Then, publish the method interceptor with a pointcut that corresponds to when you want that `AuthorizationManager` to run.
  1175. For example, you could replace how `@PreAuthorize` and `@PostAuthorize` work like so:
  1176. .Only @PreAuthorize and @PostAuthorize Configuration
  1177. [tabs]
  1178. ======
  1179. Java::
  1180. +
  1181. [source,java,role="primary"]
  1182. ----
  1183. @Configuration
  1184. @EnableMethodSecurity(prePostEnabled = false)
  1185. class MethodSecurityConfig {
  1186. @Bean
  1187. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  1188. Advisor preAuthorize(MyAuthorizationManager manager) {
  1189. return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
  1190. }
  1191. @Bean
  1192. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  1193. Advisor postAuthorize(MyAuthorizationManager manager) {
  1194. return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
  1195. }
  1196. }
  1197. ----
  1198. Kotlin::
  1199. +
  1200. [source,kotlin,role="secondary"]
  1201. ----
  1202. @Configuration
  1203. @EnableMethodSecurity(prePostEnabled = false)
  1204. class MethodSecurityConfig {
  1205. @Bean
  1206. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  1207. fun preAuthorize(manager: MyAuthorizationManager) : Advisor {
  1208. return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager)
  1209. }
  1210. @Bean
  1211. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  1212. fun postAuthorize(manager: MyAuthorizationManager) : Advisor {
  1213. return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager)
  1214. }
  1215. }
  1216. ----
  1217. Xml::
  1218. +
  1219. [source,xml,role="secondary"]
  1220. ----
  1221. <sec:method-security pre-post-enabled="false"/>
  1222. <aop:config/>
  1223. <bean id="preAuthorize"
  1224. class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor"
  1225. factory-method="preAuthorize">
  1226. <constructor-arg ref="myAuthorizationManager"/>
  1227. </bean>
  1228. <bean id="postAuthorize"
  1229. class="org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor"
  1230. factory-method="postAuthorize">
  1231. <constructor-arg ref="myAuthorizationManager"/>
  1232. </bean>
  1233. ----
  1234. ======
  1235. [TIP]
  1236. ====
  1237. You can place your interceptor in between Spring Security method interceptors using the order constants specified in `AuthorizationInterceptorsOrder`.
  1238. ====
  1239. [[customizing-expression-handling]]
  1240. === Customizing Expression Handling
  1241. Or, third, you can customize how each SpEL expression is handled.
  1242. To do that, you can expose a custom javadoc:org.springframework.security.access.expression.method.MethodSecurityExpressionHandler[], like so:
  1243. .Custom MethodSecurityExpressionHandler
  1244. [tabs]
  1245. ======
  1246. Java::
  1247. +
  1248. [source,java,role="primary"]
  1249. ----
  1250. @Bean
  1251. static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
  1252. DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
  1253. handler.setRoleHierarchy(roleHierarchy);
  1254. return handler;
  1255. }
  1256. ----
  1257. Kotlin::
  1258. +
  1259. [source,kotlin,role="secondary"]
  1260. ----
  1261. companion object {
  1262. @Bean
  1263. fun methodSecurityExpressionHandler(roleHierarchy: RoleHierarchy) : MethodSecurityExpressionHandler {
  1264. val handler = DefaultMethodSecurityExpressionHandler()
  1265. handler.setRoleHierarchy(roleHierarchy)
  1266. return handler
  1267. }
  1268. }
  1269. ----
  1270. Xml::
  1271. +
  1272. [source,xml,role="secondary"]
  1273. ----
  1274. <sec:method-security>
  1275. <sec:expression-handler ref="myExpressionHandler"/>
  1276. </sec:method-security>
  1277. <bean id="myExpressionHandler"
  1278. class="org.springframework.security.messaging.access.expression.DefaultMessageSecurityExpressionHandler">
  1279. <property name="roleHierarchy" ref="roleHierarchy"/>
  1280. </bean>
  1281. ----
  1282. ======
  1283. [TIP]
  1284. ====
  1285. We expose `MethodSecurityExpressionHandler` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes
  1286. ====
  1287. You can also <<subclass-defaultmethodsecurityexpressionhandler,subclass `DefaultMessageSecurityExpressionHandler`>> to add your own custom authorization expressions beyond the defaults.
  1288. [[pre-post-authorize-aot]]
  1289. === Working with AOT
  1290. Spring Security will scan all beans in the application context for methods that use `@PreAuthorize` or `@PostAuthorize`.
  1291. When it finds one, it will resolve any beans used inside the security expression and register the appropriate runtime hints for that bean.
  1292. If it finds a method that uses `@AuthorizeReturnObject`, it will recursively search inside the method's return type for `@PreAuthorize` and `@PostAuthorize` annotations and register them accordingly.
  1293. For example, consider the following Spring Boot application:
  1294. [tabs]
  1295. ======
  1296. Java::
  1297. +
  1298. [source,java,role="primary"]
  1299. ----
  1300. @Service
  1301. public class AccountService { <1>
  1302. @PreAuthorize("@authz.decide()") <2>
  1303. @AuthorizeReturnObject <3>
  1304. public Account getAccountById(String accountId) {
  1305. // ...
  1306. }
  1307. }
  1308. public class Account {
  1309. private final String accountNumber;
  1310. // ...
  1311. @PreAuthorize("@accountAuthz.canViewAccountNumber()") <4>
  1312. public String getAccountNumber() {
  1313. return this.accountNumber;
  1314. }
  1315. @AuthorizeReturnObject <5>
  1316. public User getUser() {
  1317. return new User("John Doe");
  1318. }
  1319. }
  1320. public class User {
  1321. private final String fullName;
  1322. // ...
  1323. @PostAuthorize("@myOtherAuthz.decide()") <6>
  1324. public String getFullName() {
  1325. return this.fullName;
  1326. }
  1327. }
  1328. ----
  1329. Kotlin::
  1330. +
  1331. [source,kotlin,role="secondary"]
  1332. ----
  1333. @Service
  1334. class AccountService { <1>
  1335. @PreAuthorize("@authz.decide()") <2>
  1336. @AuthorizeReturnObject <3>
  1337. fun getAccountById(accountId: String): Account {
  1338. // ...
  1339. }
  1340. }
  1341. class Account(private val accountNumber: String) {
  1342. @PreAuthorize("@accountAuthz.canViewAccountNumber()") <4>
  1343. fun getAccountNumber(): String {
  1344. return this.accountNumber
  1345. }
  1346. @AuthorizeReturnObject <5>
  1347. fun getUser(): User {
  1348. return User("John Doe")
  1349. }
  1350. }
  1351. class User(private val fullName: String) {
  1352. @PostAuthorize("@myOtherAuthz.decide()") <6>
  1353. fun getFullName(): String {
  1354. return this.fullName
  1355. }
  1356. }
  1357. ----
  1358. ======
  1359. <1> Spring Security finds the `AccountService` bean
  1360. <2> Finding a method that uses `@PreAuthorize`, it will resolve any bean names used inside the expression, `authz` in that case, and register runtime hints for the bean class
  1361. <3> Finding a method that uses `@AuthorizeReturnObject`, it will look into the method's return type for any `@PreAuthorize` or `@PostAuthorize`
  1362. <4> Then, it finds a `@PreAuthorize` with another bean name: `accountAuthz`; the runtime hints are registered for the bean class as well
  1363. <5> Finding another `@AuthorizeReturnObject` it will look again into the method's return type
  1364. <6> Now, a `@PostAuthorize` is found with yet another bean name used: `myOtherAuthz`; the runtime hints are registered for the bean class as well
  1365. There will be many times when Spring Security cannot determine the actual return type of the method ahead of time since it may be hidden in an erased generic type.
  1366. Consider the following service:
  1367. [tabs]
  1368. ======
  1369. Java::
  1370. +
  1371. [source,java,role="primary"]
  1372. ----
  1373. @Service
  1374. public class AccountService {
  1375. @AuthorizeReturnObject
  1376. public List<Account> getAllAccounts() {
  1377. // ...
  1378. }
  1379. }
  1380. ----
  1381. Kotlin::
  1382. +
  1383. [source,kotlin,role="secondary"]
  1384. ----
  1385. @Service
  1386. class AccountService {
  1387. @AuthorizeReturnObject
  1388. fun getAllAccounts(): List<Account> {
  1389. // ...
  1390. }
  1391. }
  1392. ----
  1393. ======
  1394. In this case, the generic type is erased and so it isn’t apparent to Spring Security ahead-of-time that `Account` needs to be visited in order to check for `@PreAuthorize` and `@PostAuthorize`.
  1395. To address this, you can publish a javadoc:org.springframework.security.aot.hint.PrePostAuthorizeExpressionBeanHintsRegistrar[`PrePostAuthorizeExpressionBeanHintsRegistrar`] like so:
  1396. [tabs]
  1397. ======
  1398. Java::
  1399. +
  1400. [source,java,role="primary"]
  1401. ----
  1402. @Bean
  1403. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  1404. static SecurityHintsRegistrar registerTheseToo() {
  1405. return new PrePostAuthorizeExpressionBeanHintsRegistrar(Account.class);
  1406. }
  1407. ----
  1408. Kotlin::
  1409. +
  1410. [source,kotlin,role="secondary"]
  1411. ----
  1412. @Bean
  1413. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  1414. fun registerTheseToo(): SecurityHintsRegistrar {
  1415. return PrePostAuthorizeExpressionBeanHintsRegistrar(Account::class.java)
  1416. }
  1417. ----
  1418. ======
  1419. [[use-aspectj]]
  1420. == Authorizing with AspectJ
  1421. [[match-by-pointcut]]
  1422. === Matching Methods with Custom Pointcuts
  1423. Being built on Spring AOP, you can declare patterns that are not related to annotations, similar to xref:servlet/authorization/authorize-http-requests.adoc[request-level authorization].
  1424. This has the potential advantage of centralizing method-level authorization rules.
  1425. For example, you can use publish your own `Advisor` or use xref:servlet/appendix/namespace/method-security.adoc#nsa-protect-pointcut[`<protect-pointcut>`] to match AOP expressions to authorization rules for your service layer like so:
  1426. [tabs]
  1427. ======
  1428. Java::
  1429. +
  1430. [source,java,role="primary"]
  1431. ----
  1432. import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole
  1433. @Bean
  1434. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  1435. static Advisor protectServicePointcut() {
  1436. AspectJExpressionPointcut pattern = new AspectJExpressionPointcut()
  1437. pattern.setExpression("execution(* com.mycompany.*Service.*(..))")
  1438. return new AuthorizationManagerBeforeMethodInterceptor(pattern, hasRole("USER"))
  1439. }
  1440. ----
  1441. Kotlin::
  1442. +
  1443. [source,kotlin,role="secondary"]
  1444. ----
  1445. import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole
  1446. companion object {
  1447. @Bean
  1448. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  1449. fun protectServicePointcut(): Advisor {
  1450. val pattern = AspectJExpressionPointcut()
  1451. pattern.setExpression("execution(* com.mycompany.*Service.*(..))")
  1452. return new AuthorizationManagerBeforeMethodInterceptor(pattern, hasRole("USER"))
  1453. }
  1454. }
  1455. ----
  1456. Xml::
  1457. +
  1458. [source,xml,role="secondary"]
  1459. ----
  1460. <sec:method-security>
  1461. <protect-pointcut expression="execution(* com.mycompany.*Service.*(..))" access="hasRole('USER')"/>
  1462. </sec:method-security>
  1463. ----
  1464. ======
  1465. [[weave-aspectj]]
  1466. === Integrate with AspectJ Byte-weaving
  1467. Performance can at times be enhanced by using AspectJ to weave Spring Security advice into the byte code of your beans.
  1468. After setting up AspectJ, you can quite simply state in the `@EnableMethodSecurity` annotation or `<method-security>` element that you are using AspectJ:
  1469. [tabs]
  1470. ======
  1471. Java::
  1472. +
  1473. [source,java,role="primary"]
  1474. ----
  1475. @EnableMethodSecurity(mode=AdviceMode.ASPECTJ)
  1476. ----
  1477. Kotlin::
  1478. +
  1479. [source,kotlin,role="secondary"]
  1480. ----
  1481. @EnableMethodSecurity(mode=AdviceMode.ASPECTJ)
  1482. ----
  1483. Xml::
  1484. +
  1485. [source,xml,role="secondary"]
  1486. ----
  1487. <sec:method-security mode="aspectj"/>
  1488. ----
  1489. ======
  1490. And the result will be that Spring Security will publish its advisors as AspectJ advice so that they can be woven in accordingly.
  1491. [[changing-the-order]]
  1492. == Specifying Order
  1493. As already noted, there is a Spring AOP method interceptor for each annotation, and each of these has a location in the Spring AOP advisor chain.
  1494. Namely, the `@PreFilter` method interceptor's order is 100, ``@PreAuthorize``'s is 200, and so on.
  1495. You can use the `offset` parameter on `@EnableMethodSecurity` to move all interceptors en masse to provide their advice earlier or later in a method invocation.
  1496. [[authorization-expressions]]
  1497. == Expressing Authorization with SpEL
  1498. You've already seen several examples using SpEL, so now let's cover the API a bit more in depth.
  1499. Spring Security encapsulates all of its authorization fields and methods in a set of root objects.
  1500. The most generic root object is called `SecurityExpressionRoot` and it forms the basis for `MethodSecurityExpressionRoot`.
  1501. Spring Security supplies this root object to `MethodSecurityEvaluationContext` when preparing to evaluate an authorization expression.
  1502. [[using-authorization-expression-fields-and-methods]]
  1503. === Using Authorization Expression Fields and Methods
  1504. The first thing this provides is an enhanced set of authorization fields and methods to your SpEL expressions.
  1505. What follows is a quick overview of the most common methods:
  1506. * `permitAll` - The method requires no authorization to be invoked; note that in this case, xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[the `Authentication`] is never retrieved from the session
  1507. * `denyAll` - The method is not allowed under any circumstances; note that in this case, the `Authentication` is never retrieved from the session
  1508. * `hasAuthority` - The method requires that the `Authentication` have xref:servlet/authorization/architecture.adoc#authz-authorities[a `GrantedAuthority`] that matches the given value
  1509. * `hasRole` - A shortcut for `hasAuthority` that prefixes `ROLE_` or whatever is configured as the default prefix
  1510. * `hasAnyAuthority` - The method requires that the `Authentication` have a `GrantedAuthority` that matches any of the given values
  1511. * `hasAnyRole` - A shortcut for `hasAnyAuthority` that prefixes `ROLE_` or whatever is configured as the default prefix
  1512. * `hasPermission` - A hook into your `PermissionEvaluator` instance for doing object-level authorization
  1513. And here is a brief look at the most common fields:
  1514. * `authentication` - The `Authentication` instance associated with this method invocation
  1515. * `principal` - The `Authentication#getPrincipal` associated with this method invocation
  1516. Having now learned the patterns, rules, and how they can be paired together, you should be able to understand what is going on in this more complex example:
  1517. .Authorize Requests
  1518. [tabs]
  1519. ======
  1520. Java::
  1521. +
  1522. [source,java,role="primary"]
  1523. ----
  1524. @Component
  1525. public class MyService {
  1526. @PreAuthorize("denyAll") <1>
  1527. MyResource myDeprecatedMethod(...);
  1528. @PreAuthorize("hasRole('ADMIN')") <2>
  1529. MyResource writeResource(...)
  1530. @PreAuthorize("hasAuthority('db') and hasRole('ADMIN')") <3>
  1531. MyResource deleteResource(...)
  1532. @PreAuthorize("principal.claims['aud'] == 'my-audience'") <4>
  1533. MyResource readResource(...);
  1534. @PreAuthorize("@authz.check(authentication, #root)")
  1535. MyResource shareResource(...);
  1536. }
  1537. ----
  1538. Kotlin::
  1539. +
  1540. [source,kotlin,role="secondary"]
  1541. ----
  1542. @Component
  1543. open class MyService {
  1544. @PreAuthorize("denyAll") <1>
  1545. fun myDeprecatedMethod(...): MyResource
  1546. @PreAuthorize("hasRole('ADMIN')") <2>
  1547. fun writeResource(...): MyResource
  1548. @PreAuthorize("hasAuthority('db') and hasRole('ADMIN')") <3>
  1549. fun deleteResource(...): MyResource
  1550. @PreAuthorize("principal.claims['aud'] == 'my-audience'") <4>
  1551. fun readResource(...): MyResource
  1552. @PreAuthorize("@authz.check(#root)")
  1553. fun shareResource(...): MyResource
  1554. }
  1555. ----
  1556. Xml::
  1557. +
  1558. [source,xml,role="secondary"]
  1559. ----
  1560. <sec:method-security>
  1561. <protect-pointcut expression="execution(* com.mycompany.*Service.myDeprecatedMethod(..))" access="denyAll"/> <1>
  1562. <protect-pointcut expression="execution(* com.mycompany.*Service.writeResource(..))" access="hasRole('ADMIN')"/> <2>
  1563. <protect-pointcut expression="execution(* com.mycompany.*Service.deleteResource(..))" access="hasAuthority('db') and hasRole('ADMIN')"/> <3>
  1564. <protect-pointcut expression="execution(* com.mycompany.*Service.readResource(..))" access="principal.claims['aud'] == 'my-audience'"/> <4>
  1565. <protect-pointcut expression="execution(* com.mycompany.*Service.shareResource(..))" access="@authz.check(#root)"/> <5>
  1566. </sec:method-security>
  1567. ----
  1568. ======
  1569. <1> This method may not be invoked by anyone for any reason
  1570. <2> This method may only be invoked by ``Authentication``s granted the `ROLE_ADMIN` authority
  1571. <3> This method may only be invoked by ``Authentication``s granted the `db` and `ROLE_ADMIN` authorities
  1572. <4> This method may only be invoked by ``Princpal``s with an `aud` claim equal to "my-audience"
  1573. <5> This method may only be invoked if the bean ``authz``'s `check` method returns `true`
  1574. [TIP]
  1575. ====
  1576. You can use a bean like `authz` above to <<_using_a_custom_bean_in_spel, add programmatic authorization>>.
  1577. ====
  1578. [[using_method_parameters]]
  1579. === Using Method Parameters
  1580. Additionally, Spring Security provides a mechanism for discovering method parameters so they can also be accessed in the SpEL expression as well.
  1581. For a complete reference, Spring Security uses `DefaultSecurityParameterNameDiscoverer` to discover the parameter names.
  1582. By default, the following options are tried for a method.
  1583. 1. If Spring Security's `@P` annotation is present on a single argument to the method, the value is used.
  1584. The following example uses the `@P` annotation:
  1585. +
  1586. [tabs]
  1587. ======
  1588. Java::
  1589. +
  1590. [source,java,role="primary"]
  1591. ----
  1592. import org.springframework.security.access.method.P;
  1593. ...
  1594. @PreAuthorize("hasPermission(#c, 'write')")
  1595. public void updateContact(@P("c") Contact contact);
  1596. ----
  1597. Kotlin::
  1598. +
  1599. [source,kotlin,role="secondary"]
  1600. ----
  1601. import org.springframework.security.access.method.P
  1602. ...
  1603. @PreAuthorize("hasPermission(#c, 'write')")
  1604. fun doSomething(@P("c") contact: Contact?)
  1605. ----
  1606. ======
  1607. +
  1608. The intention of this expression is to require that the current `Authentication` have `write` permission specifically for this `Contact` instance.
  1609. +
  1610. Behind the scenes, this is implemented by using `AnnotationParameterNameDiscoverer`, which you can customize to support the value attribute of any specified annotation.
  1611. 2. If xref:servlet/integrations/data.adoc[Spring Data's] `@Param` annotation is present on at least one parameter for the method, the value is used.
  1612. The following example uses the `@Param` annotation:
  1613. +
  1614. [tabs]
  1615. ======
  1616. Java::
  1617. +
  1618. [source,java,role="primary"]
  1619. ----
  1620. import org.springframework.data.repository.query.Param;
  1621. ...
  1622. @PreAuthorize("#n == authentication.name")
  1623. Contact findContactByName(@Param("n") String name);
  1624. ----
  1625. Kotlin::
  1626. +
  1627. [source,kotlin,role="secondary"]
  1628. ----
  1629. import org.springframework.data.repository.query.Param
  1630. ...
  1631. @PreAuthorize("#n == authentication.name")
  1632. fun findContactByName(@Param("n") name: String?): Contact?
  1633. ----
  1634. ======
  1635. +
  1636. The intention of this expression is to require that `name` be equal to `Authentication#getName` for the invocation to be authorized.
  1637. +
  1638. Behind the scenes, this is implemented by using `AnnotationParameterNameDiscoverer`, which you can customize to support the value attribute of any specified annotation.
  1639. 3. If you compile your code with the `-parameters` argument, the standard JDK reflection API is used to discover the parameter names.
  1640. This works on both classes and interfaces.
  1641. 4. Finally, if you compile your code with debug symbols, the parameter names are discovered by using the debug symbols.
  1642. This does not work for interfaces, since they do not have debug information about the parameter names.
  1643. For interfaces, either annotations or the `-parameters` approach must be used.
  1644. [[authorize-object]]
  1645. == Authorizing Arbitrary Objects
  1646. Spring Security also supports wrapping any object that is annotated its method security annotations.
  1647. The simplest way to achieve this is to mark any method that returns the object you wish to authorize with the `@AuthorizeReturnObject` annotation.
  1648. For example, consider the following `User` class:
  1649. [tabs]
  1650. ======
  1651. Java::
  1652. +
  1653. [source,java,role="primary"]
  1654. ----
  1655. public class User {
  1656. private String name;
  1657. private String email;
  1658. public User(String name, String email) {
  1659. this.name = name;
  1660. this.email = email;
  1661. }
  1662. public String getName() {
  1663. return this.name;
  1664. }
  1665. @PreAuthorize("hasAuthority('user:read')")
  1666. public String getEmail() {
  1667. return this.email;
  1668. }
  1669. }
  1670. ----
  1671. Kotlin::
  1672. +
  1673. [source,kotlin,role="secondary"]
  1674. ----
  1675. class User (val name:String, @get:PreAuthorize("hasAuthority('user:read')") val email:String)
  1676. ----
  1677. ======
  1678. Given an interface like this one:
  1679. [tabs]
  1680. ======
  1681. Java::
  1682. +
  1683. [source,java,role="primary"]
  1684. ----
  1685. public class UserRepository {
  1686. @AuthorizeReturnObject
  1687. Optional<User> findByName(String name) {
  1688. // ...
  1689. }
  1690. }
  1691. ----
  1692. Kotlin::
  1693. +
  1694. [source,kotlin,role="secondary"]
  1695. ----
  1696. class UserRepository {
  1697. @AuthorizeReturnObject
  1698. fun findByName(name:String?): Optional<User?>? {
  1699. // ...
  1700. }
  1701. }
  1702. ----
  1703. ======
  1704. Then any `User` that is returned from `findById` will be secured like other Spring Security-protected components:
  1705. [tabs]
  1706. ======
  1707. Java::
  1708. +
  1709. [source,java,role="primary"]
  1710. ----
  1711. @Autowired
  1712. UserRepository users;
  1713. @Test
  1714. void getEmailWhenProxiedThenAuthorizes() {
  1715. Optional<User> securedUser = users.findByName("name");
  1716. assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> securedUser.get().getEmail());
  1717. }
  1718. ----
  1719. Kotlin::
  1720. +
  1721. [source,kotlin,role="secondary"]
  1722. ----
  1723. import jdk.incubator.vector.VectorOperators.Test
  1724. import java.nio.file.AccessDeniedException
  1725. import java.util.*
  1726. @Autowired
  1727. var users:UserRepository? = null
  1728. @Test
  1729. fun getEmailWhenProxiedThenAuthorizes() {
  1730. val securedUser: Optional<User> = users.findByName("name")
  1731. assertThatExceptionOfType(AccessDeniedException::class.java).isThrownBy{securedUser.get().getEmail()}
  1732. }
  1733. ----
  1734. ======
  1735. === Using `@AuthorizeReturnObject` at the class level
  1736. `@AuthorizeReturnObject` can be placed at the class level. Note, though, that this means Spring Security will attempt to proxy any return object, including ``String``, ``Integer`` and other types.
  1737. This is often not what you want to do.
  1738. If you want to use `@AuthorizeReturnObject` on a class or interface whose methods return value types, like `int`, `String`, `Double` or collections of those types, then you should also publish the appropriate `AuthorizationAdvisorProxyFactory.TargetVisitor` as follows:
  1739. [tabs]
  1740. ======
  1741. Java::
  1742. +
  1743. [source,java,role="primary"]
  1744. ----
  1745. import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor;
  1746. // ...
  1747. @Bean
  1748. static TargetVisitor skipValueTypes() {
  1749. return TargetVisitor.defaultsSkipValueTypes();
  1750. }
  1751. ----
  1752. Kotlin::
  1753. +
  1754. [source,kotlin,role="secondary"]
  1755. ----
  1756. import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor
  1757. // ...
  1758. @Bean
  1759. open fun skipValueTypes() = TargetVisitor.defaultsSkipValueTypes()
  1760. ----
  1761. ======
  1762. [TIP]
  1763. ====
  1764. You can set your own `AuthorizationAdvisorProxyFactory.TargetVisitor` to customize the proxying for any set of types
  1765. ====
  1766. === Programmatically Proxying
  1767. You can also programmatically proxy a given object.
  1768. To achieve this, you can autowire the provided `AuthorizationProxyFactory` instance, which is based on which method security interceptors you have configured.
  1769. If you are using `@EnableMethodSecurity`, then this means that it will by default have the interceptors for `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFilter`.
  1770. You can proxy an instance of user in the following way:
  1771. [tabs]
  1772. ======
  1773. Java::
  1774. +
  1775. [source,java,role="primary"]
  1776. ----
  1777. @Autowired
  1778. AuthorizationProxyFactory proxyFactory;
  1779. @Test
  1780. void getEmailWhenProxiedThenAuthorizes() {
  1781. User user = new User("name", "email");
  1782. assertThat(user.getEmail()).isNotNull();
  1783. User securedUser = proxyFactory.proxy(user);
  1784. assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(securedUser::getEmail);
  1785. }
  1786. ----
  1787. Kotlin::
  1788. +
  1789. [source,kotlin,role="secondary"]
  1790. ----
  1791. @Autowired
  1792. var proxyFactory:AuthorizationProxyFactory? = null
  1793. @Test
  1794. fun getEmailWhenProxiedThenAuthorizes() {
  1795. val user: User = User("name", "email")
  1796. assertThat(user.getEmail()).isNotNull()
  1797. val securedUser: User = proxyFactory.proxy(user)
  1798. assertThatExceptionOfType(AccessDeniedException::class.java).isThrownBy(securedUser::getEmail)
  1799. }
  1800. ----
  1801. ======
  1802. === Manual Construction
  1803. You can also define your own instance if you need something different from the Spring Security default.
  1804. For example, if you define an `AuthorizationProxyFactory` instance like so:
  1805. [tabs]
  1806. ======
  1807. Java::
  1808. +
  1809. [source,java,role="primary"]
  1810. ----
  1811. import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor;
  1812. import static org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.preAuthorize;
  1813. // ...
  1814. AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults();
  1815. // and if needing to skip value types
  1816. proxyFactory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes());
  1817. ----
  1818. Kotlin::
  1819. +
  1820. [source,kotlin,role="secondary"]
  1821. ----
  1822. import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor;
  1823. import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.preAuthorize
  1824. // ...
  1825. val proxyFactory: AuthorizationProxyFactory = AuthorizationProxyFactory(preAuthorize())
  1826. // and if needing to skip value types
  1827. proxyFactory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes())
  1828. ----
  1829. ======
  1830. Then you can wrap any instance of `User` as follows:
  1831. [tabs]
  1832. ======
  1833. Java::
  1834. +
  1835. [source,java,role="primary"]
  1836. ----
  1837. @Test
  1838. void getEmailWhenProxiedThenAuthorizes() {
  1839. AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults();
  1840. User user = new User("name", "email");
  1841. assertThat(user.getEmail()).isNotNull();
  1842. User securedUser = proxyFactory.proxy(user);
  1843. assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(securedUser::getEmail);
  1844. }
  1845. ----
  1846. Kotlin::
  1847. +
  1848. [source,kotlin,role="secondary"]
  1849. ----
  1850. @Test
  1851. fun getEmailWhenProxiedThenAuthorizes() {
  1852. val proxyFactory: AuthorizationProxyFactory = AuthorizationAdvisorProxyFactory.withDefaults()
  1853. val user: User = User("name", "email")
  1854. assertThat(user.getEmail()).isNotNull()
  1855. val securedUser: User = proxyFactory.proxy(user)
  1856. assertThatExceptionOfType(AccessDeniedException::class.java).isThrownBy(securedUser::getEmail)
  1857. }
  1858. ----
  1859. ======
  1860. === Proxying Collections
  1861. `AuthorizationProxyFactory` supports Java collections, streams, arrays, optionals, and iterators by proxying the element type and maps by proxying the value type.
  1862. This means that when proxying a `List` of objects, the following also works:
  1863. [tabs]
  1864. ======
  1865. Java::
  1866. +
  1867. [source,java,role="primary"]
  1868. ----
  1869. @Test
  1870. void getEmailWhenProxiedThenAuthorizes() {
  1871. AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults();
  1872. List<User> users = List.of(ada, albert, marie);
  1873. List<User> securedUsers = proxyFactory.proxy(users);
  1874. securedUsers.forEach((securedUser) ->
  1875. assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(securedUser::getEmail));
  1876. }
  1877. ----
  1878. ======
  1879. === Proxying Classes
  1880. In limited circumstances, it may be valuable to proxy a `Class` itself, and `AuthorizationProxyFactory` also supports this.
  1881. This is roughly the equivalent of calling `ProxyFactory#getProxyClass` in Spring Framework's support for creating proxies.
  1882. One place where this is handy is when you need to construct the proxy class ahead-of-time, like with Spring AOT.
  1883. === Support for All Method Security Annotations
  1884. `AuthorizationProxyFactory` supports whichever method security annotations are enabled in your application.
  1885. It is based off of whatever `AuthorizationAdvisor` classes are published as a bean.
  1886. Since `@EnableMethodSecurity` publishes `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFilter` advisors by default, you will typically need to do nothing to activate the ability.
  1887. [NOTE]
  1888. ====
  1889. SpEL expressions that use `returnObject` or `filterObject` sit behind the proxy and so have full access to the object.
  1890. ====
  1891. [#custom_advice]
  1892. === Custom Advice
  1893. If you have security advice that you also want applied, you can publish your own `AuthorizationAdvisor` like so:
  1894. [tabs]
  1895. ======
  1896. Java::
  1897. +
  1898. [source,java,role="primary"]
  1899. ----
  1900. @EnableMethodSecurity
  1901. class SecurityConfig {
  1902. @Bean
  1903. static AuthorizationAdvisor myAuthorizationAdvisor() {
  1904. return new AuthorizationAdvisor();
  1905. }
  1906. }
  1907. ----
  1908. Kotlin::
  1909. +
  1910. [source,kotlin,role="secondary"]
  1911. ----
  1912. @EnableMethodSecurity
  1913. internal class SecurityConfig {
  1914. @Bean
  1915. fun myAuthorizationAdvisor(): AuthorizationAdvisor {
  1916. return AuthorizationAdvisor()
  1917. }
  1918. ]
  1919. ----
  1920. ======
  1921. And Spring Security will add that advisor into the set of advice that `AuthorizationProxyFactory` adds when proxying an object.
  1922. === Working with Jackson
  1923. One powerful use of this feature is to return a secured value from a controller like so:
  1924. [tabs]
  1925. ======
  1926. Java::
  1927. +
  1928. [source,java,role="primary"]
  1929. ----
  1930. @RestController
  1931. public class UserController {
  1932. @Autowired
  1933. AuthorizationProxyFactory proxyFactory;
  1934. @GetMapping
  1935. User currentUser(@AuthenticationPrincipal User user) {
  1936. return this.proxyFactory.proxy(user);
  1937. }
  1938. }
  1939. ----
  1940. Kotlin::
  1941. +
  1942. [source,kotlin,role="secondary"]
  1943. ----
  1944. @RestController
  1945. class UserController {
  1946. @Autowired
  1947. var proxyFactory: AuthorizationProxyFactory? = null
  1948. @GetMapping
  1949. fun currentUser(@AuthenticationPrincipal user:User?): User {
  1950. return proxyFactory.proxy(user)
  1951. }
  1952. }
  1953. ----
  1954. ======
  1955. You will need to <<fallback-values-authorization-denied,add a `MethodAuthorizationDeniedHandler`>> like this one:
  1956. [tabs]
  1957. ======
  1958. Java::
  1959. +
  1960. [source,java,role="primary"]
  1961. ----
  1962. @Component
  1963. public class Null implements MethodAuthorizationDeniedHandler {
  1964. @Override
  1965. public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
  1966. return null;
  1967. }
  1968. }
  1969. // ...
  1970. @HandleAuthorizationDenied(handlerClass = Null.class)
  1971. public class User {
  1972. ...
  1973. }
  1974. ----
  1975. Kotlin::
  1976. +
  1977. [source,kotlin,role="secondary"]
  1978. ----
  1979. @Component
  1980. class Null : MethodAuthorizationDeniedHandler {
  1981. override fun handleDeniedInvocation(methodInvocation: MethodInvocation?, authorizationResult: AuthorizationResult?): Any? {
  1982. return null
  1983. }
  1984. }
  1985. // ...
  1986. @HandleAuthorizationDenied(handlerClass = Null.class)
  1987. open class User {
  1988. ...
  1989. }
  1990. ----
  1991. ======
  1992. Then, you'll see a different JSON serialization based on the authorization level of the user.
  1993. If they don't have the `user:read` authority, then they'll see:
  1994. [source,json]
  1995. ----
  1996. {
  1997. "name" : "name",
  1998. "email" : null
  1999. }
  2000. ----
  2001. And if they do have that authority, they'll see:
  2002. [source,json]
  2003. ----
  2004. {
  2005. "name" : "name",
  2006. "email" : "email"
  2007. }
  2008. ----
  2009. [TIP]
  2010. ====
  2011. You can also add the Spring Boot property `spring.jackson.default-property-inclusion=non_null` to exclude the null value from serialization, if you also don't want to reveal the JSON key to an unauthorized user.
  2012. ====
  2013. [[authorize-return-object-aot]]
  2014. === Working with AOT
  2015. Spring Security will scan all beans in the application context for methods that use `@AuthorizeReturnObject`.
  2016. When it finds one, it will create and register the appropriate proxy class ahead of time.
  2017. It will also recursively search for other nested objects that also use `@AuthorizeReturnObject` and register them accordingly.
  2018. For example, consider the following Spring Boot application:
  2019. [tabs]
  2020. ======
  2021. Java::
  2022. +
  2023. [source,java,role="primary"]
  2024. ----
  2025. @SpringBootApplication
  2026. public class MyApplication {
  2027. @RestController
  2028. public static class MyController { <1>
  2029. @GetMapping
  2030. @AuthorizeReturnObject
  2031. Message getMessage() { <2>
  2032. return new Message(someUser, "hello!");
  2033. }
  2034. }
  2035. public static class Message { <3>
  2036. User to;
  2037. String text;
  2038. // ...
  2039. @AuthorizeReturnObject
  2040. public User getTo() { <4>
  2041. return this.to;
  2042. }
  2043. // ...
  2044. }
  2045. public static class User { <5>
  2046. // ...
  2047. }
  2048. public static void main(String[] args) {
  2049. SpringApplication.run(MyApplication.class);
  2050. }
  2051. }
  2052. ----
  2053. Kotlin::
  2054. +
  2055. [source,kotlin,role="secondary"]
  2056. ----
  2057. @SpringBootApplication
  2058. open class MyApplication {
  2059. @RestController
  2060. open class MyController { <1>
  2061. @GetMapping
  2062. @AuthorizeReturnObject
  2063. fun getMessage():Message { <2>
  2064. return Message(someUser, "hello!")
  2065. }
  2066. }
  2067. open class Message { <3>
  2068. val to: User
  2069. val test: String
  2070. // ...
  2071. @AuthorizeReturnObject
  2072. fun getTo(): User { <4>
  2073. return this.to
  2074. }
  2075. // ...
  2076. }
  2077. open class User { <5>
  2078. // ...
  2079. }
  2080. fun main(args: Array<String>) {
  2081. SpringApplication.run(MyApplication.class)
  2082. }
  2083. }
  2084. ----
  2085. ======
  2086. <1> - First, Spring Security finds the `MyController` bean
  2087. <2> - Finding a method that uses `@AuthorizeReturnObject`, it proxies `Message`, the return value, and registers that proxy class to `RuntimeHints`
  2088. <3> - Then, it traverses `Message` to see if it uses `@AuthorizeReturnObject`
  2089. <4> - Finding a method that uses `@AuthorizeReturnObject`, it proxies `User`, the return value, and registers that proxy class to `RuntimeHints`
  2090. <5> - Finally, it traverses `User` to see if it uses `@AuthorizeReturnObject`; finding nothing, the algorithm completes
  2091. There will be many times when Spring Security cannot determine the proxy class ahead of time since it may be hidden in an erased generic type.
  2092. Consider the following change to `MyController`:
  2093. [tabs]
  2094. ======
  2095. Java::
  2096. +
  2097. [source,java,role="primary"]
  2098. ----
  2099. @RestController
  2100. public static class MyController {
  2101. @GetMapping
  2102. @AuthorizeReturnObject
  2103. List<Message> getMessages() {
  2104. return List.of(new Message(someUser, "hello!"));
  2105. }
  2106. }
  2107. ----
  2108. Kotlin::
  2109. +
  2110. [source,kotlin,role="secondary"]
  2111. ----
  2112. @RestController
  2113. static class MyController {
  2114. @AuthorizeReturnObject
  2115. @GetMapping
  2116. fun getMessages(): Array<Message> = arrayOf(Message(someUser, "hello!"))
  2117. }
  2118. ----
  2119. ======
  2120. In this case, the generic type is erased and so it isn't apparent to Spring Security ahead-of-time that `Message` will need to be proxied at runtime.
  2121. To address this, you can publish `AuthorizeProxyFactoryHintsRegistrar` like so:
  2122. [tabs]
  2123. ======
  2124. Java::
  2125. +
  2126. [source,java,role="primary"]
  2127. ----
  2128. @Bean
  2129. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  2130. static SecurityHintsRegsitrar registerTheseToo(AuthorizationProxyFactory proxyFactory) {
  2131. return new AuthorizeReturnObjectHintsRegistrar(proxyFactory, Message.class);
  2132. }
  2133. ----
  2134. Kotlin::
  2135. +
  2136. [source,kotlin,role="secondary"]
  2137. ----
  2138. @Bean
  2139. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  2140. fun registerTheseToo(proxyFactory: AuthorizationProxyFactory?): SecurityHintsRegistrar {
  2141. return AuthorizeReturnObjectHintsRegistrar(proxyFactory, Message::class.java)
  2142. }
  2143. ----
  2144. ======
  2145. Spring Security will register that class and then traverse its type as before.
  2146. [[fallback-values-authorization-denied]]
  2147. == Providing Fallback Values When Authorization is Denied
  2148. There are some scenarios where you may not wish to throw an `AuthorizationDeniedException` when a method is invoked without the required permissions.
  2149. Instead, you might wish to return a post-processed result, like a masked result, or a default value in cases where authorization denied happened before invoking the method.
  2150. Spring Security provides support for handling authorization denied on method invocation by using the javadoc:org.springframework.security.authorization.method.HandleAuthorizationDenied[format=annotation].
  2151. The handler works for denied authorizations that happened in the <<authorizing-with-annotations,`@PreAuthorize` and `@PostAuthorize` annotations>> as well as javadoc:org.springframework.security.authorization.AuthorizationDeniedException[] thrown from the method invocation itself.
  2152. Let's consider the example from the <<authorize-object,previous section>>, but instead of creating the `AccessDeniedExceptionInterceptor` to transform an `AccessDeniedException` to a `null` return value, we will use the `handlerClass` attribute from `@HandleAuthorizationDenied`:
  2153. [tabs]
  2154. ======
  2155. Java::
  2156. +
  2157. [source,java,role="primary"]
  2158. ----
  2159. public class NullMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { <1>
  2160. @Override
  2161. public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
  2162. return null;
  2163. }
  2164. }
  2165. @Configuration
  2166. @EnableMethodSecurity
  2167. public class SecurityConfig {
  2168. @Bean <2>
  2169. public NullMethodAuthorizationDeniedHandler nullMethodAuthorizationDeniedHandler() {
  2170. return new NullMethodAuthorizationDeniedHandler();
  2171. }
  2172. }
  2173. public class User {
  2174. // ...
  2175. @PreAuthorize(value = "hasAuthority('user:read')")
  2176. @HandleAuthorizationDenied(handlerClass = NullMethodAuthorizationDeniedHandler.class)
  2177. public String getEmail() {
  2178. return this.email;
  2179. }
  2180. }
  2181. ----
  2182. Kotlin::
  2183. +
  2184. [source,kotlin,role="secondary"]
  2185. ----
  2186. class NullMethodAuthorizationDeniedHandler : MethodAuthorizationDeniedHandler { <1>
  2187. override fun handleDeniedInvocation(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any {
  2188. return null
  2189. }
  2190. }
  2191. @Configuration
  2192. @EnableMethodSecurity
  2193. class SecurityConfig {
  2194. @Bean <2>
  2195. fun nullMethodAuthorizationDeniedHandler(): NullMethodAuthorizationDeniedHandler {
  2196. return MaskMethodAuthorizationDeniedHandler()
  2197. }
  2198. }
  2199. class User (val name:String, @PreAuthorize(value = "hasAuthority('user:read')") @HandleAuthorizationDenied(handlerClass = NullMethodAuthorizationDeniedHandler::class) val email:String) <3>
  2200. ----
  2201. ======
  2202. <1> Create an implementation of `MethodAuthorizationDeniedHandler` that returns a `null` value
  2203. <2> Register the `NullMethodAuthorizationDeniedHandler` as a bean
  2204. <3> Annotate the method with `@HandleAuthorizationDenied` and pass the `NullMethodAuthorizationDeniedHandler` to the `handlerClass` attribute
  2205. And then you can verify that a `null` value is returned instead of the `AccessDeniedException`:
  2206. [TIP]
  2207. ====
  2208. You can also annotate your class with `@Component` instead of creating a `@Bean` method
  2209. ====
  2210. [tabs]
  2211. ======
  2212. Java::
  2213. +
  2214. [source,java,role="primary"]
  2215. ----
  2216. @Autowired
  2217. UserRepository users;
  2218. @Test
  2219. void getEmailWhenProxiedThenNullEmail() {
  2220. Optional<User> securedUser = users.findByName("name");
  2221. assertThat(securedUser.get().getEmail()).isNull();
  2222. }
  2223. ----
  2224. Kotlin::
  2225. +
  2226. [source,kotlin,role="secondary"]
  2227. ----
  2228. @Autowired
  2229. var users:UserRepository? = null
  2230. @Test
  2231. fun getEmailWhenProxiedThenNullEmail() {
  2232. val securedUser: Optional<User> = users.findByName("name")
  2233. assertThat(securedUser.get().getEmail()).isNull()
  2234. }
  2235. ----
  2236. ======
  2237. === Using the Denied Result From the Method Invocation
  2238. There are some scenarios where you might want to return a secure result derived from the denied result.
  2239. For example, if a user is not authorized to see email addresses, you might want to apply some masking on the original email address, i.e. _useremail@example.com_ would become _use\\******@example.com_.
  2240. For those scenarios, you can override the `handleDeniedInvocationResult` from the `MethodAuthorizationDeniedHandler`, which has the javadoc:org.springframework.security.authorization.method.MethodInvocationResult[] as an argument.
  2241. Let's continue with the previous example, but instead of returning `null`, we will return a masked value of the email:
  2242. [tabs]
  2243. ======
  2244. Java::
  2245. +
  2246. [source,java,role="primary"]
  2247. ----
  2248. public class EmailMaskingMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { <1>
  2249. @Override
  2250. public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
  2251. return "***";
  2252. }
  2253. @Override
  2254. public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) {
  2255. String email = (String) methodInvocationResult.getResult();
  2256. return email.replaceAll("(^[^@]{3}|(?!^)\\G)[^@]", "$1*");
  2257. }
  2258. }
  2259. @Configuration
  2260. @EnableMethodSecurity
  2261. public class SecurityConfig {
  2262. @Bean <2>
  2263. public EmailMaskingMethodAuthorizationDeniedHandler emailMaskingMethodAuthorizationDeniedHandler() {
  2264. return new EmailMaskingMethodAuthorizationDeniedHandler();
  2265. }
  2266. }
  2267. public class User {
  2268. // ...
  2269. @PostAuthorize(value = "hasAuthority('user:read')")
  2270. @HandleAuthorizationDenied(handlerClass = EmailMaskingMethodAuthorizationDeniedHandler.class)
  2271. public String getEmail() {
  2272. return this.email;
  2273. }
  2274. }
  2275. ----
  2276. Kotlin::
  2277. +
  2278. [source,kotlin,role="secondary"]
  2279. ----
  2280. class EmailMaskingMethodAuthorizationDeniedHandler : MethodAuthorizationDeniedHandler {
  2281. override fun handleDeniedInvocation(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any {
  2282. return "***"
  2283. }
  2284. override fun handleDeniedInvocationResult(methodInvocationResult: MethodInvocationResult, authorizationResult: AuthorizationResult): Any {
  2285. val email = methodInvocationResult.result as String
  2286. return email.replace("(^[^@]{3}|(?!^)\\G)[^@]".toRegex(), "$1*")
  2287. }
  2288. }
  2289. @Configuration
  2290. @EnableMethodSecurity
  2291. class SecurityConfig {
  2292. @Bean
  2293. fun emailMaskingMethodAuthorizationDeniedHandler(): EmailMaskingMethodAuthorizationDeniedHandler {
  2294. return EmailMaskingMethodAuthorizationDeniedHandler()
  2295. }
  2296. }
  2297. class User (val name:String, @PostAuthorize(value = "hasAuthority('user:read')") @HandleAuthorizationDenied(handlerClass = EmailMaskingMethodAuthorizationDeniedHandler::class) val email:String) <3>
  2298. ----
  2299. ======
  2300. <1> Create an implementation of `MethodAuthorizationDeniedHandler` that returns a masked value of the unauthorized result value
  2301. <2> Register the `EmailMaskingMethodAuthorizationDeniedHandler` as a bean
  2302. <3> Annotate the method with `@HandleAuthorizationDenied` and pass the `EmailMaskingMethodAuthorizationDeniedHandler` to the `handlerClass` attribute
  2303. And then you can verify that a masked email is returned instead of an `AccessDeniedException`:
  2304. [WARNING]
  2305. ====
  2306. Since you have access to the original denied value, make sure that you correctly handle it and do not return it to the caller.
  2307. ====
  2308. [tabs]
  2309. ======
  2310. Java::
  2311. +
  2312. [source,java,role="primary"]
  2313. ----
  2314. @Autowired
  2315. UserRepository users;
  2316. @Test
  2317. void getEmailWhenProxiedThenMaskedEmail() {
  2318. Optional<User> securedUser = users.findByName("name");
  2319. // email is useremail@example.com
  2320. assertThat(securedUser.get().getEmail()).isEqualTo("use******@example.com");
  2321. }
  2322. ----
  2323. Kotlin::
  2324. +
  2325. [source,kotlin,role="secondary"]
  2326. ----
  2327. @Autowired
  2328. var users:UserRepository? = null
  2329. @Test
  2330. fun getEmailWhenProxiedThenMaskedEmail() {
  2331. val securedUser: Optional<User> = users.findByName("name")
  2332. // email is useremail@example.com
  2333. assertThat(securedUser.get().getEmail()).isEqualTo("use******@example.com")
  2334. }
  2335. ----
  2336. ======
  2337. When implementing the `MethodAuthorizationDeniedHandler` you have a few options on what type you can return:
  2338. - A `null` value.
  2339. - A non-null value, respecting the method's return type.
  2340. - Throw an exception, usually an instance of `AuthorizationDeniedException`. This is the default behavior.
  2341. - A `Mono` type for reactive applications.
  2342. Note that since the handler must be registered as beans in your application context, you can inject dependencies into them if you need a more complex logic.
  2343. In addition to that, you have available the `MethodInvocation` or the `MethodInvocationResult`, as well as the `AuthorizationResult` for more details related to the authorization decision.
  2344. [[deciding-return-based-parameters]]
  2345. === Deciding What to Return Based on Available Parameters
  2346. Consider a scenario where there might be multiple mask values for different methods, it would be not so productive if we had to create a handler for each of those methods, although it is perfectly fine to do that.
  2347. In such cases, we can use the information passed via parameters to decide what to do.
  2348. For example, we can create a custom `@Mask` annotation and a handler that detects that annotation to decide what mask value to return:
  2349. [tabs]
  2350. ======
  2351. Java::
  2352. +
  2353. [source,java,role="primary"]
  2354. ----
  2355. import org.springframework.core.annotation.AnnotationUtils;
  2356. @Target({ ElementType.METHOD, ElementType.TYPE })
  2357. @Retention(RetentionPolicy.RUNTIME)
  2358. public @interface Mask {
  2359. String value();
  2360. }
  2361. public class MaskAnnotationDeniedHandler implements MethodAuthorizationDeniedHandler {
  2362. @Override
  2363. public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
  2364. Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class);
  2365. return mask.value();
  2366. }
  2367. }
  2368. @Configuration
  2369. @EnableMethodSecurity
  2370. public class SecurityConfig {
  2371. @Bean
  2372. public MaskAnnotationDeniedHandler maskAnnotationDeniedHandler() {
  2373. return new MaskAnnotationDeniedHandler();
  2374. }
  2375. }
  2376. @Component
  2377. public class MyService {
  2378. @PreAuthorize(value = "hasAuthority('user:read')")
  2379. @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class)
  2380. @Mask("***")
  2381. public String foo() {
  2382. return "foo";
  2383. }
  2384. @PreAuthorize(value = "hasAuthority('user:read')")
  2385. @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class)
  2386. @Mask("???")
  2387. public String bar() {
  2388. return "bar";
  2389. }
  2390. }
  2391. ----
  2392. Kotlin::
  2393. +
  2394. [source,kotlin,role="secondary"]
  2395. ----
  2396. import org.springframework.core.annotation.AnnotationUtils
  2397. @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
  2398. @Retention(AnnotationRetention.RUNTIME)
  2399. annotation class Mask(val value: String)
  2400. class MaskAnnotationDeniedHandler : MethodAuthorizationDeniedHandler {
  2401. override fun handleDeniedInvocation(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any {
  2402. val mask = AnnotationUtils.getAnnotation(methodInvocation.method, Mask::class.java)
  2403. return mask.value
  2404. }
  2405. }
  2406. @Configuration
  2407. @EnableMethodSecurity
  2408. class SecurityConfig {
  2409. @Bean
  2410. fun maskAnnotationDeniedHandler(): MaskAnnotationDeniedHandler {
  2411. return MaskAnnotationDeniedHandler()
  2412. }
  2413. }
  2414. @Component
  2415. class MyService {
  2416. @PreAuthorize(value = "hasAuthority('user:read')")
  2417. @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler::class)
  2418. @Mask("***")
  2419. fun foo(): String {
  2420. return "foo"
  2421. }
  2422. @PreAuthorize(value = "hasAuthority('user:read')")
  2423. @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler::class)
  2424. @Mask("???")
  2425. fun bar(): String {
  2426. return "bar"
  2427. }
  2428. }
  2429. ----
  2430. ======
  2431. Now the return values when access is denied will be decided based on the `@Mask` annotation:
  2432. [tabs]
  2433. ======
  2434. Java::
  2435. +
  2436. [source,java,role="primary"]
  2437. ----
  2438. @Autowired
  2439. MyService myService;
  2440. @Test
  2441. void fooWhenDeniedThenReturnStars() {
  2442. String value = this.myService.foo();
  2443. assertThat(value).isEqualTo("***");
  2444. }
  2445. @Test
  2446. void barWhenDeniedThenReturnQuestionMarks() {
  2447. String value = this.myService.foo();
  2448. assertThat(value).isEqualTo("???");
  2449. }
  2450. ----
  2451. Kotlin::
  2452. +
  2453. [source,kotlin,role="secondary"]
  2454. ----
  2455. @Autowired
  2456. var myService: MyService
  2457. @Test
  2458. fun fooWhenDeniedThenReturnStars() {
  2459. val value: String = myService.foo()
  2460. assertThat(value).isEqualTo("***")
  2461. }
  2462. @Test
  2463. fun barWhenDeniedThenReturnQuestionMarks() {
  2464. val value: String = myService.foo()
  2465. assertThat(value).isEqualTo("???")
  2466. }
  2467. ----
  2468. ======
  2469. === Combining with Meta Annotation Support
  2470. You can also combine the `@HandleAuthorizationDenied` with other annotations in order to reduce and simplify the annotations in a method.
  2471. Let's consider the <<deciding-return-based-parameters,example from the previous section>> and merge `@HandleAuthorizationDenied` with `@Mask`:
  2472. [tabs]
  2473. ======
  2474. Java::
  2475. +
  2476. [source,java,role="primary"]
  2477. ----
  2478. @Target({ ElementType.METHOD, ElementType.TYPE })
  2479. @Retention(RetentionPolicy.RUNTIME)
  2480. @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class)
  2481. public @interface Mask {
  2482. String value();
  2483. }
  2484. @Mask("***")
  2485. public String myMethod() {
  2486. // ...
  2487. }
  2488. ----
  2489. Kotlin::
  2490. +
  2491. [source,kotlin,role="secondary"]
  2492. ----
  2493. @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
  2494. @Retention(AnnotationRetention.RUNTIME)
  2495. @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler::class)
  2496. annotation class Mask(val value: String)
  2497. @Mask("***")
  2498. fun myMethod(): String {
  2499. // ...
  2500. }
  2501. ----
  2502. ======
  2503. Now you do not have to remember to add both annotations when you need a mask behavior in your method.
  2504. Make sure to read the <<meta-annotations,Meta Annotations Support>> section for more details on the usage.
  2505. [[migration-enableglobalmethodsecurity]]
  2506. == Migrating from `@EnableGlobalMethodSecurity`
  2507. If you are using `@EnableGlobalMethodSecurity`, you should migrate to `@EnableMethodSecurity`.
  2508. [[servlet-replace-globalmethodsecurity-with-methodsecurity]]
  2509. === Replace xref:servlet/authorization/method-security.adoc#jc-enable-global-method-security[global method security] with xref:servlet/authorization/method-security.adoc#jc-enable-method-security[method security]
  2510. javadoc:org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity[format=annotation] and xref:servlet/appendix/namespace/method-security.adoc#nsa-global-method-security[`<global-method-security>`] are deprecated in favor of javadoc:org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity[`@EnableMethodSecurity`] and xref:servlet/appendix/namespace/method-security.adoc#nsa-method-security[`<method-security>`], respectively.
  2511. The new annotation and XML element activate Spring's xref:servlet/authorization/method-security.adoc#jc-enable-method-security[pre-post annotations] by default and use `AuthorizationManager` internally.
  2512. This means that the following two listings are functionally equivalent:
  2513. [tabs]
  2514. ======
  2515. Java::
  2516. +
  2517. [source,java,role="primary"]
  2518. ----
  2519. @EnableGlobalMethodSecurity(prePostEnabled = true)
  2520. ----
  2521. Kotlin::
  2522. +
  2523. [source,kotlin,role="secondary"]
  2524. ----
  2525. @EnableGlobalMethodSecurity(prePostEnabled = true)
  2526. ----
  2527. Xml::
  2528. +
  2529. [source,xml,role="secondary"]
  2530. ----
  2531. <global-method-security pre-post-enabled="true"/>
  2532. ----
  2533. ======
  2534. and:
  2535. [tabs]
  2536. ======
  2537. Java::
  2538. +
  2539. [source,java,role="primary"]
  2540. ----
  2541. @EnableMethodSecurity
  2542. ----
  2543. Kotlin::
  2544. +
  2545. [source,kotlin,role="secondary"]
  2546. ----
  2547. @EnableMethodSecurity
  2548. ----
  2549. Xml::
  2550. +
  2551. [source,xml,role="secondary"]
  2552. ----
  2553. <method-security/>
  2554. ----
  2555. ======
  2556. For applications not using the pre-post annotations, make sure to turn it off to avoid activating unwanted behavior.
  2557. For example, a listing like:
  2558. [tabs]
  2559. ======
  2560. Java::
  2561. +
  2562. [source,java,role="primary"]
  2563. ----
  2564. @EnableGlobalMethodSecurity(securedEnabled = true)
  2565. ----
  2566. Kotlin::
  2567. +
  2568. [source,kotlin,role="secondary"]
  2569. ----
  2570. @EnableGlobalMethodSecurity(securedEnabled = true)
  2571. ----
  2572. Xml::
  2573. +
  2574. [source,xml,role="secondary"]
  2575. ----
  2576. <global-method-security secured-enabled="true"/>
  2577. ----
  2578. ======
  2579. should change to:
  2580. [tabs]
  2581. ======
  2582. Java::
  2583. +
  2584. [source,java,role="primary"]
  2585. ----
  2586. @EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
  2587. ----
  2588. Kotlin::
  2589. +
  2590. [source,kotlin,role="secondary"]
  2591. ----
  2592. @EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
  2593. ----
  2594. Xml::
  2595. +
  2596. [source,xml,role="secondary"]
  2597. ----
  2598. <method-security secured-enabled="true" pre-post-enabled="false"/>
  2599. ----
  2600. ======
  2601. === Use a Custom `@Bean` instead of subclassing `DefaultMethodSecurityExpressionHandler`
  2602. As a performance optimization, a new method was introduced to `MethodSecurityExpressionHandler` that takes a `Supplier<Authentication>` instead of an `Authentication`.
  2603. This allows Spring Security to defer the lookup of the `Authentication`, and is taken advantage of automatically when you use `@EnableMethodSecurity` instead of `@EnableGlobalMethodSecurity`.
  2604. However, let's say that your code extends `DefaultMethodSecurityExpressionHandler` and overrides `createSecurityExpressionRoot(Authentication, MethodInvocation)` to return a custom `SecurityExpressionRoot` instance.
  2605. This will no longer work because the arrangement that `@EnableMethodSecurity` sets up calls `createEvaluationContext(Supplier<Authentication>, MethodInvocation)` instead.
  2606. Happily, such a level of customization is often unnecessary.
  2607. Instead, you can create a custom bean with the authorization methods that you need.
  2608. For example, let's say you are wanting a custom evaluation of `@PostAuthorize("hasAuthority('ADMIN')")`.
  2609. You can create a custom `@Bean` like this one:
  2610. [tabs]
  2611. ======
  2612. Java::
  2613. +
  2614. [source,java,role="primary"]
  2615. ----
  2616. class MyAuthorizer {
  2617. boolean isAdmin(MethodSecurityExpressionOperations root) {
  2618. boolean decision = root.hasAuthority("ADMIN");
  2619. // custom work ...
  2620. return decision;
  2621. }
  2622. }
  2623. ----
  2624. Kotlin::
  2625. +
  2626. [source,kotlin,role="secondary"]
  2627. ----
  2628. class MyAuthorizer {
  2629. fun isAdmin(root: MethodSecurityExpressionOperations): boolean {
  2630. val decision = root.hasAuthority("ADMIN");
  2631. // custom work ...
  2632. return decision;
  2633. }
  2634. }
  2635. ----
  2636. ======
  2637. and then refer to it in the annotation like so:
  2638. [tabs]
  2639. ======
  2640. Java::
  2641. +
  2642. [source,java,role="primary"]
  2643. ----
  2644. @PreAuthorize("@authz.isAdmin(#root)")
  2645. ----
  2646. Kotlin::
  2647. +
  2648. [source,kotlin,role="secondary"]
  2649. ----
  2650. @PreAuthorize("@authz.isAdmin(#root)")
  2651. ----
  2652. ======
  2653. [[subclass-defaultmethodsecurityexpressionhandler]]
  2654. ==== I'd still prefer to subclass `DefaultMethodSecurityExpressionHandler`
  2655. If you must continue subclassing `DefaultMethodSecurityExpressionHandler`, you can still do so.
  2656. Instead, override the `createEvaluationContext(Supplier<Authentication>, MethodInvocation)` method like so:
  2657. [tabs]
  2658. ======
  2659. Java::
  2660. +
  2661. [source,java,role="primary"]
  2662. ----
  2663. @Component
  2664. class MyExpressionHandler extends DefaultMethodSecurityExpressionHandler {
  2665. @Override
  2666. public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, MethodInvocation mi) {
  2667. StandardEvaluationContext context = (StandardEvaluationContext) super.createEvaluationContext(authentication, mi);
  2668. MethodSecurityExpressionOperations delegate = (MethodSecurityExpressionOperations) context.getRootObject().getValue();
  2669. MySecurityExpressionRoot root = new MySecurityExpressionRoot(delegate);
  2670. context.setRootObject(root);
  2671. return context;
  2672. }
  2673. }
  2674. ----
  2675. Kotlin::
  2676. +
  2677. [source,kotlin,role="secondary"]
  2678. ----
  2679. @Component
  2680. class MyExpressionHandler: DefaultMethodSecurityExpressionHandler {
  2681. override fun createEvaluationContext(authentication: Supplier<Authentication>,
  2682. val mi: MethodInvocation): EvaluationContext {
  2683. val context = super.createEvaluationContext(authentication, mi) as StandardEvaluationContext
  2684. val delegate = context.getRootObject().getValue() as MethodSecurityExpressionOperations
  2685. val root = MySecurityExpressionRoot(delegate)
  2686. context.setRootObject(root)
  2687. return context
  2688. }
  2689. }
  2690. ----
  2691. ======
  2692. == Further Reading
  2693. Now that you have secured your application's requests, please xref:servlet/authorization/authorize-http-requests.adoc[secure its requests] if you haven't already.
  2694. You can also read further on xref:servlet/test/index.adoc[testing your application] or on integrating Spring Security with other aspects of you application like xref:servlet/integrations/data.adoc[the data layer] or xref:servlet/integrations/observability.adoc[tracing and metrics].