1、默认存在哪些filter,具体执行顺序
-
ChannelProcessingFilter,如果你访问的 channel 错了,那首先就会在channel 之间进行跳转,如 http 变为 https。
-
SecurityContextPersistenceFilter,这样的话在一开始进行 request 的时候就可以在 SecurityContextHolder 中建立一个SecurityContext,然后在请求结束的时候,任何对 SecurityContext 的改变都可以被 copy 到 HttpSession。
-
ConcurrentSessionFilter,因为它需要使用 SecurityContextHolder 的功能,而且更新对应 session 的最后更
-
新时间,以及通过 SessionRegistry 获取当前的 SessionInformation 以检查当前的 session 是否已经过期,过期则会调用 LogoutHandler。
-
HeaderWriterFilter
-
CsrfFilter
-
LogoutFilter
-
X509AuthenticationFilter
-
AbstractPreAuthenticatedProcessingFilter
-
认证处理机制,如 UsernamePasswordAuthenticationFilter,CasAuthenticationFilter,BasicAuthenticationFilter 等,以至于 SecurityContextHolder 可以被更新为包含一个有效的 Authentication 请求。
-
SecurityContextHolderAwareRequestFilter,它将会把 HttpServletRequest 封装成一个继承自 HttpServletRequestWrapper 的 SecurityContextHolderAwareRequestWrapper,同时使用 SecurityContext 实现了 HttpServletRequest 中与安全相关的方法。
扫描二维码关注公众号,回复: 4348837 查看本文章 -
JaasApiIntegrationFilter,如果 SecurityContextHolder 中拥有的 Authentication 是一个 JaasAuthenticationToken,那么该 Filter 将使用包含在 JaasAuthenticationToken 中的 Subject 继续执行 FilterChain。
-
RememberMeAuthenticationFilter,如果之前的认证处理机制没有更新 SecurityContextHolder,并且用户请求包含了一个 Remember-Me 对应的 cookie,那么一个对应的 Authentication 将会设给 SecurityContextHolder。
-
AnonymousAuthenticationFilter,如果之前的认证机制都没有更新 SecurityContextHolder 拥有的 Authentication,那么一个 AnonymousAuthenticationToken 将会设给 SecurityContextHolder。
-
SessionManagementFilter
-
ExceptionTransactionFilter,用于处理在 FilterChain 范围内抛出的 AccessDeniedException 和 AuthenticationException,并把它们转换为对应的 Http 错误码返回或者对应的页面。
-
FilterSecurityInterceptor,保护 Web URI,并且在访问被拒绝时抛出异常。
2、重点了解SecurityContextPersistenceFilter,session是如何进行操作,做到任何对SecurityContext的改变都可以被copy到HttpSession中。
先查看其中doFilter方法的代码
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
.......
//省略部分代码
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
//获取SecurityContext,其中是从httpsession中获取,如果httpsession中没有,则新建并返回一个新的
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
//将返回的securityContext保存到本地线程中,方便将来访问
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
} finally {
//获取之前保存的securityContext
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
// Crucial removal of SecurityContextHolder contents - do this before anything else.
//清空SecurityContextHolder中的Context
SecurityContextHolder.clearContext();
//将Security保存到HttpSession中,下次请求的时候就可以利用保存好的SecurityContext
repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
if (debug) {
logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
再看一下loadContext方法
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
HttpServletRequest request = requestResponseHolder.getRequest();
HttpServletResponse response = requestResponseHolder.getResponse();
HttpSession httpSession = request.getSession(false);
//从这就可以看出来,SecurityContext的获取就是从Httpsession中获得的,所以根据java的传址特性,就可以判断出Httpsession会同步SecurityContext的变化
SecurityContext context = readSecurityContextFromSession(httpSession);
if (context == null) {
if (logger.isDebugEnabled()) {
logger.debug("No SecurityContext was available from the HttpSession: " + httpSession +". " +
"A new one will be created.");
}
//如果当然session中没有SecurityContext,则重新创建一个新的Context
context = generateNewContext();
}
requestResponseHolder.setResponse(
new SaveToSessionResponseWrapper(response, request, httpSession != null, context));
return context;
}
3、重点了解AbstractAuthenticationProcessingFilter,了解一下授权
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 {
--> 1 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);
--> 2 unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed
--> 3 unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
--> 4 successfulAuthentication(request, response, chain, authResult);
}
—>3 处代码表示身份校验失败之后调用方法unsuccessfulAuthentication()
该方法如下
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
SecurityContextHolder.clearContext();
if (logger.isDebugEnabled()) {
logger.debug("Authentication request failed: " + failed.toString(), failed);
logger.debug("Updated SecurityContextHolder to contain null Authentication");
logger.debug("Delegating to authentication failure handler " + failureHandler);
}
rememberMeServices.loginFail(request, response);
failureHandler.onAuthenticationFailure(request, response, failed);
}
最重要的一条语句是
failureHandler.onAuthenticationFailure(request,response,failed);
—>4 处代码表示验证身份信息成功后,调用successfulAuthentication()方法
该方法代码如下
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+ authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
successHandler.onAuthenticationSuccess(request, response, authResult);
}
最重要的一行代码是
successfulHandler.onAuthenticationSuccess(request,response,authResult);
4、重点了解FilterSecurityInterceptor,了解没有授权情况下,security是如何处理的
-
FilterSecurityInterceptor的创建是在WebSecurityConfigurerAdapter的configure方法,当我们调用http.authorizeRequests()时,会创建一个ExpressionUrlAuthorizationConfigurer添加到httpsecurity的configurers中,所有的configurers会在httpsecurity调用build方法的时候配置。
-
FilterSecurityInterceptor的逻辑
-
默认的AccessDecisionManager是AffirmativeBased
-
默认的AccessDecisionVoter是WebExpressionVoter(对于ExpressionUrlAuthorizationConfigurer)
- AffirmativeBased :至少一个投票者必须决定授予访问权限
- ConsensusBased :多数投票者必须授予访问权限
- UnanimousBased:所有投票者都必须投票或放弃投票授予访问权限(无投票表决拒绝访问)
-
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
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) {
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);
}
}
//......
}
如果这个filter之前没有执行过的话,那么首先执行的
InterceptorStatusToken token = super.beforeInvocation(fi);
这个是由AbstractSecurityInterceptor提供。
它就是springsecurity处理鉴权的入口。
AbstractSercurityInterceptor
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);
}
}
- 这里调用了accessDecisionManager来进行判断,如果没有登录态,在到达这个filter之前会先经过AnonymousAuthenticationFilter.java,其Authentication的值为AnonymousAuthenticationToken。如果匿名请求需要登录态的url,或者权限不够,则抛出AccessDeniedException。
5、了解spring security如何进行资源访问权限控制
参考一下两个链接
SpringBoot + SpringSecurity 控制授权
6、添加自定义Filter到FilterChain
public class BeforeLoginFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException,ServletException {
System.out.println("This is a filter before UsernamePasswordAuthenticationFilter.");
// 继续调用 Filter 链
filterChain.doFilter(servletRequest, servletResponse);
}
}
配置自定义Filter在SpringSecurity过滤器链中的位置
protected void configure(HttpSecurity http) throws Exception{
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/user/**").hasRole("USER")
.and()
.formLogin().loginPage("/login").defaultSuccessUrl("/user")
.and()
.logout().logoutUrl("/logout").logoutSuccessUrl("/login");
// 在 UsernamePasswordAuthenticationFilter 前添加 BeforeLoginFilter
http.addFilterBefore(new BeforeLoginFilter(), UsernamePasswordAuthenticationFilter.class);
// 在 CsrfFilter 后添加 AfterCsrfFilter
http.addFilterAfter(new AfterCsrfFilter(), CsrfFilter.class);
}
说明: HttpSecurity 有三个常用方法来配置:
-
addFilterBefore(Filter filter, Class beforeFilter) 在 beforeFilter 之前添加 filter
-
addFilterAfter(Filter filter, Class afterFilter) 在 afterFilter 之后添加 filter
-
addFilterAt(Filter filter, Class atFilter) 在 atFilter 相同位置添加 filter, 此 filter 不覆盖 filter