Spring-Security实现流程
spring-security入口
DelegatingFilterProxy(Spring-Web) ->
FilterChainProxy `(Spring-Security)->
这里看出Spring是通过DelegatingFilterProxy来整合Spring-Security的
一般这个类在我们的web.xml配置中使用的,当我们定义的filter为DelegatingFilterProxy
此时呢,所有的请求filter,都会去容器中找一个叫springsecurityfilterchain的bean来处理。
这里是方便我们去处理web应用,当我们需要一个filter来处理,此时我们只需要在spring相关的配置文件中定义一个bean,id为
springSecurityFilterChain
,而在web.xml中写成DelegatingFilterProxy。
特别提示:
DelegatingFilterProxy
不需要是一个spring 的bean,也就是说在不使用spring容器开发的应用中,可以是有该类。或者说在spring容器还没有初始化的时候,可以使用该类来处理。这也是为什么可以在web.xml中使用该类的原因。
DelegatingFilterProxy的作用
DelegatingFilterProxy 把处理的执行交给了 FilterChainProxy(注意这个类是spring-security中的类,然后会有很多这样的类去做具体的filter操作),这个bean一般使用 springSecurityFilterChain
这个bean id来做配置。
FilterChainProxy这里代理中包含很多的filter(关于spring-security安全认证和权限控制的filters),当然也可以是不同的filter chains,不过一个请求只会被一个filter chain处理,一般是根据请求路径来判断交给那个filter chain处理。
这里就是我们需要关注和定义的filters
SecurityFilterChain接口
spring-security可以有很多SecurityFilterChain,但是最终只有一个会被匹配。
可以用于oauth整合、jwt整合,也可以用于处理不需要安全处理的路径。
也就是说所有的链都必须实现该类
默认的**
DefaultSecurityFilterChain
**
一般springboot应用中有6条filter chains,第一个是用来控制静态文件和错误页面的(可以通过配置security.ignored来定义资源路径)。
最后一个filter chain 默认会对 /**
进行控制(包括认证、授权、异常处理、session处理、请求头处理等等)。
默认最后一个chain有11个filters,不过一般我们不需要太关注这些filters。
IgnoredPathsWebSecurityConfigurerAdapter
@Order(SecurityProperties.IGNORED_ORDER)
private static class IgnoredPathsWebSecurityConfigurerAdapter
implements WebSecurityConfigurer<WebSecurity> {
默认的springBoot security项目中,其实对静态资源的处理和error路径的处理,使用的同一个filter chain :
IgnoredPathsWebSecurityConfigurerAdapter
,它的order 为最高的。HIGHEST_PRECEDENCE = Integer.MIN_VALUE,所以这是一个回去匹配的filter chain。
在spring-security中新增一个filter chain(其实也是覆盖的意思,比如原来的filter针对的是 /** ,这里我们写了一个/foo/** ,此时如果请求是/foo/,则只会经过该filter chain;如果是其他的请求,才会使用原来默认的chain ):
@Configuration@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/foo/**")
...;
}}
注意:
如果想在spring-security 的chain使用filter Bean,1.不要通过使用@bean 方式去添加
2.如果使用FilterRegistrationBean注册filter为一个Bean,则需要显示设置其不被容器加载。
因为这两种方式都会将filter应用于整个web 容器中。
而添加自定义的security filter,需要通过
WebSecurityConfigurerAdapter#configure()
WebSecurityConfigurerAdapter#init()
开始认证处理
其实这里是认证处理的发起Filter,当用户第一次从浏览器访问受限资源时,因为其他认证Filter只对自己关注的AuthenticationToken进行处理,也就是说第一次访问时,这些Filter是去处理的,只有这个Filter发现如果用户没有进行认证,则进行相应的处理。比如:重定向到登入页面。
处理流程
The
ExceptionTranslationFilter
allows translation ofAccessDeniedException
andAuthenticationException
into HTTP responses.
- First, the
ExceptionTranslationFilter
invokesFilterChain.doFilter(request, response)
to invoke the rest of the application. - If the user is not authenticated or it is an
AuthenticationException
, then Start Authentication.- The SecurityContextHolder is cleared out
- The
HttpServletRequest
is saved in theRequestCache
. When the user successfully authenticates, theRequestCache
is used to replay the original request. - The
AuthenticationEntryPoint
is used to request credentials from the client. For example, it might redirect to a login page or send aWWW-Authenticate
header.
- Otherwise if it is an
AccessDeniedException
, then Access Denied. TheAccessDeniedHandler
is invoked to handle access denied.
源码展示
try {
filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException e) {
if (!authenticated || e instanceof AuthenticationException) {
startAuthentication();
} else {
accessDenied();
}
}
开始认证处理,从AuthenticationEntryPoint
开始,然后调用org.springframework.security.web.AuthenticationEntryPoint#commence,具体的实现类来实现如何进行认证。
是否需要处理认证
当用户提交认证信息后,比如输入用户名\密码之后。
AbstractAuthenticationProcessingFilter
该类将会判断请求是否需要认证或者是本类实现类应该处理的认证类型。
如果是,则进行具体的认证信息判断。
源码展示
这里以 UsernamePasswordAuthenticationFilter
为例:
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
//这里开始尝试进行认证信息判断
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
String username = obtainUsername(request);
String password = obtainPassword(request);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
//最终调用authenticationManager去进行认证
return this.getAuthenticationManager().authenticate(authRequest);
}
}
真正处理用户认证信息判断
认证判断
具体的认证判断由 AuthenticationProvider
的实现类来判断。
如之前分析,一个AuthenticationManger可以有多个AuthenticationProvider
,每个都可以处理或者处理不了,只要有一个能判断即可,或者都判断不了,还可以通过 ParentAuthenticationManager来兜底。
认证处理的结果
三种情况:
- 成功
- 不能决定
- 异常
如果返回认证异常,我们可以捕获后跳转到认证页面,比如登入页面,或者返回认证错误页面,在接口调用时,可以返回401响应等等