SpringSecurity是一系列filter,我们看功能实现,看你具体Filter就可以了。
我们需要关注的过滤器大致有4个:
- UsernamePasswordAuthenticationFilter:登录认证过滤器
- FilterSecurityInterceptor:权限认证过滤器
- ExceptionTranslationFilter:登录/权限认证失败处理器
- RememberMeAuthenticationFilter:自动登录过滤器
1、UsernamePasswordAuthenticationFilter
在类说明我们可以看到如下内容:
- 该过滤器处理表单提交的身份验证
- 使用该过滤器需要两个参数:用户名和密码,默认参数名称包含在静态字段SPRING_SECURITY_FORM_USERNAME_KEY和SPRING_SECURITY_FORM_PASSWORD_KEY 。 也可以在配置文件设置usernameParameter和passwordParameter属性来更改参数名称。
- 默认情况下,此过滤器只响应URL /login
doFilter()方法: 当前方法为父类AbstractAuthenticationProcessingFilter
实现,我们可以将doFilter()
方法划分下面4个部分:
private void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 1. 判断当前路径是否需要响应
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
try {
// 2. 认证过程
Authentication authenticationResult = attemptAuthentication(request, response);
...
// 3. 认证成功
successfulAuthentication(request, response, chain, authenticationResult);
}
catch (AuthenticationException ex) {
...
// 4. 认证失败
unsuccessfulAuthentication(request, response, failed);
}
}
示意图如下:
1、判断当前路径是否响应
我们可以从如下代码看到当前请求是否响应就是调用一个RequestMatcher
进行匹配,RequestMatcher
在父类中只有引用,没有实现,在子类中才有默认的RequestMatcher
实现。
public abstract class AbstractAuthenticationProcessingFilter {
private RequestMatcher requiresAuthenticationRequestMatcher;
protected boolean requiresAuthentication(HttpServletRequest request,
HttpServletResponse response) {
if (this.requiresAuthenticationRequestMatcher.matches(request)) {
return true;
}
return false;
}
}
子类:UsernamePasswordAuthenticationFilter
public class UsernamePasswordAuthenticationFilter{
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER =
new AntPathRequestMatcher("/login","POST");
public UsernamePasswordAuthenticationFilter() {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
}
}
我们可以通过自定义子类或者配置文件的方式来修改匹配路径,例如formLogin().loginProcessingUrl("登录路径")
// 这个authFilter会指向UsernamePasswordFilter
public T loginProcessingUrl(String loginProcessingUrl) {
this.loginProcessingUrl = loginProcessingUrl;
this.authFilter.setRequiresAuthenticationRequestMatcher(
createLoginProcessingUrlMatcher(loginProcessingUrl));
return getSelf();
}
示意图如下:
2、认证过程
方法由子类实现
public class UsernamePasswordAuthenticationFilter{
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response)throws AuthenticationException {
// 1. 从子类中获取参数值:request.getParameter(this.passwordParameter)
String username = obtainUsername(request);
...
String password = obtainPassword(request);
...
// 2. 根据用户名和密码生成Token
UsernamePasswordAuthenticationToken authRequest =
new UsernamePasswordAuthenticationToken(username, password);
setDetails(request, authRequest);
// 3. 进行下一步认证
return this.getAuthenticationManager().authenticate(authRequest);
}
}
UsernamePasswordAuthenticationToken
我们可以把它看成有如下5个参数类。
public class UsernamePasswordAuthenticationToken {
private final Object principal;
private Object credentials;
// GrantedAuthority可以简单的认为String类型
private final Collection<GrantedAuthority> authorities;
private Object details;
private boolean authenticated = false;
}
this.getAuthenticationManager().authenticate(authRequest)
会跳转到如下类ProviderManager
:
public class ProviderManager implements AuthenticationManager{
// 存放AuthenticationProvider的集合
private List<AuthenticationProvider> providers = Collections.emptyList();
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// 1. 获取authentication的class对象
Class<? extends Authentication> toTest = authentication.getClass();
// 2. 遍历集合
for (AuthenticationProvider provider : getProviders()) {
// 判断当前认证器是否支持,判断的方式就是当前authentication是否为适当的子类实现类。
if (!provider.supports(toTest)) {
continue;
}
// 进入认证流程
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AuthenticationException ex) {
lastException = ex;
}
}
// 调用parent的authnticate()方法
if (result == null && this.parent != null) {
try {
parentResult = this.parent.authenticate(authentication);
result = parentResult;
}
catch (AuthenticationException ex) {
parentException = ex;
lastException = ex;
}
}
// 返回结果
if (result != null) {
if (this.eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
((CredentialsContainer) result).eraseCredentials();
}
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
}
}
authenticate()方法流程如下:
- 获取当前authentication的class对象
- 遍历authenticationProvider的集合,先判断是否支持该authentication,后再进行认证
- 如果result = null且parent != null,那就调用parent的authenticate()方法,parent也是一个ProviderManager。
- 如果result != null,清空密码后(credenticals = null)返回。
判断当前认证器是否支持的方法,从下面的方法看出来,还是比较简单的。
public boolean supports(Class<?> authentication) {
return (AnonymousAuthenticationToken.class.isAssignableFrom(authentication));
}
public boolean supports(Class<?> authentication) {
return (RememberMeAuthenticationToken.class.isAssignableFrom(authentication));
}
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));
}
流程细节如下:
AuthenticationProvider
的认证方法:
如果我们不去主动添加,常见的认证器有三种:AnonymousAuthenticationProvider
,RememberMeAuthenticationProvider
,DaoAuthenticationProvider
。
我们主要来看下第三种:DaoAuthenticationProvider
,把多余的代码去掉只下面这样
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// 1. 获取用户名
String username = determineUsername(authentication);
...
// 2. 获取信息,本质上就是调用UserDetailsService.loadUserByUsername(username)
user = retrieveUser(username, (UsernamePasswordAuthenticationToken)
...
// 3. 提交上来的信息与查询得到的信息进行对比
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
...
// 4. 创建一个Token
return createSuccessAuthentication(principalToReturn, authentication, user);
}
示意图如下:
认证过程就是这样的!!!还是比较简单的。。。。
3、认证成功
还是从doFilter()方法开始看起,下面来
successfulAuthentication(request, response, chain, authenticationResult);
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
// 1. 保存authentication
SecurityContextHolder.getContext().setAuthentication(authResult);
// 2. rememberMeServices的操作
this.rememberMeServices.loginSuccess(request, response, authResult);
// 3. 继续调用认证成功处理器
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
1、保存authentication
SecurityContextHolder.getContext().setAuthentication(authResult);
我们根据源码向下找,SecurityContextHolder
的默认配置是ThreadLocalSecurityContextHolderStrategy
一看ThreadLocal
,其实一切都明白了,
// SecurityContext是一个只有一个Authentication的类型
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
2、rememberMeServices的操作
如果开启了rememeberMe功能,那RememberMeServices
的默认实现为TokenBasedRememberMeServices
。
public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication successfulAuthentication) {
// 1. 从authentication中获取用户名和密码
String username = retrieveUserName(successfulAuthentication);
String password = retrievePassword(successfulAuthentication);
...
int tokenLifetime = calculateLoginLifetime(request, successfulAuthentication);
long expiryTime = System.currentTimeMillis();
expiryTime += 1000L * ((tokenLifetime < 0) ? TWO_WEEKS_S : tokenLifetime);
// 2. 根据用户名、密码、过期时间和自定义的key来生成Token签名保存在cookie中。
String signatureValue = makeTokenSignature(expiryTime, username, password);
// 3. 向response中设置cookie
setCookie(new String[] { username, Long.toString(expiryTime), signatureValue },
tokenLifetime, request,response);
}
3、继续调用
AuthenticationSuccessHandler
的默认实现为SavedRequestAwareAuthenticationSuccessHandler
。
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,Authentication authentication)
throws ServletException, IOException {
// 1. 从缓存中取出缓存的请求
SavedRequest savedRequest = this.requestCache.getRequest(request, response);
if (savedRequest == null) {
super.onAuthenticationSuccess(request, response, authentication);
return;
}
// 2. 从配置文件取出数据
String targetUrlParameter = getTargetUrlParameter();
if (isAlwaysUseDefaultTargetUrl()|| (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
this.requestCache.removeRequest(request, response);
super.onAuthenticationSuccess(request, response, authentication);
return;
}
// 3. 删除在身份验证过程中可能已存储在会话中的与身份验证有关的临时数据
clearAuthenticationAttributes(request);
String targetUrl = savedRequest.getRedirectUrl();
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
// 上面转到这里
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,Authentication authentication)
throws IOException, ServletException {
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}
具体功能如下:
- 从缓存中获取
SavedRequest
,如果有则取出targetUrl并重定向请求。 - 从配置中读取
targetUrlParameter
,如果不为空则重定向,可以通过formLogin().defaultSuccessUrl()
来进行配置。
示意图如下:
4、认证失败
老规矩,从doFilter()方法开始看,接下面
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
// 1. 清除保存的authentication
SecurityContextHolder.clearContext();
// 2. rememberMeServices操作
this.rememberMeServices.loginFail(request, response);
// 3. 继续调用
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
第一步不说了把,请求ThreadLocal
中的保存的SecurityContext
。
2、rememberMeServices操作
public final void loginFail(HttpServletRequest request, HttpServletResponse response) {
// 1、将cookie的maxAge设置为0
cancelCookie(request, response);
// 2、暂时没有实现
onLoginFail(request, response);
}
3、继续调用
failureHandler
的默认实现为SimpleUrlAuthenticationFailureHandler
。
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
// 1. 通过配置文件设置formLogin().failureUrl("xxxx")
if (this.defaultFailureUrl == null) {
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
return;
}
saveException(request, exception);
// 默认为false
if (this.forwardToDestination) {
request.getRequestDispatcher(this.defaultFailureUrl).forward(request, response);
}
else {
this.redirectStrategy.sendRedirect(request, response, this.defaultFailureUrl);
}
}
实现功能如下:
- 获取配置文件配置的
defaultFailureUrl
,再通过判断forwardToDestination
来决定是转发还是重定向。
示意图如下:
2、FilterSecurityInterceptor
过滤器说明:
- 该过滤器负责资源的安全性处理,也就是权限验证。
public class FilterSecurityInterceptor{
...
// 入口方法
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)throws IOException, ServletException {
invoke(new FilterInvocation(request, response, chain));
}
// 接着上面:filterInvocation = new FilterInvocation(request, response, chain);
public void invoke(FilterInvocation filterInvocation)
throws IOException, ServletException {
// 1. 判断当前请求是不是处理过
if (isApplied(filterInvocation) && this.observeOncePerRequest) {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
return;
}
// 2. 给当前请求打个标记
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
// 3. 权限认证
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
// 4. 过滤链传递
try {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(),
filterInvocation.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
...
}
认证过程:
// object = new FilterInvocation(request, response, chain)
protected InterceptorStatusToken beforeInvocation(Object object) {
// 1. 从配置文件中读取配置
Collection<ConfigAttribute> attributes =
this.obtainSecurityMetadataSource().getAttributes(object);
// 2. 判断是否重新用户身份认证
Authentication authenticated = authenticateIfRequired();
// 3. 进行权限认证
attemptAuthorization(object, attributes, authenticated);
...
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
}
上面接下来:
private void attemptAuthorization(Object object,
Collection<ConfigAttribute> attributes,Authentication authenticated) {
...
this.accessDecisionManager.decide(authenticated, object, attributes);
...
}
accessDecisionManager
的默认实现为AffirmativeBased
。
public class AffirmativeBased{
private List<AccessDecisionVoter<?>> decisionVoters;
// 遍历投票器集合进行投票
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);
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
return;
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
break;
default:
break;
}
}
checkAllowIfAllAbstainDecisions();
}
}
AccessDecisionVoter
默认实现为WebExpressionVoter
。
示意图如下:
3、ExceptionTranslationFilter
过滤器说明:
- 该过滤器处理过滤链中引发的所有AccessDeniedException和AuthenticationException 。
- 如果检测到AuthenticationException ,则过滤器将启动authenticationEntryPoint 。
- 如果检测到AccessDeniedException ,则筛选器将确定该用户是否为匿名用户。 如果他们是匿名用户,则将启动authenticationEntryPoint 。 如果他们不是匿名用户,则过滤器将委派给AccessDeniedHandler 。 默认情况下,过滤器将使用AccessDeniedHandlerImpl 。
入口方法:
private void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
}
catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
// 1. 获得异常抛出链
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
// 2. 尝试获取AuthenticationException或者AccessDeniedException
RuntimeException securityException =
(AuthenticationException) this.throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (securityException == null) {
securityException = (AccessDeniedException) this.throwableAnalyzer
.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
}
// 3. 没有这两个异常则抛出
if (securityException == null) {
rethrow(ex);
}
// 4. 处理异常
handleSpringSecurityException(request, response, chain, securityException);
}
}
private void handleSpringSecurityException(HttpServletRequest request,
HttpServletResponse response,FilterChain chain, RuntimeException exception)
throws IOException, ServletException {
// 1. 根据异常类型的不同调用不同的处理器
if (exception instanceof AuthenticationException) {
handleAuthenticationException(request, response, chain,
(AuthenticationException) exception);
}
else if (exception instanceof AccessDeniedException) {
handleAccessDeniedException(request, response, chain,
(AccessDeniedException) exception);
}
}
AuthenticationException:
private void handleAuthenticationException(HttpServletRequest request,
HttpServletResponse response,FilterChain chain,
AuthenticationException exception) throws ServletException, IOException {
// 1. 开始处理
sendStartAuthentication(request, response, chain, exception);
}
protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
AuthenticationException reason) throws ServletException, IOException {
// 1. 清除当前线程保存的authentication
SecurityContextHolder.getContext().setAuthentication(null);
// 2. 保存当前请求
this.requestCache.saveRequest(request, response);
// 3. 调用处理器
this.authenticationEntryPoint.commence(request, response, reason);
}
authenticationEntryPoint
的默认实现为LoginUrlAuthenticationEntryPoint
。
public class LoginUrlAuthenticationEntryPoint {
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
// userForward默认为false
if (!this.useForward) {
String redirectUrl = buildRedirectUrlToLoginPage(request, response,
authException);
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
return;
}
String redirectUrl = null;
// forceHttps 默认为false
if (this.forceHttps && "http".equals(request.getScheme())) {
redirectUrl = buildHttpsRedirectUrlForRequest(request);
}
if (redirectUrl != null) {
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
return;
}
String loginForm = determineUrlToUseForThisRequest(request, response,
authException);
RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
dispatcher.forward(request, response);
return;
}
}
AccessDeniedException:
AccessDeniedHandler
默认为AccessDeniedHandlerImpl
。
private void handleAccessDeniedException(HttpServletRequest request,
HttpServletResponse response,FilterChain chain, AccessDeniedException exception) throws ServletException, IOException {
// 1. 获取authentication
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
// 2. 判断是否匿名,匿名/rememberMe情况要转给AuthenticationException情况处理
boolean isAnonymous = this.authenticationTrustResolver
.isAnonymous(authentication);
if (isAnonymous || this.authenticationTrustResolver
.isRememberMe(authentication)) {
sendStartAuthentication(request, response, chain,
new InsufficientAuthenticationException(xxx));
}
else {
// 3. GO
this.accessDeniedHandler.handle(request, response, exception);
}
}
具体处理:
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException)
throws IOException, ServletException {
// 1. 响应是否提交
if (response.isCommitted()) {
return;
}
// 2. 错误响应页面是否配置
if (this.errorPage == null) {
response.sendError(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase());
return;
}
// 3. 配置错误信息,重定向到错误页面
request.setAttribute(WebAttributes.ACCESS_DENIED_403, accessDeniedException);
response.setStatus(HttpStatus.FORBIDDEN.value());
request.getRequestDispatcher(this.errorPage).forward(request, response);
}
示意图如下:
4、RememberMeAuthenticationFilter
过滤器说明: 实现自动登录的一个过滤器,逻辑很简单,判断内存中有没有保存authentication,有则认证,没有则将过滤链传递下去。
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 1. 判断内存中是否有保存authentication
if (SecurityContextHolder.getContext().getAuthentication() != null) {
chain.doFilter(request, response);
return;
}
// 2. 取出请求中的cookie并解析成authentication
Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request,
response);
// 3. authentication不为空,则进行身份验证
if (rememberMeAuth != null) {
try {
rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);
SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
onSuccessfulAuthentication(request, response, rememberMeAuth);
return;
}
}
catch (AuthenticationException ex) {
this.rememberMeServices.loginFail(request, response);
onUnsuccessfulAuthentication(request, response, ex);
}
}
chain.doFilter(request, response);
}
总体流程如下: