Spring Security内置过滤器详解_springsecurity过滤器(1)

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
img

正文

}

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
//通过requestMatcher判断request请求是否需要处理
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
try {
//获取身份认证结果,并创建认证对象,由子类实现,OAuth2 使用的是OAuth2LoginAuthenticationFilter
Authentication authenticationResult = attemptAuthentication(request, response);
if (authenticationResult == null) {
// return immediately as subclass has indicated that it hasn’t completed
return;
}
//对会话进行处理,防止会话固定攻击(session-fixation详情可网上查询)
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
//认证成功后,是否继续执行后面的过滤器
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//处理认证成功后的处理逻辑,委托给AuthenticationSuccessHandler,此处为SavedRequestAwareAuthenticationSuccessHandler
successfulAuthentication(request, response, chain, authenticationResult);
}
catch (InternalAuthenticationServiceException failed) {
this.logger.error(“An internal error occurred while trying to authenticate the user.”, failed);
//认证失败处理
unsuccessfulAuthentication(request, response, failed);
}
catch (AuthenticationException ex) {
// Authentication failed
//认证失败处理
unsuccessfulAuthentication(request, response, ex);
}
}


其中attemptAuthentication处的代码量很大,作用是:获取身份认证结果,并创建认证对象,由子类实现,OAuth2 使用的是OAuth2LoginAuthenticationFilter,我们来看看这部分



public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
//从授权服务器回调的URL请求中,将请求参数转换为map格式,key=参数名,value=参数值
MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());
//根据code、status、error字段判断是否为返回请求
if (!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) {
OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
//返回OAuth2AuthorizationRequest,该对象在OAuth2LoginAuthenticationFilter中构建
//removeAuthorizationRequest官方的话是,但不太理解为什么要remove
//Removes and returns the OAuth2AuthorizationRequest associated to the provided
//HttpServletRequest and HttpServletResponse or if not available returns null.
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository
.removeAuthorizationRequest(request, response);
if (authorizationRequest == null) {
OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
String registrationId = authorizationRequest.getAttribute(OAuth2ParameterNames.REGISTRATION_ID);
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
if (clientRegistration == null) {
OAuth2Error oauth2Error = new OAuth2Error(CLIENT_REGISTRATION_NOT_FOUND_ERROR_CODE,
"Client Registration not found with Id: " + registrationId, null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
// @formatter:off
String redirectUri = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
.replaceQuery(null)
.build()
.toUriString();
// @formatter:on
//构建OAuth2授权响应对象
OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params,
redirectUri);
//从request构建一个完整的身份认证信息
Object authenticationDetails = this.authenticationDetailsSource.buildDetails(request);
//构建OAuth2认证Token
OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(clientRegistration,
new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
authenticationRequest.setDetails(authenticationDetails);
//请求授权服务器的token-uri,进行身份认证,返回完整的OAuth2认证Token
//请求user-info-uri,获取资源所有者主体信息
//会轮询所有的provider,直到遇到支持的provider,该处为OAuth2LoginAuthenticationProvider
OAuth2LoginAuthenticationToken authenticationResult = (OAuth2LoginAuthenticationToken) this
.getAuthenticationManager().authenticate(authenticationRequest);
//转换成需要返回的OAuth2AuthenticationToken
OAuth2AuthenticationToken oauth2Authentication = this.authenticationResultConverter
.convert(authenticationResult);
Assert.notNull(oauth2Authentication, “authentication result cannot be null”);
oauth2Authentication.setDetails(authenticationDetails);
//构建最终的OAuth2 授权客户端
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
authenticationResult.getClientRegistration(), oauth2Authentication.getName(),
authenticationResult.getAccessToken(), authenticationResult.getRefreshToken());
//保存授权相关信息,此处保存到本地
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response);
return oauth2Authentication;
}


## DefaultLoginPageGeneratingFilter


说明:默认的登录页面  
 当没有配置自定义登录页时,将使用该默认登录页  
 如下的doFilter核心代码:



@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 {
//当前请求是否是failureUrl指定的地址
boolean loginError = isErrorPage(request);
//当前请求是否是logoutSuccessUrl指定的地址
boolean logoutSuccess = isLogoutSuccess(request);
//是否是登录请求,是否是重定向的错误请求,是否是登出地址
if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
//生成一个默认的登录页
String loginPageHtml = generateLoginPageHtml(request, loginError, logoutSuccess);
response.setContentType(“text/html;charset=UTF-8”);
response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
response.getWriter().write(loginPageHtml);
return;
}
//否则执行下一个过滤器
chain.doFilter(request, response);
}


## DefaultLogoutPageGeneratingFilter


说明:默认的登出页面  
 当请求地址为GET请求,/logout时,生成一个默认的登出页面  
 核心代码如下:



@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
//地址是/logout
if (this.matcher.matches(request)) {
//生成一个登出页面
renderLogout(request, response);
}
else {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format(“Did not render default logout page since request did not match [%s]”,
this.matcher));
}
filterChain.doFilter(request, response);
}
}


界面如下:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/8680b0e73b8a4541b2379345820dfd99.png#pic_center)


## RequestCacheAwareFilter


说明:  
 从session中获取SavedRequest,如果当前请求信息和SaveRequest信息一致(一般是登录成功后重定向),则返回SavedRequestAwareWrapper的HttpServletRequest包装类


## SecurityContextHolderAwareRequestFilter


说明:  
 通过HttpServletRequestFactory将HttpServletRequest请求包装成SecurityContextHolderAwareRequestWrapper,它实现了HttpServletRequest,并进行了扩展,添加一些额外的方法,比如:getPrincipal()方法等。这样就可以那些需要Principal等参数的Controller就可以接收到对应参数了。除了这个地方的应用,在其他地方,也可以直接调用request#getUserPrincipal()获取对应信息。


## AnonymousAuthenticationFilter


说明:  
 匿名过滤器,如果执行到该过滤器时还没有主体,则创建一个匿名主体


## OAuth2AuthorizationCodeGrantFilter


说明:  
 OAuth2授权码授权过滤器,跟OAuth2LoginAuthenticationFilter很像,不知道在这里的作用是什么?  
 是为了兜底?


## SessionManagementFilter


Session管理过滤器


## ExceptionTranslationFilter


处理过滤器链中抛出的AccessDeniedException和AuthenticationException 异常


## FilterSecurityInterceptor


对资源的过滤处理  
 核心代码如下:



public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
//该请求已经运行过该过滤,跳过,执行下一个过滤器
if (isApplied(filterInvocation) && this.observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don’t re-do security checking
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
return;
}
// first time this request being called, so perform security checking
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
//获取当前的权限配置
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
try {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
}
finally {
//设置SecurityContextHolderStrategy context
super.finallyInvocation(token);
}
//最后的处理
super.afterInvocation(token, null);
}


获取当前 request 对应的权限配置,首先是调用基类的 beforeInvocation 方法 。


看一下基类的 beforeInvocation 方法,从配置好的 SecurityMetadataSource 中获取当前 request 所对应的 ConfigAttribute,即权限信息。



protected InterceptorStatusToken beforeInvocation(Object object) {

Collection attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
if (attributes == null || attributes.isEmpty()) {
if (rejectPublicInvocations) {
throw new IllegalArgumentException(
"Secure object invocation "
+ object
+ " was denied as public invocations are not allowed via this interceptor. "
+ "This indicates a configuration error because the "
+ “rejectPublicInvocations property is set to ‘true’”);
}

}
}


这里需要注意一下 rejectPublicInvocations 属性,默认为 false。此属性含义为拒绝公共请求。如果从配置好的 SecurityMetadataSource 中获取不到当前 request 所对应的 ConfigAttribute 时,即认为当前请求为公共请求。如配置 rejectPublicInvocations 属性为 true,则系统会抛出 IllegalArgumentException 异常,即当前请求需要配置权限信息。


接下来,就要判断是否需要进行身份认证了,即调用 authenticateIfRequired 方法。



protected InterceptorStatusToken beforeInvocation(Object object) {

Authentication authenticated = authenticateIfRequired();

......

}


而判断及身份认证逻辑也并不复杂,首先会判断当前用户是否已通过身份认证,如果已通过身份认证,则直接返回;如果尚未通过身份认证,则调用身份认证管理器 AuthenticationManager 进行认证,就如同登录时一样。认证通过后,同样会在当前的安全上下文中存储一份认证后的 authentication。



private Authentication authenticateIfRequired() {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (authentication.isAuthenticated() && !alwaysReauthenticate) {
if (logger.isDebugEnabled()) {
logger.debug("Previously Authenticated: " + authentication);
}
return authentication;
}
authentication = authenticationManager.authenticate(authentication);
// We don’t authenticated.setAuthentication(true), because each provider should do
// that
if (logger.isDebugEnabled()) {
logger.debug("Successfully Authenticated: " + authentication);
}
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
}


然后,使用获取到的 ConfigAttribute ,继续调用访问控制器 AccessDecisionManager 对当前请求进行鉴权。



protected InterceptorStatusToken beforeInvocation(Object object) {

// Attempt authorization
try {
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
if (debug) {
logger.debug(“Authorization successful”);
}
if (publishAuthorizationSuccess) {
publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
}


注意,无论鉴权通过或是不通后,Spring Security 框架均使用了观察者模式,来通知其它Bean,当前请求的鉴权结果。  
 如果鉴权不通过,则会抛出 AccessDeniedException 异常,即访问受限,然后会被 ExceptionTranslationFilter 捕获,最终解析后调转到对应的鉴权失败页面。


如果鉴权通过,AbstractSecurityInterceptor 通常会继续请求。但是,在极少数情况下,用户可能希望使用不同的 Authentication 来替换 SecurityContext 中的 Authentication。该身份认证就会由 RunAsManager 来处理。这在某些业务场景下可能很有用,录入服务层方法需要调用远程系统并呈现不同的身份。因为 Spring Security 会自动将安全标识从一个服务器传播到另一个服务器(假设使用的是正确配置的 RMI 或 HttpInvoker 远程协议客户端),这就可能很有用。


在 AccessDecisionManager 鉴权成功后,将通过 RunAsManager 在现有 Authentication 基础上构建一个新的Authentication,如果新的 Authentication 不为空则将产生一个新的 SecurityContext,并把新产生的Authentication 存放在其中。这样在请求受保护资源时从 SecurityContext中 获取到的 Authentication 就是新产生的 Authentication。



protected InterceptorStatusToken beforeInvocation(Object object) {

    // Attempt to run as a different user
    Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
                                                        attributes);

if (runAs == null) {
    if (debug) {
        logger.debug("RunAsManager did not change Authentication object");
    }

    // no further work post-invocation
    return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
                                      attributes, object);
}
else {
    if (debug) {
        logger.debug("Switching to RunAs Authentication: " + runAs);
    }

    SecurityContext origCtx = SecurityContextHolder.getContext();
    SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
    SecurityContextHolder.getContext().setAuthentication(runAs);

    // need to revert to token.Authenticated post-invocation
    return new InterceptorStatusToken(origCtx, true, attributes, object);
}

}


注意,AbstractSecurityInterceptor 默认持有的是 RunAsManager 的空实现 NullRunAsManager。



public abstract class AbstractSecurityInterceptor implements InitializingBean,
ApplicationEventPublisherAware, MessageSourceAware {

private RunAsManager runAsManager = new NullRunAsManager();

}


待请求完成后会在 finallyInvocation() 中将原来的 SecurityContext 重新设置给SecurityContextHolder。



protected void finallyInvocation(InterceptorStatusToken token) {
if (token != null && token.isContextHolderRefreshRequired()) {
if (logger.isDebugEnabled()) {
logger.debug("Reverting to original Authentication: "
+ token.getSecurityContext().getAuthentication());
}

    SecurityContextHolder.setContext(token.getSecurityContext());
}

}


然而,无论正常调用,亦或是请求异常等,都会触发 finallyInvocation()。



public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)

}
else {

    try {
        fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
    }
    finally {
        // 无论是否成功、抛异常与否,均会执行
        super.finallyInvocation(token);
    }

    // 正常请求结束,最后也会执行(afterInvocation 内部会调用finallyInvocation )
    super.afterInvocation(token, null);
}

}


即便是正常执行结束,依然会执行 finallyInvocation()(afterInvocation 内部会调用finallyInvocation )。




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)**
![img](https://img-blog.csdnimg.cn/img_convert/6392d26f4440f9a50cbb67bc38c0314e.png)

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

     }

        // 正常请求结束,最后也会执行(afterInvocation 内部会调用finallyInvocation )
        super.afterInvocation(token, null);
    }
}

即便是正常执行结束,依然会执行 finallyInvocation()(afterInvocation 内部会调用finallyInvocation )。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
[外链图片转存中…(img-M4CiNj1x-1713325234317)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 7
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Security是一个用于保护Java应用程序的框架,它提供了一系列过滤器来处理安全相关的任务。在Spring Security中,过滤器被组织成一个过滤器链(Filter Chain),它根据配置的顺序依次执行。以下是一些常用的Spring Security过滤器及其功能: 1. SecurityContextPersistenceFilter:用于在请求处理期间存储和检索SecurityContext,即当前用户的安全上下文。 2. LogoutFilter:处理用户注销请求,并清除当前用户的认证信息。 3. UsernamePasswordAuthenticationFilter:用于处理基于用户名和密码的认证请求。 4. ConcurrentSessionFilter:用于处理并发会话控制,限制同一用户的同时登录数。 5. BasicAuthenticationFilter:用于处理基于HTTP基本身份验证的认证请求。 6. RequestCacheAwareFilter:用于缓存请求,以便在重定向后重新发送请求。 7. SecurityContextHolderAwareRequestFilter:用于包装请求,使其能够识别特定的安全上下文。 8. AnonymousAuthenticationFilter:用于在请求上下文中添加匿名用户的认证信息。 9. SessionManagementFilter:用于处理会话管理,例如过期检查、并发控制等。 10. ExceptionTranslationFilter:用于捕获并处理异常,将其转换为合适的响应。 11. FilterSecurityInterceptor:用于实施访问控制,检查用户是否具有访问资源的权限。 这些过滤器可以根据具体的安全需求进行配置和组合,以提供所需的安全功能。通过指定不同的过滤器顺序、添加自定义过滤器或替换默认过滤器,您可以灵活地定制Spring Security过滤器链。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值