Spring Security中一般有两种方式来对访问进行鉴权,第一种是在HttpSecurity上配置路径和相应访问所需的权限,第二种是在方法上添加注解来定义接口的访问权限
一、注解定义权限
1、让鉴权注解生效
要能通过在方法上添加注解来控制访问权限,首先就是要让注解可以被识别到。在Spring Security中是通过MethodInterceptor来实现对方法执行的拦截以及解析方法上的注解的。
有以下两种方式来添加各个MethodInterceptor
方式1:让注解生效的XML解析器
在继续前我们先看下MethodSecurityBeanDefinitionParser
,该类本身是一个BeanDefinitionParser类型,可见其作用是解析Bean的定义。然而,这个类是怎么被使用的呢,这就需要看spring-security-config下的META-INF\spring.handlers
文件了。该文件的配置如下所示:
http\://www.springframework.org/schema/security
=org.springframework.security.config.SecurityNamespaceHandler
我们知道这个配置的是用于解析xml命名空间的解析器,对于以前通过spring-security.xml来配置的时候,这个就可以用于解析<method-security>元素了。例如有以下配置
<method-security jsr250-enabled="true", secured-enabled="true">
在被MethodSecurityBeanDefinitionParser解析后就能知道是否要启用jsr250注解和secured注解了,如果开启了则会创建 Jsr250AuthorizationMethodInterceptor和SecuredAuthorizationMethodInterceptor的BeanDefinition信息,从而被添加到IOC容器。
public final class SecurityNamespaceHandler
implements NamespaceHandler {
@Override
public void init() {
loadParsers();
}
//加载解析器
private void loadParsers() {
// Parsers
this.parsers.put(Elements.LDAP_PROVIDER, new LdapProviderBeanDefinitionParser());
this.parsers.put(Elements.LDAP_SERVER, new LdapServerBeanDefinitionParser());
this.parsers.put(Elements.LDAP_USER_SERVICE, new LdapUserServiceBeanDefinitionParser());
this.parsers.put(Elements.USER_SERVICE, new UserServiceBeanDefinitionParser());
this.parsers.put(Elements.JDBC_USER_SERVICE, new JdbcUserServiceBeanDefinitionParser());
this.parsers.put(Elements.AUTHENTICATION_PROVIDER, new AuthenticationProviderBeanDefinitionParser());
this.parsers.put(Elements.GLOBAL_METHOD_SECURITY, new GlobalMethodSecurityBeanDefinitionParser());
//注意:这里被添加进去的
this.parsers.put(Elements.METHOD_SECURITY, new MethodSecurityBeanDefinitionParser());
this.parsers.put(Elements.AUTHENTICATION_MANAGER, new AuthenticationManagerBeanDefinitionParser());
this.parsers.put(Elements.METHOD_SECURITY_METADATA_SOURCE,
new MethodSecurityMetadataSourceBeanDefinitionParser());
if (ClassUtils.isPresent(FILTER_CHAIN_PROXY_CLASSNAME, getClass().getClassLoader())) {
loadWebParsers();
}
if (ClassUtils.isPresent(MESSAGE_CLASSNAME, getClass().getClassLoader())) {
loadWebSocketParsers();
}
}
}
更多细节可查阅MethodSecurityBeanDefinitionParser
的源码
方式2:让注解生效的Configuration
上面有讲到采用xml文件配置时,会通过MethodSecurityBeanDefinitionParser来解析元素并创建对应的MethodInterceptor,从而可以拦截方法的执行来验证需要的权限。
除此以外,现在最常用的就是通过配置类来完成Bean的注册了,那么通过配置类是怎么做的呢,答案从@EnableMethodSecurity
注解出发
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MethodSecuritySelector.class)
public @interface EnableMethodSecurity {
boolean prePostEnabled() default true;
boolean securedEnabled() default false;
boolean jsr250Enabled() default false;
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
}
注解上Import了MethodSecuritySelector
final class MethodSecuritySelector implements ImportSelector {
@Override
public String[] selectImports(@NonNull AnnotationMetadata importMetadata) {
if (!importMetadata.hasAnnotation(EnableMethodSecurity.class.getName())) {
return new String[0];
}
//获取注解
EnableMethodSecurity annotation = importMetadata.getAnnotations().get(EnableMethodSecurity.class).synthesize();
List<String> imports = new ArrayList<>(Arrays.asList(this.autoProxy.selectImports(importMetadata)));
//判断注解上的配置,也就是里面的几个属性
if (annotation.prePostEnabled()) {
//如果prePostEnabled设置为ture
//则添加PrePostMethodSecurityConfiguration配置类
imports.add(PrePostMethodSecurityConfiguration.class.getName());
}
if (annotation.securedEnabled()) {
//如果securedEnabled设置为ture
//则添加SecuredMethodSecurityConfiguration配置类
imports.add(SecuredMethodSecurityConfiguration.class.getName());
}
if (annotation.jsr250Enabled()) {
//如果jsr250Enabled设置为ture
//则添加Jsr250MethodSecurityConfiguration配置类
imports.add(Jsr250MethodSecurityConfiguration.class.getName());
}
return imports.toArray(new String[0]);
}
}
可见,会根据我们对注解的设置而添加不同的配置类
- 在PrePostMethodSecurityConfiguration中,完成了以下Bean的创建
- PreFilterAuthorizationMethodInterceptor
- PostFilterAuthorizationMethodInterceptor
- AuthorizationManagerBeforeMethodInterceptor
- AuthorizationManagerAfterMethodInterceptor
- 在SecuredMethodSecurityConfiguration中,完成了以下Bean的创建
- AuthorizationManagerBeforeMethodInterceptor
- 在Jsr250MethodSecurityConfiguration中,完成了以下Bean的创建
- AuthorizationManagerBeforeMethodInterceptor
其添加的MethodInterceptor与使用xml配置解析的结果是一样的,将拦截器添加到IOC后,就可以正常的拦截标记有相应注解的方法了。
入口清晰了,接下来就要看各个MethodInterceptor具体是如何工作的了。
2、MethodInterceptor的执行逻辑
首先我们需要知道,不同的MethodInterceptor与不同的AuthorizationManager是有绑定关系的。MethodInterceptor负责拦截方法的执行,解析需要的权限,AuthorizationManager则负责对权限的校验,下面我们选择几个看看
1)Jsr250MethodSecurity
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
final class Jsr250MethodSecurityConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor jsr250AuthorizationMethodInterceptor(ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
ObjectProvider<ObservationRegistry> registryProvider) {
//1. 直接实例化Jsr250AuthorizationManager
Jsr250AuthorizationManager jsr250 = new Jsr250AuthorizationManager();
defaultsProvider.ifAvailable((d) -> jsr250.setRolePrefix(d.getRolePrefix()));
//2. 获取SecurityContext的持有策略
SecurityContextHolderStrategy strategy = strategyProvider
.getIfAvailable(SecurityContextHolder::getContextHolderStrategy);
AuthorizationManager<MethodInvocation> manager = new DeferringObservationAuthorizationManager<>(
registryProvider, jsr250);
//4. 创建方法拦截器
AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
.jsr250(manager);
interceptor.setSecurityContextHolderStrategy(strategy);
return interceptor;
}
}
Jsr250AuthorizationManager
public final class Jsr250AuthorizationManager implements AuthorizationManager<MethodInvocation> {
private final Jsr250AuthorizationManagerRegistry registry
= new Jsr250AuthorizationManagerRegistry();
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation methodInvocation) {
//执行下面内部类方法获取匹配的AuthorizationManager
AuthorizationManager<MethodInvocation> delegate = this.registry.getManager(methodInvocation);
//使用匹配的AuthorizationManager执行鉴权
return delegate.check(authentication, methodInvocation);
}
private final class Jsr250AuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {
@NonNull
@Override
AuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass) {
//获得方法上的注解
Annotation annotation = findJsr250Annotation(method, targetClass);
if (annotation instanceof DenyAll) {
//如果是DenyAll,则返回check方法返回
//固定AuthorizationDecision的AuthorizationManager
return (a, o) -> new AuthorizationDecision(false);
}
if (annotation instanceof PermitAll) {
//如果是PermitAll,则返回check方法返回
//固定AuthorizationDecision的AuthorizationManager
return (a, o) -> new AuthorizationDecision(true);
}
if (annotation instanceof RolesAllowed) {
//如果是RolesAllowed,则调用AuthorityAuthorizationManager
//的hasAnyRole来判断是否具备权限
RolesAllowed rolesAllowed = (RolesAllowed) annotation;
return AuthorityAuthorizationManager.hasAnyRole(Jsr250AuthorizationManager.this.rolePrefix,
rolesAllowed.value());
}
return NULL_MANAGER;
}
}
}
可见,其最终依托的是 AuthorityAuthorizationManager,接着看些拦截器里面都做了啥 AuthorizationManagerBeforeMethodInterceptor
public final class AuthorizationManagerBeforeMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
private Supplier<Authentication> authentication = getAuthentication(
SecurityContextHolder.getContextHolderStrategy());
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
//进行鉴权
attemptAuthorization(mi);
return mi.proceed();
}
private void attemptAuthorization(MethodInvocation mi) {
this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi));
//继续鉴权(调用上面的Jsr250AuthorizationManager)
AuthorizationDecision decision = this.authorizationManager.check(this.authentication, mi);
this.eventPublisher.publishAuthorizationEvent(this.authentication, mi, decision);
//根据响应的结果来决定抛出异常还是顺利执行
if (decision != null && !decision.isGranted()) {
this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager "
+ this.authorizationManager + " and decision " + decision));
throw new AccessDeniedException("Access Denied");
}
this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi));
}
}
既然上面用到了AuthorityAuthorizationManager,那么我们就看看AuthorityAuthorizationManager是怎么做的
public final class AuthorityAuthorizationManager<T>
implements AuthorizationManager<T> {
public static <T> AuthorityAuthorizationManager<T> hasAnyRole(String rolePrefix, String[] roles) {
Assert.notNull(rolePrefix, "rolePrefix cannot be null");
Assert.notEmpty(roles, "roles cannot be empty");
Assert.noNullElements(roles, "roles cannot contain null values");
//将角色名转换成待"ROLE_"前缀的值
return hasAnyAuthority(toNamedRolesArray(rolePrefix, roles));
}
public static <T> AuthorityAuthorizationManager<T> hasAnyAuthority(String... authorities) {
Assert.notEmpty(authorities, "authorities cannot be empty");
Assert.noNullElements(authorities, "authorities cannot contain null values");
//新建实例并返回
return new AuthorityAuthorizationManager<>(authorities);
}
//执行鉴权
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, T object) {
boolean granted = isGranted(authentication.get());
return new AuthorityAuthorizationDecision(granted, this.authorities);
}
private boolean isGranted(Authentication authentication) {
return authentication != null
&& authentication.isAuthenticated()
&& isAuthorized(authentication);
}
private boolean isAuthorized(Authentication authentication) {
//具备的权限
Set<String> authorities = AuthorityUtils.authorityListToSet(this.authorities);
//获得具有继承关系的权限并判断【默认是NullRoleHierarchy,即没有继承关系】
for (GrantedAuthority grantedAuthority : getGrantedAuthorities(authentication)) {
if (authorities.contains(grantedAuthority.getAuthority())) {
return true;
}
}
return false;
}
private Collection<? extends GrantedAuthority> getGrantedAuthorities(Authentication authentication) {
return this.roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities());
}
}
其实就是对认证完填充后的权限值进行对比
2)SecuredMethodSecurity
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
final class SecuredMethodSecurityConfiguration {
//这里的逻辑和上面的很相似,主要区别在于这里
//使用的是SecuredAuthorizationManager
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor securedAuthorizationMethodInterceptor(ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
ObjectProvider<ObservationRegistry> registryProvider) {
//创建鉴权管理器
SecuredAuthorizationManager secured = new SecuredAuthorizationManager();
SecurityContextHolderStrategy strategy = strategyProvider
.getIfAvailable(SecurityContextHolder::getContextHolderStrategy);
AuthorizationManager<MethodInvocation> manager = new DeferringObservationAuthorizationManager<>(
registryProvider, secured);
//创建方法拦截器【与上面一致】
AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
.secured(manager);
interceptor.setSecurityContextHolderStrategy(strategy);
return interceptor;
}
}
SecuredAuthorizationManager
public final class SecuredAuthorizationManager
implements AuthorizationManager<MethodInvocation> {
private final SecuredAuthorizationManagerRegistry registry
= new SecuredAuthorizationManagerRegistry();
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi) {
AuthorizationManager<MethodInvocation> delegate = this.registry.getManager(mi);
return delegate.check(authentication, mi);
}
private static final class SecuredAuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {
@NonNull
@Override
AuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
Secured secured = findSecuredAnnotation(specificMethod);
//上面因为是判断角色所以调了hasAnyRole方法
//这里是判断权限所以用了hasAnyAuthority
return (secured != null) ? AuthorityAuthorizationManager.hasAnyAuthority(secured.value()) : NULL_MANAGER;
}
private Secured findSecuredAnnotation(Method method) {
Secured secured = AuthorizationAnnotationUtils.findUniqueAnnotation(method, Secured.class);
return (secured != null) ? secured
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), Secured.class);
}
}
}
与Jsr250MethodSecurity的区别只在于一个验证角色,一个验证权限,其他的都基本一样,不再赘述
3)PrePostMethodSecurity
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
final class PrePostMethodSecurityConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor preFilterAuthorizationMethodInterceptor(ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider, ApplicationContext context) {
//创建拦截器
PreFilterAuthorizationMethodInterceptor preFilter = new PreFilterAuthorizationMethodInterceptor();
strategyProvider.ifAvailable(preFilter::setSecurityContextHolderStrategy);
//设置表达式解析器【@PreFilter注解中的el表达式】
preFilter.setExpressionHandler(
expressionHandlerProvider.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, context)));
return preFilter;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor preAuthorizeAuthorizationMethodInterceptor(ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
ObjectProvider<AuthorizationEventPublisher> eventPublisherProvider,
ObjectProvider<ObservationRegistry> registryProvider, ApplicationContext context) {
//创建对应的前置鉴权器
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
//设置表达式解析器
manager.setExpressionHandler(
expressionHandlerProvider.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, context)));
//创建方法执行前鉴权拦截器
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor
.preAuthorize(manager(manager, registryProvider));
strategyProvider.ifAvailable(preAuthorize::setSecurityContextHolderStrategy);
eventPublisherProvider.ifAvailable(preAuthorize::setAuthorizationEventPublisher);
return preAuthorize;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor postAuthorizeAuthorizationMethodInterceptor(ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
ObjectProvider<AuthorizationEventPublisher> eventPublisherProvider,
ObjectProvider<ObservationRegistry> registryProvider, ApplicationContext context) {
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
manager.setExpressionHandler(
expressionHandlerProvider.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, context)));
AuthorizationManagerAfterMethodInterceptor postAuthorize = AuthorizationManagerAfterMethodInterceptor
.postAuthorize(manager(manager, registryProvider));
strategyProvider.ifAvailable(postAuthorize::setSecurityContextHolderStrategy);
eventPublisherProvider.ifAvailable(postAuthorize::setAuthorizationEventPublisher);
return postAuthorize;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor postFilterAuthorizationMethodInterceptor(ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider, ApplicationContext context) {
PostFilterAuthorizationMethodInterceptor postFilter = new PostFilterAuthorizationMethodInterceptor();
strategyProvider.ifAvailable(postFilter::setSecurityContextHolderStrategy);
postFilter.setExpressionHandler(
expressionHandlerProvider.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, context)));
return postFilter;
}
private static MethodSecurityExpressionHandler defaultExpressionHandler(
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider, ApplicationContext context) {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
defaultsProvider.ifAvailable((d) -> handler.setDefaultRolePrefix(d.getRolePrefix()));
handler.setApplicationContext(context);
return handler;
}
static <T> AuthorizationManager<T> manager(AuthorizationManager<T> delegate,
ObjectProvider<ObservationRegistry> registryProvider) {
return new DeferringObservationAuthorizationManager<>(registryProvider, delegate);
}
}
对于注解这种鉴权方式,其整体的逻辑就是创建方法的拦截器,解析注解然后根据注解的含义使用对应的AuthorizationManager实现类进行角色或者权限的校验。
以下展示了Spring Security中的一些AuthorizationManager
- AuthenticatedAuthorizationManager<T> : 用于确定当前用户是否经过了身份验证【authenticated、fullyAuthenticated、rememberMe、anonymous、】
- AuthorityAuthorizationManager<T> :通过评估Authentication是否包含指定的权限来确定当前用户是否已被授权【hasRole、hasAuthority、hasAnyRole、hasAnyAuthority】
- Jsr250AuthorizationManager<MethodInvocation>:它可以通过评估身份验证是否包含来自JSR-250安全注释的指定权限来确定身份验证是否可以调用MethodInvocation【DenyAll、PermitAll、RolesAllowed】
- SecuredAuthorizationManager<MethodInvocation>:它可以通过评估身份验证是否包含Spring Security的Secured注释中的指定权限来确定身份验证是否可以调用MethodInvocation【Secured】
- PreAuthorizeAuthorizationManager<MethodInvocation>:它可以通过计算PreAuthorize 注释中的表达式来确定Authentication是否可以调用MethodInvocation。
- PostAuthorizeAuthorizationManager<MethodInvocationResult>:它可以通过计算来自PostAuthorize注释的表达式来确定一个Authentication是否可以从调用的MethodInvocation返回结果。
- MethodExpressionAuthorizationManager<MethodInvocation>:基于表达式的AuthorizationManager,通过根据MethodInvocation计算提供的表达式来确定访问。
- WebExpressionAuthorizationManager<RequestAuthorizationContext>:基于表达式的AuthorizationManager,通过计算提供的表达式来确定访问。
在包org.springframework.security.authorization下涉及了鉴权的代码,主要的拦截器有以下几个
- AuthorizationManagerBeforeMethodInterceptor
- AuthorizationManagerAfterMethodInterceptor
- PreFilterAuthorizationMethodInterceptor
- PostFilterAuthorizationMethodInterceptor
- AuthorizationManagerBeforeReactiveMethodInterceptor
- AuthorizationManagerAfterReactiveMethodInterceptor
- PreFilterAuthorizationReactiveMethodInterceptor
- PostFilterAuthorizationReactiveMethodInterceptor
二、HttpSecurity上配置权限
我们可以通过如下的方式对Http请求进行权限配置
@Configuration
public class Configuration {
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
return http
//授权请求配置(以前版本是authorizeRequests())
.authorizeHttpRequests()
//匹配的路径都可以访问
.requestMatchers("/auth/code","/error").permitAll()
//具备某个角色的用户才可以访问的路径
.requestMatchers("/admin/**").hasAnyRole("admin")
//具备某个权限的用户才可以访问的路径
.requestMatchers("/show/**").hasAnyAuthority("P001")
//其他的请求都需要已登录
.anyRequest().authenticated()
.and()
.build() ;
}
}
当执行HttpSecurity的authorizeHttpRequests()方法时,其内部会创建一个AuthorizeHttpRequestsConfigurer的实例对象,同时返回调用该对象getRegistry()方法返回的AuthorizationManagerRequestMatcherRegistry进行后续的配置。
AuthorizeHttpRequestsConfigurer在被执行configure方法时会创建AuthorizationFilter过滤器,并添加到HttpSecurity中。而在创建AuthorizationFilter实例时,需要传递一个AuthorizationManager对象作为构造参数。
1)AuthorizationFilter
首先看下AuthorizationFilter内部都做了些啥
public class AuthorizationFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain chain)
throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if (this.observeOncePerRequest && isApplied(request)) {
chain.doFilter(request, response);
return;
}
if (skipDispatch(request)) {
chain.doFilter(request, response);
return;
}
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
//重点:获取到当前认证后的Authentication
//调用authorizationManager的check方法
//根据返回的结果决定抛出异常还是继续执行后续的过滤器
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request);
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, decision);
if (decision != null && !decision.isGranted()) {
throw new AccessDeniedException("Access Denied");
}
chain.doFilter(request, response);
}
finally {
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}
其逻辑和上面介绍的方法拦截器是一样的,重点还是看其使用的authorizationManager是如何完成鉴权的。
2)AuthorizationManager
至于AuthorizationManager是如何被创建的,我想通过以下的图来大体说明mxCell
鉴权是通过AuthorizationManager实现,在使用时会有不同的鉴权要求,也就会有不同鉴权形式的AuthorizationManager。
首先我们配置要鉴权的请求匹配规则,然后给这规则关联上对应的AuthorizationManager。请求的匹配由AuthorizationManagerRequestMatcherRegistry
负责,请求路径的鉴权形式由AuthorizedUrl
配置并创建匹配的AuthorizationManager,最终再由AuthorizeHttpRequestsConfigurer
来触发构建出AuthorizationManager并创建AuthorizationFilter
主要有以下两个AuthorizationManager
- AuthorityAuthorizationManager:主要是通过已认证的Authentication中保存的GrantedAuthority数据来匹配判断,包含角色和一般权限值的匹配
- AuthenticatedAuthorizationManager:用于确定当前用户是否经过身份验证,其内部依赖具体的AbstractAuthorizationStrategy去判断。
- AuthenticatedAuthorizationStrategy:判断是否是成功登录的非匿名用户
- RememberMeAuthorizationStrategy:指示传递的身份验证令牌是否表示已记住的用户(即不是已完全身份验证的用户)。
- FullyAuthenticatedAuthorizationStrategy:判断是否是成功登录的非匿名用户,且不是来自”记住我的用户“
- AnonymousAuthorizationStrategy:指示传递的身份验证令牌是否表示匿名用户
需要说明是是,这里介绍的都是Spring Security6.0版本的实现,在先前版本中的AccessDecisionVoter、AccessDecisionManager相关的方式已经不推荐了,被AuthorizationManager取代。
3)ExceptionTranslationFilter
鉴权可能失败,我们看AuthorizationFilter和各个方法拦截器,在鉴权失败后都是抛出AccessDeniedException
异常,那么该异常是在哪里被处理,以及按照什么策略进行处理的呢,关于这点可以从ExceptionTranslationFilter
说起。
public class ExceptionTranslationFilter extends GenericFilterBean
implements MessageSourceAware {
//访问拒绝处理器
private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
//认证入口
private AuthenticationEntryPoint authenticationEntryPoint;
//身份验证信任解析
private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
//异常分析
private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
//重点:捕获了后续代码的执行,从而可对抛出的异常进行分析
try {
chain.doFilter(request, response);
}
catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
// Try to extract a SpringSecurityException from the stacktrace
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
//分析异常是否是AuthenticationException
RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (securityException == null) {
//分析异常是否是AccessDeniedException
securityException = (AccessDeniedException) this.throwableAnalyzer
.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
}
if (securityException == null) {
//如果不是AuthenticationException或AccessDeniedException则重新抛出
rethrow(ex);
}
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception "
+ "because the response is already committed.", ex);
}
//处理AuthenticationException或AccessDeniedException
handleSpringSecurityException(request, response, chain, securityException);
}
}
private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, RuntimeException exception) throws IOException, ServletException {
if (exception instanceof AuthenticationException) {
handleAuthenticationException(request, response, chain, (AuthenticationException) exception);
}
else if (exception instanceof AccessDeniedException) {
handleAccessDeniedException(request, response, chain, (AccessDeniedException) exception);
}
}
private void handleAuthenticationException(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, AuthenticationException exception) throws ServletException, IOException {
this.logger.trace("Sending to authentication entry point since authentication failed", exception);
//开始发起认证
sendStartAuthentication(request, response, chain, exception);
}
protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
AuthenticationException reason) throws ServletException, IOException {
// SEC-112: Clear the SecurityContextHolder's Authentication, as the
// existing Authentication is no longer considered valid
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
this.securityContextHolderStrategy.setContext(context);
this.requestCache.saveRequest(request, response);
//触发authenticationEntryPoint发起认证操作
this.authenticationEntryPoint.commence(request, response, reason);
}
private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, AccessDeniedException exception) throws ServletException, IOException {
Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
//判断是否是匿名用户
boolean isAnonymous = this.authenticationTrustResolver.isAnonymous(authentication);
//如果是匿名或者"记住我的用户token"
if (isAnonymous || this.authenticationTrustResolver.isRememberMe(authentication)) {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Sending %s to authentication entry point since access is denied",
authentication), exception);
}
sendStartAuthentication(request, response, chain,
new InsufficientAuthenticationException(
this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication",
"Full authentication is required to access this resource")));
} else {
if (logger.isTraceEnabled()) {
logger.trace(
LogMessage.format("Sending %s to access denied handler since access is denied", authentication),
exception);
}
//使用认证被拒绝的处理器进行处理
this.accessDeniedHandler.handle(request, response, exception);
}
}
}
其整体的逻辑还是比较清晰,捕获后续的异常,对异常进行解析,然后根据异常来决定触发登录操作还是执行某些逻辑。
这里有两个类型以及对应的实现类可以关注下
- AuthenticationEntryPoint:用于启动身份验证方案
- LoginUrlAuthenticationEntryPoint
- Http403ForbiddenEntryPoint
- HttpStatusEntryPoint
- BasicAuthenticationEntryPoint
- DigestAuthenticationEntryPoint
- DelegatingAuthenticationEntryPoint
- AccessDeniedHandler:处理AccessDeniedException异常
- AccessDeniedHandlerImpl
- InvalidSessionAccessDeniedHandler
- ObservationMarkingAccessDeniedHandler
- DelegatingAccessDeniedHandler
- CompositeAccessDeniedHandler
- RequestMatcherDelegatingAccessDeniedHandler
在很多场景下我们都可能需要自定义AuthenticationEntryPoint与AccessDeniedHandler的实现。
通过以下的配置就可以让ExceptionTranslationFilter生效
@Configuration
public class Configuration {
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
return http
.exceptionHandling()
.accessDeniedHandler(/*自定义的AccessDeniedHandler*/)
.authenticationEntryPoint(/*自定义的AuthenticationEntryPoint*/)
.and()
.build() ;
}
}
以上便是Spring Security 6 的整体鉴权逻辑,只要抓住AuthorizationFilter和AuthorizationManager进行推导就容易理解了。