13种核心过滤器
spring security的13个核心过滤器(按执行顺序陈列):
- WebAsyncManagerIntegrationFilter
- SecurityContextPersistenceFilter
- HeaderWriterFilter
- LogoutFilter
- UsernamePasswordAuthenticationFilter
- DefaultLoginPageGeneratingFilter
- DefaultLogoutPageGeneratingFilter
- RequestCacheAwareFilter
- SecurityContextHolderAwareRequestFilter
- AnonymousAuthenticationFilter
- SessionManagementFilter
- ExceptionTranslationFilter
- FilterSecurityInterceptor
security过滤器创建流程
感兴趣了解详细源码servlet过滤器执行security过滤器详细流程
security过滤器执行流程
过滤器介绍
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);
}
}
主要功能:
- 创建WebAsyncManager
- 注册SecurityContextCallableProcessingInterceptor,具体使用位置org.springframework.web.servlet.mvc.method.annotation.AsyncTaskMethodReturnValueHandler#handleReturnValue
属于mvc源码部分了,想进一步了解可以参考mvc源码。
SecurityContextPersistenceFilter
public class SecurityContextPersistenceFilter extends GenericFilterBean {
static final String FILTER_APPLIED = "__spring_security_scpf_applied";
private SecurityContextRepository repo;
private boolean forceEagerSessionCreation = false;
public SecurityContextPersistenceFilter() {
this(new HttpSessionSecurityContextRepository());
}
public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
this.repo = repo;
}
@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 {
// ensure that filter is only applied once per request
if (request.getAttribute(FILTER_APPLIED) != null) {
chain.doFilter(request, response);
return;
}
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
if (this.forceEagerSessionCreation) {
HttpSession session = request.getSession();
if (this.logger.isDebugEnabled() && session.isNew()) {
this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));
}
}
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
try {
SecurityContextHolder.setContext(contextBeforeChainExecution);
if (contextBeforeChainExecution.getAuthentication() == null) {
logger.debug("Set SecurityContextHolder to empty SecurityContext");
}
else {
if (this.logger.isDebugEnabled()) {
this.logger
.debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
}
}
chain.doFilter(holder.getRequest(), holder.getResponse());
}
finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
// Crucial removal of SecurityContextHolder contents before anything else.
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
this.logger.debug("Cleared SecurityContextHolder to complete request");
}
}
public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
this.forceEagerSessionCreation = forceEagerSessionCreation;
}
}
主要功能:
-
通过SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);加载保存在session中的SecurityContext数据。
我们知道客户端会将会话ID保存在cookie当中用于后端识别是否是同一个会话,所以当我们通过浏览器清除cookie中sessionID数据的时候,会导致后台找不到对应的session进而加载不到具体的SecurityContext数据,被security其他过滤器视为没有登录,跳转到登录页面。
-
每次请求结束都会清除SecurityContext数据并将该数据保存到session,方便SecurityContext数据要么来自登录或者来自下次加载session数据,key为“SPRING_SECURITY_CONTEXT”,因此SecurityContext的生命周期是整个请求的生命周期。
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
// Crucial removal of SecurityContextHolder contents before anything else.
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
HeaderWriterFilter
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HeaderWriterResponse headerWriterResponse = new HeaderWriterResponse(request,
response, this.headerWriters);
HeaderWriterRequest headerWriterRequest = new HeaderWriterRequest(request,
headerWriterResponse);
try {
filterChain.doFilter(headerWriterRequest, headerWriterResponse);
}
finally {
headerWriterResponse.writeHeaders();
}
}
主要功能:
- response对象重新封装,封装header配置集合
① headerWriters包含:
② contentTypeOptions
③ xssProtection
④ cacheControl
⑤ hsts
⑥ frameOptions
⑦ hpkp
⑧ contentSecurityPolicy
⑨ referrerPolicy
⑩ featurePolicy
具体代码位置org.springframework.security.config.annotation.web.configurers.HeadersConfigurer#getHeaderWriters
private List<HeaderWriter> getHeaderWriters() {
List<HeaderWriter> writers = new ArrayList<>();
addIfNotNull(writers, contentTypeOptions.writer);
addIfNotNull(writers, xssProtection.writer);
addIfNotNull(writers, cacheControl.writer);
addIfNotNull(writers, hsts.writer);
addIfNotNull(writers, frameOptions.writer);
addIfNotNull(writers, hpkp.writer);
addIfNotNull(writers, contentSecurityPolicy.writer);
addIfNotNull(writers, referrerPolicy.writer);
addIfNotNull(writers, featurePolicy.writer);
writers.addAll(headerWriters);
return writers;
}
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");
}
this.handler.logout(request, response, auth);
logoutSuccessHandler.onLogoutSuccess(request, response, auth);
return;
}
chain.doFilter(request, response);
}
主要功能:
- 拦截登出页请求/logout
- 清空SecurityContext ,handler的实例类是CompositeLogoutHandler
this.handler.logout(request, response, auth);
代码位置:org.springframework.security.web.authentication.logout.CompositeLogoutHandler#logout
代码位置:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler#logout
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();
}
3.跳转到登出页
logoutSuccessHandler.onLogoutSuccess(request, response, auth);
logoutSuccessHandler的实力类SimpleUrlLogoutSuccessHandler
代码位置:org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler#onLogoutSuccess
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
super.handle(request, response, authentication);
}
代码位置:org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler#determineTargetUrl
重定向到登录页
如果你想自定义logoutSuccessHandler.onLogoutSuccess(request, response, auth),可以通过修改LogoutConfigurer的logoutSuccessHandler,方式:
http
.logout()
.logoutSuccessHandler((httpServletRequest, httpServletResponse, authentication) -> {
// 成功退出登录后返回200状态码
// httpServletResponse.setStatus(HttpServletResponse.SC_OK);
// 成功退出登录后的需要执行的代码写在这
System.out.println("123");
});
原因:
org.springframework.security.config.annotation.web.configurers.LogoutConfigurer#createLogoutFilter
private LogoutFilter createLogoutFilter(H http) throws Exception {
logoutHandlers.add(contextLogoutHandler);
LogoutHandler[] handlers = logoutHandlers
.toArray(new LogoutHandler[logoutHandlers.size()]);
LogoutFilter result = new LogoutFilter(getLogoutSuccessHandler(), handlers);
result.setLogoutRequestMatcher(getLogoutRequestMatcher(http));
result = postProcess(result);
return result;
}
getLogoutSuccessHandler方法()
private LogoutSuccessHandler getLogoutSuccessHandler() {
LogoutSuccessHandler handler = this.logoutSuccessHandler;
if (handler == null) {
handler = createDefaultSuccessHandler();
}
return handler;
}
具体为什么修改LogoutConfigurer,请参考文章spring Security源码讲解-WebSecurityConfigurerAdapter
UsernamePasswordAuthenticationFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
successfulAuthentication(request, response, chain, authResult);
}
主要功能:
- 校验是否是登录路径并且是post请求,如果不是就执行下一个过滤器
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
- 进行登录校验(是否有效、登录次数、账号密码)等,因此我们可以在这个过滤器逻辑里面做一些定制,比如根据ip登录限制次数什么的。
authResult = attemptAuthentication(request, response);
- 登录
①如果登录失败,重定向到/login?error,
unsuccessfulAuthentication(request, response, failed);
failureHandler的对象类SimpleUrlAuthenticationFailureHandler
代码位置:org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler#onAuthenticationFailure
②如果登录成功则改变登录前的sessionID,防止session会话固定攻攻击,默认是有状态session,不改变session对象,只改变sessionID,防止登录前和登陆后sessionID一致,导致被攻击。
sessionStrategy.onAuthentication(authResult, request, response);
- 保存登录authentication到SecurityContext
successfulAuthentication(request, response, chain, authResult);
DefaultLoginPageGeneratingFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
boolean loginError = isErrorPage(request);
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);
}
主要功能:
- 处理登录失败或者登出成功,将登录页的html写回客户端
boolean loginError = isErrorPage(request);
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;
}
DefaultLogoutPageGeneratingFilter
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (this.matcher.matches(request)) {
renderLogout(request, response);
} else {
filterChain.doFilter(request, response);
}
}
这个过滤器优点特殊,默认不会执行 renderLogout(request, response);
因为登出会被LogoutFilter过滤器拦截并且重定向登录页/login?logout,因此this.matcher.matches(request)永远是false
如果不想要LogoutFilter登出逻辑,解决办法将LogoutConfigurer从HttpSecurity注销,方式:
http
// 关闭默认注销接口
.logout().disable();
主要功能:
1.跳转登出页,通过页面进行注销
renderLogout(request, response);
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);
}
主要功能:
1.获取上次请求失败的目标路径,继续访问。
比如:匿名直接请求需要权限资源失败,ExceptionTranslationFilter过滤器会记录本次的目标请求,登录验证通过后会获取上次的目标请求继续请求。
HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
(HttpServletRequest) request, (HttpServletResponse) response);
代码位置: org.springframework.security.web.savedrequest.RequestCacheAwareFilter#doFilter
SecurityContextHolderAwareRequestFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(this.requestFactory.create((HttpServletRequest) req,
(HttpServletResponse) res), res);
}
this.requestFactory.create((HttpServletRequest),create方法属于HttpServlet3RequestFactory类.
代码位置:org.springframework.security.web.servletapi.HttpServlet3RequestFactory#create
public HttpServletRequest create(HttpServletRequest request,
HttpServletResponse response) {
return new Servlet3SecurityContextHolderAwareRequestWrapper(request,
this.rolePrefix, response);
}
主要功能:
1.这个过滤器看起来很简单。目的仅仅是实现java ee中servlet api一些接口方法。
一些应用中直接使用getRemoteUser方法、isUserInRole方法,在使用spring security时其实就是通过这个过滤器来实现的。
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);
}
主要功能:
1.当用户没有登录的时候创建一个用户名为anonymousUser,角色为ROLE_ANONYMOUS的匿名用户
SecurityContextHolder.getContext().setAuthentication(
createAuthentication((HttpServletRequest) req));
createAuthentication方法
protected Authentication createAuthentication(HttpServletRequest request) {
AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,
principal, authorities);
auth.setDetails(authenticationDetailsSource.buildDetails(request));
return auth;
}
SessionManagementFilter
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);
}
主要功能:
1.校验用户是否没有被持久化过(保存到session),如果没有就持久化一次
securityContextRepository.saveContext(SecurityContextHolder.getContext(),
request, response);
2.校验session是否过期
if (request.getRequestedSessionId() != null
&& !request.isRequestedSessionIdValid()) {
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) {
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
}
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);
}
}
}
主要功能:
1.处理FilterSecurityInterceptor过滤器权限校验抛出的异常,如果权限不通过则接受FilterSecurityInterceptor抛出的异常并处理
2.记录访问异常时本次请求
handleSpringSecurityException
代码位置:org.springframework.security.web.access.ExceptionTranslationFilter#handleSpringSecurityException
private void handleSpringSecurityException(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, RuntimeException exception)
throws IOException, ServletException {
if (exception instanceof AuthenticationException) {
logger.debug(
"Authentication exception occurred; redirecting to authentication entry point",
exception);
sendStartAuthentication(request, response, chain,
(AuthenticationException) exception);
}
else if (exception instanceof AccessDeniedException) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
logger.debug(
"Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",
exception);
sendStartAuthentication(
request,
response,
chain,
new InsufficientAuthenticationException(
messages.getMessage(
"ExceptionTranslationFilter.insufficientAuthentication",
"Full authentication is required to access this resource")));
}
else {
logger.debug(
"Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
exception);
accessDeniedHandler.handle(request, response,
(AccessDeniedException) exception);
}
}
}
sendStartAuthentication方法
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
SecurityContextHolder.getContext().setAuthentication(null);
requestCache.saveRequest(request, response);
logger.debug("Calling Authentication entry point.");
authenticationEntryPoint.commence(request, response, reason);
}
requestCache.saveRequest(request, response);记录本次请求
FilterSecurityInterceptor
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
核心代码
InterceptorStatusToken token = super.beforeInvocation(fi);
代码位置:org.springframework.security.access.intercept.AbstractSecurityInterceptor#beforeInvocation
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
final boolean debug = logger.isDebugEnabled();
if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
throw new IllegalArgumentException(
"Security invocation attempted for object "
+ object.getClass().getName()
+ " but AbstractSecurityInterceptor only configured to support secure objects of type: "
+ getSecureObjectClass());
}
Collection<ConfigAttribute> 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'");
}
if (debug) {
logger.debug("Public object - authentication not attempted");
}
publishEvent(new PublicInvocationEvent(object));
return null; // no further work post-invocation
}
if (debug) {
logger.debug("Secure object: " + object + "; Attributes: " + attributes);
}
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(messages.getMessage(
"AbstractSecurityInterceptor.authenticationNotFound",
"An Authentication object was not found in the SecurityContext"),
object, attributes);
}
Authentication authenticated = authenticateIfRequired();
// 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));
}
// 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);
}
}
主要功能:
1.获取路径和角色的映射关系
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
如我们在这里配置的映射数据
2.是否每次都要重新登录一次用户(开启的话,可以防止用户此时过期,但是性能降低,默认不开启,只在UserNamepasswordFilter过滤器中进行登录)
Authentication authenticated = authenticateIfRequired();
3.校验当前用户访问的路径是否满足该路径配置权限
this.accessDecisionManager.decide(authenticated, object, attributes);
过程:
1.获取当前登录用户的角色
2.根据当前登录object,从attributes中拿到需要当前用户拥有的角色集合
3.判断当前登录用户信息authenticated中的authorities属性是否存在访问路径必要必要集合元素,如果存在就通过,否则无权限抛AccessDeniedException异常被FilterSecurityInterceptor过滤器捕获。
因此我们可以在FilterSecurityInterceptor过程中实现自定义权限校验逻辑
需要两不操作:
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
需要改变FilterSecurityInterceptor过滤器的俩个实例对象securityMetadataSource和accessDecisionManager
实现代码:
@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
@Configuration
public class MyConfig{
@Autowired
private FilterChainProxy filterChainProxy;
@PostConstruct
public void postConstruct() {
List<SecurityFilterChain> securityFilterChainList = filterChainProxy.getFilterChains();
List<Filter> filters = securityFilterChainList.get(0).getFilters();
for(Filter filter : filters){
if(filter instanceof FilterSecurityInterceptor){
FilterSecurityInterceptor filterSecurityInterceptor = (FilterSecurityInterceptor) filter;
filterSecurityInterceptor.setSecurityMetadataSource(new MyFilterInvocationSecurityMetadataSource());
filterSecurityInterceptor.setAccessDecisionManager(new MyAccessDecisionManager());
}
}
}
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource{
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
System.out.println("加载路径所需角色");
return null;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return false;
}
}
public class MyAccessDecisionManager implements AccessDecisionManager{
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
System.out.println("进行权限校验");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return false;
}
@Override
public boolean supports(Class<?> clazz) {
return false;
}
}
}
这样我们就可以动态加载和校验权限,不用在WebSecurityConfigurerAdapter里配置了