SpringSecurity5.6.2版本的授权改动的还是很大的,所以将以前的文档重新写了一边。
主要包含一下部分内容:
①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳✕✓✔✖
AuthenticationManager(认证管理)
把GrantedAuthority(分配好的权限)
添加到Authentication
对象中。AuthorizationManager
会读取,并作出授权判断。GrantedAuthority
是一个接口,只有一个方法String getAuthority();
,这个方法只能返回一个字符串,如果你的权限表达式很复杂,那么需要自己定义一个方法,来支持getAuthority()
。GrantedAuthority
以集合的方式保存在Authentication
对象中。
GrantedAuthority
有很多具体的实现类:
SimpleGrantedAuthority
: 默认的实现类JaasGrantedAuthority
SwitchUserGrantedAuthority
:将原始的用户Authentication
保存在类里面了。以Remember-me
为例子:
(1):doFilter
实际上是父类AbstractAuthenticationProcessingFilter
中的方法。这种设计抽象了具体的方法,让子类按照自己的业务来进行设计。
AbstractAuthenticationProcessingFilter
的子类UsernamePasswordAuthenticationFilter
:基于用户名与密码的OAuth2LoginAuthenticationFilter
:OAuth2
登陆的,会产生出一个OAuth2LoginAuthenticationToken
。Saml2WebSsoAuthenticationFilter
: 不常见MockAuthenticationFilter
: 这是一个测试代码,直接给了一个名叫 test 的用户名。正常包中没有这个类。(2):attemptAuthentication
是类UsernamePasswordAuthenticationFilter
中的方法。具体实现步骤如下
UsernamePasswordAuthenticationToken
类。getAuthenticationManager().authenticate
对这个Token
进行验证。Authentication
。(3):new
是类UsernamePasswordAuthenticationToken
中的方法。继承了AbstractAuthenticationToken
(4):authenticate
是类ProviderManager
中的方法。实现了AuthenticationManager
接口。具体实现步骤如下
getProviders()
得到具体的Provider
集合Provider
,判断当前provider
是否支持这个authentication
,如果支持,就调用result = provider.authenticate(authentication);
result
为空,就调用父类的authenticate
得到result
result
中的密码给清除了,并且通过eventPublisher
发布成功的消息。(5):supports
是类AnonymousAuthenticationProvider
中的方法。实现了AuthenticationProvider
接口。supports
方法是AuthenticationProvider
接口中定义的,必须要实现的函数。判断是否支持当前Token
。
(6):authenticate
是类AnonymousAuthenticationProvider
中的方法。实现了AuthenticationProvider
接口。 为了安全,这里设置的一个随机的 key。
(7):authenticate
是类DaoAuthenticationProvider
中的方法。继承了AbstractUserDetailsAuthenticationProvider
类,这个类实现了AuthenticationProvider
接口。
UsernamePasswordAuthenticationToken
会被创建、填充并返回。这里的principal
表示当前登陆的主体,可以是String
类型,也可以是UserDetails
类型,但是系统中不推荐使用UserDetails
类型,如果要强制使用String
类型,可以setForcePrincipalAsString
这个方法。UserDetails
,主要是为了获取数据方便,如果不推荐这个方法,那么每次就保存用户名,用到的时候,再从缓存中取。AbstractUserDetailsAuthenticationProvider:authenticate
逻辑user
,先从缓存中获取,如果得不到,就从抽象方法retrieveUser
获取,这个抽象方法是由子类DaoAuthenticationProvider
实现的。DefaultPreAuthenticationChecks
校验用户信息,主要是校验账户是否被锁定、是否过期、是否可用等信息。additionalAuthenticationChecks
,进行验证,这个抽象方法是由子类DaoAuthenticationProvider
实现的。DefaultPostAuthenticationChecks
校验用户过期信息forcePrincipalAsString=true
,那么强制将principal=Username
UsernamePasswordAuthenticationToken
authenticated=true
用这个表示已经登陆了。(8):eraseCredentials
是类ProviderManager
中调用了CredentialsContainer#eraseCredentials()
去清空里面关于密码相关的内容。
CredentialsContainer
是一个接口,被AbstractAuthenticationToken
与UsernamePasswordAuthenticationToken
实现与继承,所以只用调用 token 的类就行。(9):publishAuthenticationSuccess
是类AuthenticationEventPublisher
中的方法。
parentResult == null
NullEventPublisher
类,这个类是不发送消息的。需要定义自己的类来发送消息。(10):onAuthentication
是类SessionAuthenticationStrategy
中的方法。主要负责处理认证成功的时候 session 需要执行的逻辑。
包含如下子类:
(11):successfulAuthentication
是类AbstractAuthenticationProcessingFilter
中的方法。
SecurityContext
rememberMeServices
做了处理ApplicationEventPublisher
进行了发布AuthenticationSuccessHandler
进行了处理假设权限配置如下:
http.authorizeHttpRequests((authorize) -> authorize.antMatchers("/user").hasRole("USER").antMatchers("/info").hasRole("INFO").anyRequest().authenticated())
系统会根据配置内容,初始化一个RequestMatcherDelegatingAuthorizationManager
代理类,这个类针对每个 URL 都会初始化一个对应的,按照上面代码,会把初始化数据放到一个Map<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>>
MAP 中。具体如下:
/user
-》 AuthorityAuthorizationManager
/info
-》 AuthorityAuthorizationManager
anyRequest()
-》 AuthenticatedAuthorizationManager
如果碰到/info
,就调用AuthorityAuthorizationManager
中的verify
方法。
(1):doFilter
是类OncePerRequestFilter
中的方法。这个 Filter 不会被重复执行。
(2):doFilterInternal
是类AuthorizationFilter
中的方法。
AuthorizationManager
来进行授权this.authorizationManager.verify(this::getAuthentication, request)
filterChain
filterChain.doFilter(request, response);
(3):verify
是类AuthorizationManager
中的方法,AuthorizationManager
是一个接口,这个接口中verify
会调用具体实现的类中实现的check
函数,这时候调用的是RequestMatcherDelegatingAuthorizationManager
实现类.
(4):check
是类RequestMatcherDelegatingAuthorizationManager URL匹配代理授权类
中的方法。
RequestMatcherDelegatingAuthorizationManager
是一个代理类,里面有一个 MAPMap<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>>
AuthorizationManager
URL=/user
,要调用对应的AuthorityAuthorizationManager
中的check
函数。URL=any
,要调用对应的AuthenticatedAuthorizationManager
中的check
函数。(5):如果URL=/user
,check
是类AuthenticatedAuthorizationManager[按照权限授权类]
中的方法。
什么情况下授权通过:
authentication != null
authentication.isAuthenticated()
isAuthorized
authentication
取出Authorities
与 自身的Set<GrantedAuthority>
做对比true
,否则返回false
。(6):如果URL=any
,check
是类AuthenticatedAuthorizationManager[是否登陆授权类]
中的方法。
什么情况下授权通过:
authentication != null
authentication.isAuthenticated()
isNotAnonymous
:不能是匿名类AuthenticationTrustResolver
接口中的isAnonymous
判断是否是匿名类。AuthenticationTrustResolverImpl
是系统默认的AuthenticationTrustResolver
实现类。mfa
例子中,就说明白为啥要自定义一个AuthenticationTrustResolver
AuthorizationManager
会取代 AccessDecisionManager
与 AccessDecisionVoter
.
@NullableAuthorizationDecision check(Supplier<Authentication> authentication, T object);default void verify(Supplier<Authentication> authentication, T object) {AuthorizationDecision decision = check(authentication, object);if (decision != null && !decision.isGranted()) {throw new AccessDeniedException("Access Denied");}}
AuthorizationManager
有两个方法,check
方法需要由具体的类来实现。T object
一般用不到,但是在特殊情况可以用到,例如下面的例子,可以将HttpServletRequest
类型的object
传入,然后获得HttpServletRequest
中的参数进行额外的判断。如果投票不通过,,那么就抛出AccessDeniedException
// 例如AuthorizationFilter中,就定义了一个HttpServletRequest类型的objectpublic class AuthorizationFilter extends OncePerRequestFilter {private final AuthorizationManager<HttpServletRequest> authorizationManager;
用户可以定义自己的AuthorizationManager
来控制认证的各个方面,Spring Security
装了一个代理类,这个代理类会把每个单个的AuthorizationManager
组合起来。
针对匹配 URL,可以使用RequestMatcherDelegatingAuthorizationManager
来做代理。针对函数的安全,可以使用 AuthorizationManagerBeforeMethodInterceptor
和 AuthorizationManagerAfterMethodInterceptor
AuthorizationManager
具体实现类AuthenticatedAuthorizationManager
AuthorityAuthorizationManager
Jsr250AuthorizationManager
PostAuthorizeAuthorizationManager
PreAuthorizeAuthorizationManager
RequestMatcherDelegatingAuthorizationManager
SecuredAuthorizationManager
基于权限的授权管理,如果当前的Authentication
包含了所需的权限,那么就通过。
为了区分匿名用户,已认证用户和remember-me
用户。 有些系统允许remember-me
用户访问,但是在一些特殊访问时,需要用户输入密码进行登陆。
可以自定义AuthorizationManagers
,通过查询外部接口或自己的数据库来实现授权的逻辑。
特别说明
老版本中的AccessDecisionVoter
已经作废了,现在通过AuthorizationManagers
来替代了.
为了继承使用老的系统中AccessDecisionManager
与AccessDecisionVoters
的逻辑,不用大的修改,可以参考官方的文档
一个很常见的需求,在一个系统中,可能要求一个角色包含另外一个角色的权限. 例如一个系统中有两个角色"admin" 和 "user",可以指定"admin"可以做任何"user"的操作,如果不是这样,可能要做很复杂的指定。
role-hierarchy 允许你指定一个角色(或权限)包含其他的。
@BeanAccessDecisionVoter hierarchyVoter() {RoleHierarchy hierarchy = new RoleHierarchyImpl();hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" +"ROLE_STAFF > ROLE_USER\n" +"ROLE_USER > ROLE_GUEST");return new RoleHierarcyVoter(hierarchy);}
AccessDecisionVoter
可能会被遗弃。
在实际的过程中,可以通过ant
表达式来实现的,例如“\user\*” 包含了下面的权限。
AccessDecisionManager
AccessDecisionManager
ImplementationsRoleVoter
AuthenticatedVoter
Custom Voters
通过AuthorizationFilter
来给 HTTP 请求授权。如果要更深入的了解针对Servlet
应用程序的授权,可以参考这个章节: Servlet 架构与实现 。
FilterSecurityInterceptor
未来会被AuthorizationFilter
,为了兼容老的系统,FilterSecurityInterceptor
仍被作为默认,这里讨论AuthorizationFilter
如何工作,以及怎么去覆盖缺省的配置。
AuthorizationFilter
可以为HttpServletRequest
提供授权。它会作为一个Security Filters被添加到 FilterChainProxy。
通过定义一个SecurityFilterChain
,并使用authorizeHttpRequests
来替换原先的authorizeRequests
,这样就可以覆盖原先的做法,不使用FilterSecurityInterceptor
。
Example 1 Use authorizeHttpRequests
// 定义了一个SecurityFilterChain 并使用了authorizeHttpRequests@BeanSecurityFilterChain web(HttpSecurity http) throws AuthenticationException {http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated();)// ...return http.build();}
相对于老的版本,有几个改进的方法:
1:使用AuthorizationManager
API 来替换原先复杂的metadata sources, config attributes, decision managers, and voters.
,这样更便于重用与定制化。
2:使用延迟授权,避免以前每个请求都去判断授权,而是在需要进行授权的时候,进行授权处理。
3:基于Bean
的配置。
当 authorizeHttpRequests
被用于替换 authorizeRequests
时, AuthorizationFilter
被用于替换 FilterSecurityInterceptor
.
AuthorizationFilter
通过SecurityContextHolder
得到Authentication
,这里通过一个Supplier
来实现延迟获取。this.authorizationManager.verify(this::getAuthentication, request);
其次,AuthorizationFilter
通过HttpServletRequest
, HttpServletResponse
与 FilterChain
创立了一个FilterInvocation
。 【文档的这部分有问题,实际上没有创将FilterInvocation
,而是将HttpServletRequest
作为参数传递进去了。】
下一步,将Supplier<Authentication>
与 FilterInvocation【或者是HttpServletRequest】
传递给 AuthorizationManager
AccessDeniedException
,这个异常会被ExceptionTranslationFilter
捕获并处理。AuthorizationFilter
会继续FilterChain
的正常请求流程。@BeanSecurityFilterChain web(HttpSecurity http) throws Exception {http// ....authorizeHttpRequests(authorize -> authorize ①.mvcMatchers("/resources/**", "/signup", "/about").permitAll() ②.mvcMatchers("/admin/**").hasRole("ADMIN") ③.mvcMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") ④.anyRequest().denyAll() ⑤);return http.build();}
hasRole
方法,就不用指定 "ROLE"前缀你可以做一个基于 bean 的方法去组织你自己的 RequestMatcherDelegatingAuthorizationManager
@BeanSecurityFilterChain web(HttpSecurity http, AuthorizationManager<RequestAuthorizationContext> access)throws AuthenticationException {http.authorizeHttpRequests((authorize) -> authorize.anyRequest().access(access))// ...return http.build();}@BeanAuthorizationManager<RequestAuthorizationContext> requestMatcherAuthorizationManager(HandlerMappingIntrospector introspector) {RequestMatcher permitAll =new AndRequestMatcher(new MvcRequestMatcher(introspector, "/resources/**"),new MvcRequestMatcher(introspector, "/signup"),new MvcRequestMatcher(introspector, "/about"));RequestMatcher admin = new MvcRequestMatcher(introspector, "/admin/**");RequestMatcher db = new MvcRequestMatcher(introspector, "/db/**");RequestMatcher any = AnyRequestMatcher.INSTANCE;AuthorizationManager<HttpRequestServlet> manager = RequestMatcherDelegatingAuthorizationManager.builder().add(permitAll, (context) -> new AuthorizationDecision(true)).add(admin, AuthorityAuthorizationManager.hasRole("ADMIN")).add(db, AuthorityAuthorizationManager.hasRole("DBA")).add(any, new AuthenticatedAuthorizationManager()).build();return (context) -> manager.check(context.getRequest());}
针对某个 URL 的授权。
Example . Custom Authorization Manager
@BeanSecurityFilterChain web(HttpSecurity http) throws Exception {http.authorizeHttpRequests((authorize) -> authorize.mvcMatchers("/my/authorized/endpoint").access(new CustomAuthorizationManager());)// ...return http.build();}
也可以针对所有 URL 授权
@BeanSecurityFilterChain web(HttpSecurity http) throws Exception {http.authorizeHttpRequests((authorize) -> authorize.anyRequest.access(new CustomAuthorizationManager());)// ...return http.build();}
这些表达式的【 root objects
】是SecurityExpressionRoot
表达 | 描述 |
---|---|
hasRole(String role) | 返回true 当前主体是否具有指定的角色。例如, hasRole('admin') 默认情况下,如果提供的角色不以“ROLE_”开头,则会添加它。这可以通过修改defaultRolePrefix on 来定制DefaultWebSecurityExpressionHandler 。 |
hasAnyRole(String… roles) | 返回true 当前主体是否具有任何提供的角色(以逗号分隔的字符串列表形式给出)。例如, hasAnyRole('admin', 'user') 默认情况下,如果提供的角色不以“ROLE_”开头,则会添加它。这可以通过修改defaultRolePrefix on 来定制DefaultWebSecurityExpressionHandler 。 |
hasAuthority(String authority) | 返回true 当前主体是否具有指定的权限。例如, hasAuthority('read') |
hasAnyAuthority(String… authorities) | 返回true 当前主体是否具有任何提供的权限(以逗号分隔的字符串列表形式给出)例如, hasAnyAuthority('read', 'write') |
principal | 允许直接访问代表当前用户的主体对象 |
authentication | 允许直接访问Authentication 从SecurityContext |
permitAll | 总是评估为 true |
denyAll | 总是评估为 false |
isAnonymous() | true 如果当前主体是匿名用户,则返回 |
isRememberMe() | 返回true 如果当前主体是记得,我的用户 |
isAuthenticated() | true 如果用户不是匿名的则返回 |
isFullyAuthenticated() | 返回true 如果用户不是匿名或记得,我的用户 |
hasPermission(Object target, Object permission) | 返回true 用户是否有权访问给定权限的提供目标。例如,hasPermission(domainObject, 'read') |
hasPermission(Object targetId, String targetType, Object permission) | 返回true 用户是否有权访问给定权限的提供目标。例如,hasPermission(1, 'com.example.domain.Message', 'read') |
hasIpAddress | hasIpAddress('192.168.1.0/24') |
下面的方法与自定义 AuthorizationManager 功能优点重复,这样做的好处是可以传递一些参数
项目中最常用的就是基于 URL 的安全表达式了,这里重点说一下如何自定义校验 Bean。
例如,假设您有一个名称为 的 Bean,webSecurity
其中包含以下方法签名:
public class WebSecurity {public boolean check(Authentication authentication, HttpServletRequest request) {...}}
您可以使用以下方法参考该方法:
http.authorizeRequests(authorize -> authorize.antMatchers("/user/**").access("@webSecurity.check(authentication,request)")...)
例如,考虑一个 RESTful 应用程序,它通过 id 格式从 URL 路径中查找用户/user/{userId}
。
public class WebSecurity {public boolean checkUserId(Authentication authentication, int id) {...}}
您可以使用以下方法参考该方法:
http.authorizeRequests(authorize -> authorize.antMatchers("/user/{userId}/**").access("@webSecurity.checkUserId(authentication,#userId)")...);
函数的安全会稍微复杂点,Spring Security 3.0 提供一些新的注解来解决这些复杂的问题。
什么时候用基于方法的表达式呢? 如果想对用户的权限进行更加详细的控制,例如某个人,只能修改自己的权限内容,那么就可以用到这个功能。但是这个方法,也可以在函数内部实现。我见有些 Sql 语句,会自动添加一个 Where 条件,从当前登录的用户获取 ID。
有四个注释支持表达式属性,以允许调用前和调用后授权检查,并支持过滤提交的集合参数或返回值。它们是@PreAuthorize
,@PreFilter
,@PostAuthorize
和@PostFilter
。 这个需要单独启动。
@PreAuthorize("hasRole('USER')")public void create(Contact contact);
这意味着只有具有“ROLE_USER”角色的用户才能访问。显然,使用传统配置和所需角色的简单配置属性可以轻松实现相同的事情。但是关于:
@PreAuthorize("hasPermission(#contact, 'admin')")public void deletePermission(Contact contact, Sid recipient, Permission permission);
我们实际上使用方法参数作为表达式的一部分来确定当前用户是否具有给定联系人的“管理员”权限。内置hasPermission()
表达式通过应用程序上下文链接到 Spring Security ACL 模块。
@PreAuthorize("#contact.name == authentication.name")public void doSomething(Contact contact);
通常在方法的返回值上执行,例如:
@PreAuthorize("hasRole('USER')")@PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'admin')")public List<Contact> getAll();
该名称filterObject
指的是集合中的当前对象。
您当然可以MethodSecurityInterceptor
直接在您的应用程序上下文中配置一个用于 Spring AOP 的代理机制之一:
<bean id="bankManagerSecurity" class="org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor"><property name="authenticationManager" ref="authenticationManager"/><property name="accessDecisionManager" ref="accessDecisionManager"/><property name="afterInvocationManager" ref="afterInvocationManager"/><property name="securityMetadataSource"><sec:method-security-metadata-source><sec:protect method="com.mycompany.BankManager.delete*" access="ROLE_SUPERVISOR"/><sec:protect method="com.mycompany.BankManager.getBalance" access="ROLE_TELLER,ROLE_SUPERVISOR"/></sec:method-security-metadata-source></property></bean>
AspectJ 安全拦截器与上一节讨论的 AOP 联盟安全拦截器非常相似。事实上,我们将只讨论本节中的差异。
让我们首先考虑AspectJSecurityInterceptor
在 Spring 应用程序上下文中是如何配置的:
<bean id="bankManagerSecurity" class="org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor"><property name="authenticationManager" ref="authenticationManager"/><property name="accessDecisionManager" ref="accessDecisionManager"/><property name="afterInvocationManager" ref="afterInvocationManager"/><property name="securityMetadataSource"><sec:method-security-metadata-source><sec:protect method="com.mycompany.BankManager.delete*" access="ROLE_SUPERVISOR"/><sec:protect method="com.mycompany.BankManager.getBalance" access="ROLE_TELLER,ROLE_SUPERVISOR"/></sec:method-security-metadata-source></property></bean>
可以针对 Service 层定义安全,不常用。所以不深入的理解了。
5.6 版本后在任何一个@Configuration
实例中使用@EnableMethodSecurity
来启用。
相对与@EnableGlobalMethodSecurity
,@EnableMethodSecurity
带来了新的好处:
AuthorizationManager
中非常简单的 API 来替代了以前的metadata sources
,config atrributes
,dicision managers
和votes
,这样简化了重用与定制。GlobalMethodSecurityConfiguration
去进行定制化配置。@PreAuthorize
, @PostAuthorize
, @PreFilter
, 和 @PostFilter
例如,以下将启用 Spring Security 的@PreAuthorize
注释。
@EnableMethodSecuritypublic class MethodSecurityConfig {// ...}
在一个类或接口的方法上添加注解后,就会限制对这个方法的访问。Spring Security 的原生注解支持为方法定义了一组属性, 这些会被传递给 DefaultAuthorizationMethodInterceptorChain
做出判断。
// 下面的代码把权限写死到注解中,让未来很难进行维护public interface BankService {@PreAuthorize("hasRole('USER')")Account readAccount(Long id);@PreAuthorize("hasRole('USER')")List<Account> findAccounts();@PreAuthorize("hasRole('TELLER')")Account post(Account account, Double amount);}
要启动 @Secured
注解,可以这么来配置。
@EnableMethodSecurity(securedEnabled = true)public class MethodSecurityConfig {// ...}
启用@Secured
的具体用法
public interface BankService {@Secured("IS_AUTHENTICATED_ANONYMOUSLY")public Account readAccount(Long id);@Secured("IS_AUTHENTICATED_ANONYMOUSLY")public Account[] findAccounts();@Secured("ROLE_TELLER")public Account post(Account account, double amount);}
或者使用 JSR-250
@EnableMethodSecurity(jsr250Enabled = true)public class MethodSecurityConfig {// ...}
如果您需要自定义处理表达式的方式,您可以公开一个自定义 MethodSecurityExpressionHandler
@Beanstatic MethodSecurityExpressionHandler methodSecurityExpressionHandler() {DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();handler.setTrustResolver(myCustomTrustResolver);return handler;}
提示
- 上面的代码使用了 static,主要目标是确保 Spring 在初始化 Spring Security 的方法 security @Configuration 类之前发布它
可以配置角色的前缀
@Beanstatic GrantedAuthorityDefaults grantedAuthorityDefaults() {return new GrantedAuthorityDefaults("MYPREFIX_");}
提示
- 上面的代码使用了 static
可以在方法执行前后进行授权,如果在执行前被拒绝,那么抛出AccessDeniedException
异常,方法不会被执行。 如果是方法后授权,那么方法会被执行,如果被拒绝了,不会返回值,同样也会抛出AccessDeniedException
异常。
假设要重载系统默认的定义, 那么需要做下面的配置
@EnableMethodSecurity(prePostEnabled = false)class MethodSecurityConfig {@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)Advisor preFilterAuthorizationMethodInterceptor() {return new PreFilterAuthorizationMethodInterceptor();}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)Advisor preAuthorizeAuthorizationMethodInterceptor() {return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)Advisor postAuthorizeAuthorizationMethodInterceptor() {return AuthorizationManagerAfterMethodInterceptor.postAuthorize();}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)Advisor postFilterAuthorizationMethodInterceptor() {return new PostFilterAuthorizationMethodInterceptor();}}
请注意,Spring Security 的方法安全性是使用 Spring AOP
构建的。因此,拦截器是根据指定的顺序调用的。这可以通过在拦截器实例上调用 setOrder
来定制,如下所示:
@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)Advisor postFilterAuthorizationMethodInterceptor() {PostFilterAuthorizationMethodInterceptor interceptor = new PostFilterAuthorizationMethodInterceptor();interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE.getOrder() - 1);return interceptor;}
您可能只想在应用程序中支持 @PreAuthorize
,在这种情况下,您可以执行以下操作:
@EnableMethodSecurity(prePostEnabled = false)class MethodSecurityConfig {@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)Advisor preAuthorize() {return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();}}
或者,您可能为AuthorizationManager
添加一个before
校验的方法。在这种情况下,您需要告诉Spring Security AuthorizationManager
以及您的AuthorizationManager
应用于哪些方法和类。因此,您可以将Spring Security
配置为在@PreAuthorize
和@PostAuthorize
之间调用AuthorizationManager
,如下所示:
//下面的代码不保证可以执行,因为在Spring security中不存在代码中的有些类,可能是笔误@EnableMethodSecurityclass MethodSecurityConfig {@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public Advisor customAuthorize() {JdkRegexpMethodPointcut pattern = new JdkRegexpMethodPointcut();pattern.setPattern("org.mycompany.myapp.service.*");AuthorizationManager<MethodInvocation> rule = AuthorityAuthorizationManager.isAuthenticated();AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(pattern, rule);interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);return interceptor;}}
After method
授权通常涉及分析返回值以验证访问。例如,您可能有一种方法可以确认请求的帐户实际上属于登录用户,如下所示:
public interface BankService {@PreAuthorize("hasRole('USER')")@PostAuthorize("returnObject.owner == authentication.name")Account readAccount(Long id);}
您可以提供自己的 AuthorizationMethodInterceptor
来自定义如何评估对返回值的访问。 例如,如果你有自己的自定义注解,你可以像这样配置它:
@EnableMethodSecurityclass MethodSecurityConfig {@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public Advisor customAuthorize(AuthorizationManager<MethodInvocationResult> rules) {AnnotationMethodMatcher pattern = new AnnotationMethodMatcher(MySecurityAnnotation.class);AuthorizationManagerAfterMethodInterceptor interceptor = new AuthorizationManagerAfterMethodInterceptor(pattern, rules);interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);return interceptor;}}
它将在 @PostAuthorize
拦截器之后调用。
按照上一章节的文档,EnableGlobalMethodSecurity
好像要被替换了,所以这里就不深入讲解了。
所以关于下面的都可以不看了
使用保护切入点添加安全切入点
的使用protect-pointcut
特别强大,因为它允许您只用一个简单的声明就可以对许多 bean 应用安全性。考虑以下示例:
<global-method-security><protect-pointcut expression="execution(* com.mycompany.*Service.*(..))"access="ROLE_USER"/></global-method-security>
Spring Security ACL
ACL 的核心就是 Acl
类,它将 Domain Object 和 对应的 Security Object 以及权限关联了起来。
其中,将 Security Object 和权限关联起来的类是 AccessControlEntry
。
ACL 的原理是这样:
对于系统中的每一个资源,都会配置一个访问列表,这个列表中记录了用户/角色对于资源的 CURD 权限,当系统需要访问这些资源时,会首先检查列表中是否存在当前用户的访问权限,进而确定当前用户是否可以执行相应的操作。
ACL 的使用非常简单,搞明白它的原理自己分分钟就能实现。但是 ACL 有一个明显的缺点,就是需要维护大量的访问权限列表。大量的访问控制列表带来的问题就是性能下降以及维护复杂。
参考文档:
①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳✕✓✔✖
为了实现权限控制,系统维护了这么一个表(实际上是由 4 个表组成)
实际由下面 4 个表组成
acl_class
acl_sid
acl_object_identity
acl_entry
@Servicepublic class NoticeMessageService {@AutowiredNoticeMessageMapper noticeMessageMapper;@PostFilter("hasPermission(filterObject, 'READ')")public List<NoticeMessage> findAll() {List<NoticeMessage> all = noticeMessageMapper.findAll();return all;}@PostAuthorize("hasPermission(returnObject, 'READ')")public NoticeMessage findById(Integer id) {return noticeMessageMapper.findById(id);}@PreAuthorize("hasPermission(#noticeMessage, 'CREATE')")public NoticeMessage save(NoticeMessage noticeMessage) {noticeMessageMapper.save(noticeMessage);return noticeMessage;}@PreAuthorize("hasPermission(#noticeMessage, 'WRITE')")public void update(NoticeMessage noticeMessage) {noticeMessageMapper.update(noticeMessage);}}
让 hr 这个用户可以读取 system_message 表中 id 为 1 的记录,方式如下:
@AutowiredJdbcMutableAclService jdbcMutableAclService;// 这里假设javaboy用户登录了,所以这个 acl 创建好之后,它的 owner 是 javaboypublic void test02() {ObjectIdentity objectIdentity = new ObjectIdentityImpl(NoticeMessage.class, 1);Permission p = BasePermission.READ;//添加了一个Object IdentityMutableAcl acl = jdbcMutableAclService.createAcl(objectIdentity);acl.insertAce(acl.getEntries().size(), p, new PrincipalSid("hr"), true);jdbcMutableAclService.updateAcl(acl);}
在这个过程中,会分别向 acl_entry、acl_object_identity 以及 acl_sid 三张表中添加记录