- 浏览: 87821 次
- 性别:
- 来自: 杭州
文章分类
最新评论
spring-security-oauth2客户端@EnableOauth2Sso注解过滤器整理
目前使用 @EnableOauth2Sso注解以及 securityConfiguration 配置 security 拦截器有13道
分别是
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CsrfFilter
CSRFHeaderFilter(自定义的过滤器,添加在CsrfFilter后)
LogoutFilter
OAuth2ClientAuthenticationProcessingFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
WebAsyncManagerIntegrationFilter
public final class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter { private static final Object CALLABLE_INTERCEPTOR_KEY = new Object(); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager .getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY); if (securityProcessingInterceptor == null) { asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY, new SecurityContextCallableProcessingInterceptor()); } filterChain.doFilter(request, response); } }
提供了对securityContext和WebAsyncManager的集成。方式是通过SecurityContextCallableProcessingInterceptor的beforeConcurrentHandling(NativeWebRequest, Callable)方法来讲SecurityContext设置到Callable上。
SecurityContextCallableProcessingInterceptor:支持spring mvc Callable集成。当SecurityContextCallableProcessingInterceptor执行preProcess(NativeWebRequest, Callable)方法时将注入的SecurityContext传递给SecurityContextHolder。
SecurityContextPersistenceFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (request.getAttribute(FILTER_APPLIED) != null) { // ensure that filter is only applied once per request chain.doFilter(request, response); return; } final boolean debug = logger.isDebugEnabled(); request.setAttribute(FILTER_APPLIED, Boolean.TRUE); if (forceEagerSessionCreation) { HttpSession session = request.getSession(); if (debug && session.isNew()) { logger.debug("Eagerly created session: " + session.getId()); } } HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response); SecurityContext contextBeforeChainExecution = repo.loadContext(holder); try { SecurityContextHolder.setContext(contextBeforeChainExecution); chain.doFilter(holder.getRequest(), holder.getResponse()); } finally { SecurityContext contextAfterChainExecution = SecurityContextHolder .getContext(); // Crucial removal of SecurityContextHolder contents - do this before anything // else. SecurityContextHolder.clearContext(); repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse()); request.removeAttribute(FILTER_APPLIED); if (debug) { logger.debug("SecurityContextHolder now cleared, as request processing completed"); } } }
SecurityContextPersistenceFilter主要是在SecurityContextRepository中保存更新一个securityContext,并将securityContext给以后的过滤器使用
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
是通过httpSessionSecurityContextRepository.loadContext()方法 从session中获取securityContext,如果获取不到,则创建一个空的,存到SecurityContextHolder中,提供给后面的过滤器使用。
Finally 方法 将securityContext储存到httpSessionSecurityContextRepository中,此时securityContext是包含后面过滤器产生的authentication数据
HeaderWriterFilter
securityConfiguration配置一些header信息,在这里进行追加到request的header中
public class HeaderWriterFilter extends OncePerRequestFilter { /** * Collection of {@link HeaderWriter} instances to write out the headers to the * response . */ private final List<HeaderWriter> headerWriters; /** @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { for (HeaderWriter factory : headerWriters) { factory.writeHeaders(request, response); } filterChain.doFilter(request, response); } }
例如 实现writeHeaders接口
CsrfFilter
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { CsrfToken csrfToken = tokenRepository.loadToken(request); final boolean missingToken = csrfToken == null; if (missingToken) { CsrfToken generatedToken = tokenRepository.generateToken(request); csrfToken = new SaveOnAccessCsrfToken(tokenRepository, request, response, generatedToken); } request.setAttribute(CsrfToken.class.getName(), csrfToken); request.setAttribute(csrfToken.getParameterName(), csrfToken); if (!requireCsrfProtectionMatcher.matches(request)) { filterChain.doFilter(request, response); return; } String actualToken = request.getHeader(csrfToken.getHeaderName()); if (actualToken == null) { actualToken = request.getParameter(csrfToken.getParameterName()); } if (!csrfToken.getToken().equals(actualToken)) { if (logger.isDebugEnabled()) { logger.debug("Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request)); } if (missingToken) { accessDeniedHandler.handle(request, response, new MissingCsrfTokenException(actualToken)); } else { accessDeniedHandler.handle(request, response, new InvalidCsrfTokenException(csrfToken, actualToken)); } return; } filterChain.doFilter(request, response); }
在跨域的场景下,客户端访问服务端会首先发起get请求,这个get请求在到达服务端的时候,服务端的Spring security会有一个过滤 器 CsrfFilter去检查这个请求,如果这个request请求的http header里面的X-CSRF-COOKIE的token值为空的时候,服务端就好自动生成一个 token值放进这个X-CSRF-COOKIE值里面,客户端在get请求的header里面获取到这个值,如果客户端有表单提交的post请求,则要求客户端要 携带这个token值给服务端,在post请求的header里面设置_csrf属性的token值,提交的方式可以是ajax也可以是放在form里面设置hidden 属性的标签里面提交给服务端,服务端就会根据post请求里面携带的token值进行校验,如果跟服务端发送给合法客户端的token值是一样的,那么 这个post请求就可以受理和处理,如果不一样或者为空,就会被拦截。由于恶意第三方可以劫持session id,而很难获取token值,所以起到了 安全的防护作用。
CSRFHeaderFilter
自定义的定义,如果cookie中没有XSRF-TOKEN,则追加
LogoutFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (requiresLogout(request, response)) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (logger.isDebugEnabled()) { logger.debug("Logging out user '" + auth + "' and transferring to logout destination"); } for (LogoutHandler handler : handlers) { handler.logout(request, response, auth); } logoutSuccessHandler.onLogoutSuccess(request, response, auth); return; } chain.doFilter(request, response); }
handler.logout(request, response, auth);
调用securityContextLogoutHandler.logout,清除session,清除SecurityContext
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { Assert.notNull(request, "HttpServletRequest required"); if (invalidateHttpSession) { HttpSession session = request.getSession(false); if (session != null) { logger.debug("Invalidating session: " + session.getId()); session.invalidate(); } } if (clearAuthentication) { SecurityContext context = SecurityContextHolder.getContext(); context.setAuthentication(null); } SecurityContextHolder.clearContext(); }
OAuth2ClientAuthenticationProcessingFilter
@Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { OAuth2AccessToken accessToken; try { accessToken = restTemplate.getAccessToken(); } catch (OAuth2Exception e) { BadCredentialsException bad = new BadCredentialsException("Could not obtain access token", e); publish(new OAuth2AuthenticationFailureEvent(bad)); throw bad; } try { OAuth2Authentication result = tokenServices.loadAuthentication(accessToken.getValue()); if (authenticationDetailsSource!=null) { request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue()); request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType()); result.setDetails(authenticationDetailsSource.buildDetails(request)); } publish(new AuthenticationSuccessEvent(result)); return result; } catch (InvalidTokenException e) { BadCredentialsException bad = new BadCredentialsException("Could not obtain user details from token", e); publish(new OAuth2AuthenticationFailureEvent(bad)); throw bad; } } private void publish(ApplicationEvent event) { if (eventPublisher!=null) { eventPublisher.publishEvent(event); } }
父类AbstractAuthenticationProcessingFilter 中doFilter方法调用attemptAuthentication()
代码
accessToken = restTemplate.getAccessToken(); 中,如果用户没有通过认证的code,或者使用code去oauth服务换取token时,code已经失效,会在
AuthorizationCodeAccessTokenProvider的getRedirectForAuthorization中抛出UserRedirectRequiredException e
public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request) throws UserRedirectRequiredException, UserApprovalRequiredException, AccessDeniedException, OAuth2AccessDeniedException { AuthorizationCodeResourceDetails resource = (AuthorizationCodeResourceDetails) details; if (request.getAuthorizationCode() == null) { if (request.getStateKey() == null) { throw getRedirectForAuthorization(resource, request); } obtainAuthorizationCode(resource, request); } return retrieveToken(request, resource, getParametersForTokenRequest(resource, request), getHeadersForTokenRequest(request)); }
RequestCacheAwareFilter
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest( (HttpServletRequest) request, (HttpServletResponse) response); chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest, response); }
优先使用缓存的请求
SecurityContextHolderAwareRequestFilter
SecurityContextHolderAwareRequestWrapper类对request包装的目的主要是实现servlet api的一些接口方法isUserInRole、getRemoteUser
AnonymousAuthenticationFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
SecurityContextHolder.getContext().setAuthentication(
createAuthentication((HttpServletRequest) req));
if (logger.isDebugEnabled()) {
logger.debug("Populated SecurityContextHolder with anonymous token: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
}
chain.doFilter(req, res);
}
如果SecurityContextHolder中securityContext没有认证,则创建一个anonymousAuthenticationToken
SessionManagementFilter
当请求所在的session里没有设置"SPRING_SECURITY_CONTEXT"属性的时候, 才会被这个过滤器拦截. 否则跳到下一个过滤器. 但这个"SPRING_SECURITY_CONTEXT"属性是在用户登录验证完成后就放进session里的(具体参考AbstractAuthenticationProcessingFilter.successfulAuthentication()方法), 所以登录成功后的每一个请求, 它的session里都会有这个属性, 所以就不会被这个过滤器拦截.
有两种情况, 用户发出的请求会被这个过滤器拦截:
1. 当用户重启浏览器, 然后使用remember-me授权成功后, 再直接访问授权后的url. 这时, session里没有了"SPRING_SECURITY_CONTEXT"属性. 那么这个请求会被此过滤器拦截. 这时得到的authentication是RemembermeAuthentication的实例.)
2. 当session因过期而被容器自动销毁时, 若用户继续发出请求, 这个请求会放在一个新的session里, 新session里没有设置"SPRING_SECURITY_CONTEXT"属性, 那么这个请求也会被这个过滤器拦截. 这时得到的authentication是AnonymousAuthentication的实例.
securityContextRepository 是HttpSessionSecurityContextRepository 的实例,第一种的情况的时候,将securityContext固化到redis(目前项目统一使用spring-session整合spring-data-redis)
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (request.getAttribute(FILTER_APPLIED) != null) { chain.doFilter(request, response); return; } request.setAttribute(FILTER_APPLIED, Boolean.TRUE); if (!securityContextRepository.containsContext(request)) { Authentication authentication = SecurityContextHolder.getContext() .getAuthentication(); if (authentication != null && !trustResolver.isAnonymous(authentication)) { // The user has been authenticated during the current request, so call the // session strategy try { sessionAuthenticationStrategy.onAuthentication(authentication, request, response); } catch (SessionAuthenticationException e) { // The session strategy can reject the authentication logger.debug( "SessionAuthenticationStrategy rejected the authentication object", e); SecurityContextHolder.clearContext(); failureHandler.onAuthenticationFailure(request, response, e); return; } // Eagerly save the security context to make it available for any possible // re-entrant // requests which may occur before the current request completes. // SEC-1396. securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response); } else { // No security context or authentication present. Check for a session // timeout if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) { if (logger.isDebugEnabled()) { logger.debug("Requested session ID " + request.getRequestedSessionId() + " is invalid."); } if (invalidSessionStrategy != null) { invalidSessionStrategy .onInvalidSessionDetected(request, response); return; } } } } chain.doFilter(request, response); }
ExceptionTranslationFilter
统一处理之后的过滤抛出异常的过滤器
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { chain.doFilter(request, response); logger.debug("Chain processed normally"); } catch (IOException ex) { throw ex; } catch (Exception ex) { // Try to extract a SpringSecurityException from the stacktrace Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex); RuntimeException ase = (AuthenticationException) throwableAnalyzer .getFirstThrowableOfType(AuthenticationException.class, causeChain); if (ase == null) { ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType( AccessDeniedException.class, causeChain); } if (ase != null) { handleSpringSecurityException(request, response, chain, ase); } else { // Rethrow ServletExceptions and RuntimeExceptions as-is if (ex instanceof ServletException) { throw (ServletException) ex; } else if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } // Wrap other Exceptions. This shouldn't actually happen // as we've already covered all the possibilities for doFilter throw new RuntimeException(ex); } }
在这个过滤器的 try 部分中会继续执行过滤器链中剩余的过滤器,后面会执行的一个过滤器是 FilterSecurityInterceptor,这个过滤器是用来处理授权的,就是验证用户是否有权访问某个资源,如果没有权限,就会抛出 AccessDeniedException,此时就会进入 handleSpringSecurityException 方法执行。
此时会访问 /login ,在OAuth2ClientAuthenticationProcessingFilter过滤器中obtainAccessToken
取不到code 会抛出 UserRedirectRequiredException e,再由OAuth2ClientContextFilter
捕获 UserRedirectRequiredException重定向到oauth服务的
https://oauth.rubikstack.com/oauth/authorize?client_id=xxx&redirect_uri=https://......
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; request.setAttribute(CURRENT_URI, calculateCurrentUri(request)); try { chain.doFilter(servletRequest, servletResponse); } catch (IOException ex) { throw ex; } catch (Exception ex) { // Try to extract a SpringSecurityException from the stacktrace Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex); UserRedirectRequiredException redirect = (UserRedirectRequiredException) throwableAnalyzer .getFirstThrowableOfType( UserRedirectRequiredException.class, causeChain); if (redirect != null) { redirectUser(redirect, request, response); } else { if (ex instanceof ServletException) { throw (ServletException) ex; } if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } throw new NestedServletException("Unhandled exception", ex); } } }
FilterSecurityInterceptor
简化授权和访问控制决定,委托一个AccessDecisionManager完成授权的判断
这个filter是filterchain中比较复杂,也是比较核心的过滤器,主要负责授权的工作
public void doFilter(ServletRequest request, ServletResponse response, FilterChin chain) throws IOException, ServletException { //封装request, response, chain,方便参数传递、增加代码阅读性 FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } 主要逻辑 // Attempt authorization try { this.accessDecisionManager.decide(authenticated, object, attributes); } catch (AccessDeniedException accessDeniedException) { publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException)); throw accessDeniedException; } [color=red]AffirmativeBased:只要有一个voter投同意票,就授权成功 ConsensusBased:只要投同意票的大于投反对票的,就授权成功 UnanimousBased:需要一致通过才授权成功[/color] 这里用 [b]AffirmativeBased决策管理器[/b] public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException { int deny = 0; for (AccessDecisionVoter voter : getDecisionVoters()) { int result = voter.vote(authentication, object, configAttributes); if (logger.isDebugEnabled()) { logger.debug("Voter: " + voter + ", returned: " + result); } switch (result) { case AccessDecisionVoter.ACCESS_GRANTED: return; case AccessDecisionVoter.ACCESS_DENIED: deny++; break; default: break; } } if (deny > 0) { throw new AccessDeniedException(messages.getMessage( "AbstractAccessDecisionManager.accessDenied", "Access is denied")); } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions(); }
相关推荐
Spring Cloud Security提供了OAuth2客户端和服务端的支持,可以方便地实现OAuth2资源服务器和授权服务器的配置。 四、安全配置 在spring-boot-security-master项目中,安全配置通常在Spring Boot的配置类中完成,...
3. **过滤器链**:Spring Security的核心在于其过滤器链,它拦截HTTP请求并应用安全策略。`FilterSecurityInterceptor`是其中的关键组件,负责根据配置的访问规则对请求进行处理。 4. **会话管理**:Spring ...
每个过滤器都有特定的职责,如`DelegatingFilterProxy`用于委托给Spring Security的其他过滤器,`HttpServletRequestWrapperFilter`用于包装请求以增加安全性。 2. **身份验证(Authentication)**:当用户尝试访问...
在Spring Boot中,可以通过`@EnableOAuth2Sso`注解开启OAuth2单点登录支持,或者使用`spring-security-oauth2-client`库进行自定义配置,以获取和刷新令牌,并在请求受保护资源时附带令牌。 3. 实现步骤: - **...
7. **OAuth2与OpenID Connect集成**:Spring Security 支持OAuth2和OpenID Connect,这两种标准用于第三方应用的授权。这使得用户可以通过单一登录(SSO)在多个应用间无缝切换。 8. **Web安全配置**:Spring ...
9. **OAuth2整合**:Spring Security也支持OAuth2,可以与第三方服务进行单点登录(SSO)或其他授权交互。 在“spring-security2”这个压缩包中,我们可能找到一个包含上述概念的实现示例,比如配置类、安全相关...
- **OAuth2支持**:Spring Security可以与其他OAuth2服务器集成,实现单点登录(Single Sign-On, SSO)。 6. **异常处理** 当用户尝试访问受保护的资源但未通过认证或授权时,Spring Security会抛出相应的异常。...
在Spring Boot项目中,这通常通过添加`@EnableOAuth2Sso`注解到配置类来完成,这个注解会自动配置OAuth2客户端和服务端的安全过滤器。我们还需要配置GitHub的客户端ID和秘密,以及重定向URL。 ```java @...
2. 使用`@EnableOAuth2Sso`:在主配置类上添加此注解,开启OAuth2单点登录支持。这将自动配置Spring Security的OAuth2过滤器链。 3. 创建Controller:创建一个处理OAuth2授权回调的Controller,使用`@...
本篇文章将深入探讨如何使用Spring Security OAuth来实现一个SSO系统,基于Spring Boot 2.1.3、Spring Security 5.1.4以及Spring Security OAuth 2。 首先,我们需要理解Spring Security的核心概念。Spring ...
无论是简单的认证和授权,还是复杂的OAuth2、SAML集成,Spring Security都能提供相应的支持。在实际开发中,根据项目需求选择合适的模块和配置,可以确保应用在安全性和易用性之间取得良好的平衡。
- Spring Security可以与OAuth2提供商集成,实现第三方登录功能,如Google、Facebook等。 8. **自定义安全逻辑** - 开发者可以自定义身份验证提供者、权限决策管理器、访问决策投票器等,以适应特定的业务需求。 ...
- 使用 Spring Security 的 `@EnableOAuth2Sso` 或自定义过滤器处理令牌,以实现用户登录和资源访问。 - 处理授权码流程,当用户同意授权后,获取访问令牌和刷新令牌,并存储在客户端。 7. **测试与部署**: - ...
1. **OAuth2整合**:Spring Security支持OAuth2协议,可实现第三方应用的授权接入。 2. **SSO单点登录**:通过CAS、SAML等方式,实现在多个应用间的一次登录。 3. **自定义认证与授权**:开发人员可以根据需求扩展...
7. **OAuth2支持**:Spring Security可以与其他OAuth2服务器集成,实现单点登录(SSO)和其他授权功能。 在"spring-security-playground-master"这个压缩包中,可能包含以下内容: - `src/main/java`:项目的Java...
- **过滤器(Filters)**:Spring Security使用一系列过滤器来处理安全性相关的请求; - **认证管理器(Authentication Manager)**:负责验证用户的凭证; - **授权(Authorization)**:根据用户的角色或权限控制...
SpringSecurity支持多种SSO协议,如CAS(Central Authentication Service)和OAuth2。 ### 三、SSO实现原理 1. **票据(Ticket)机制**:在SSO中,票据是用户身份的临时凭证。当用户成功登录后,服务器会生成一个...
Spring Security还支持OAuth2协议,允许第三方应用通过获取授权码来访问受保护的资源。这对于构建API或者实现单点登录(Single Sign-On, SSO)的场景非常有用。 **6. CSRF防护** Spring Security内置了跨站请求...
总的来说,"狂神的SpringSecurity素材.rar"提供的资源涵盖了SpringSecurity的基本概念、配置、权限管理、OAuth2整合以及与Elasticsearch的配合等方面,是深入学习和掌握SpringSecurity的理想资料。无论你是想提升你...
`springsecurity-sample`可能是一个示例项目,其中包含了如何配置这些过滤器的实例。 2. **访问控制**:Spring Security提供了基于角色的访问控制(RBAC),允许开发者定义不同角色对资源的访问权限。例如,管理员...