基于源码的学习,只做部分源码的探讨,借鉴的尚硅谷老师的图,很对对源码的解释写在了代码里,
分三块:认证流程,权限访问流程 +springSecurity请求共享认证信息
1.认证流程
主要依托于过滤器:UsernamePasswordAuthenticationFilter 这个过滤器用来进行用户的登陆验证等。
(1).会调用到父类中的方法AbstractAuthenticationProcessingFilter
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {}
过滤器要看他的doFilter,要点会在代码里注释
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!requiresAuthentication(request, response)) {//1.做提交方式的判断
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;//用于封装验证信息的类
try {//2.调用子类UsernamePasswordAuthenticationFilter中的attemptAuthentication,得到表单的数据进行认证,成功后把结果封装到authResult
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
//3.配置session的策略,比如设置session的最大并发数等等
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
//4 -4.1在这里表示,如果认证失败,会调用下面的方法
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
//4-4.2认证成功后进行的处理,continueChainBeforeSuccessfulAuthentication默认是false的,
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
successfulAuthentication(request, response, chain, authResult);
}
过程:
1.做提交方式的判断
2.调用子类UsernamePasswordAuthenticationFilter中的attemptAuthentication,得到表单的数据进行认证,成功后把结果封装到authResult
3.配置session的策略,
4
4.1在这里表示,如果认证失败,会调用下面的方法
4.2认证成功后进行的处理,continueChainBeforeSuccessfulAuthentication默认是false的,
2.深入UsernamePasswordAuthenticationFilter中的attemptAuthentication方法的操作:
源码
具体原因就是attemptAuthentication搞的鬼,上源码:
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {//1.判断是否是post提交
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
//如果是,则得到表单提交过来的用户名和密码
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
//得到
username = username.trim();
//得到一个token对象,用得到的数据放进token里,将认证状态标记为未认证(最开始的图里有这一步要求)
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
//设置相关信息
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
//调用authenticate进行认证
return this.getAuthenticationManager().authenticate(authRequest);
}
需要调用两个方法:obtainUsername obtainPassword:(只列出obtainUsername)
@Nullable
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(this.usernameParameter);
}
obtainUsername---->this.usernameParameter:
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";//注意这里的值
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login",
"POST");
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
总结一下注意点和过程:
1.判断是否是post提交,如果是,则得到表单提交过来的用户名和密码
2.获取到数据,将其标记为未认证的状态,将请求中的一些信息,设置到对象里面,调用authenticate进行身份的验证(会调用自己编写的userDetailsService中的方法进行认证,即查数据)
具体代码:
1.1判断是否是post提交,如果是,则得到表单提交过来的用户名和密码,同时细节处为页面表格提交的name必须写成“username”,“password”,否则security识别不到,具体原因上面源码已写
if (postOnly && !request.getMethod().equals("POST")) {//1.判断是否是post提交
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
//如果是,则得到表单提交过来的用户名和密码
String username = obtainUsername(request);
String password = obtainPassword(request);
2.获取到数据,将其标记为未认证的状态,将请求中的一些信息,设置到对象里面,调用authenticate进行身份的验证(会调用自己编写的userDetailsService中的方法进行认证,即查数据)
//得到
username = username.trim();
//得到一个token对象,用得到的数据放进token里,将认证状态标记为未认证(最开始的图里有这一步要求)
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
//设置相关信息
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
//调用authenticate进行认证
return this.getAuthenticationManager().authenticate(authRequest);
2.1有个方法:将请求信息设置到对象里,setDetails
protected void setDetails(HttpServletRequest request,
UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
public void setDetails(Object details) {
this.details = details;
}
2.2.关于源码中未授权问题:
源码中:
//得到一个token对象,用得到的数据放进token里,将认证状态标记为未认证(最开始的图里有这一步要求)
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
源码的具体做法:
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
//标记成未认证
setAuthenticated(false);
}
(注意:下面几点其实都是针对attemptAuthentication中的具体操作的详细解释)
3.再深入一点,看看UsernamePasswordAuthenticationToken相关代码:
主要两点,未认证成功(即上述2.2中的构造方法,上面已讲,下面说说认证成功)
认证成功:
/**
* This constructor should only be used by <code>AuthenticationManager</code> or
* <code>AuthenticationProvider</code> implementations that are satisfied with
* producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
* authentication token.
*
* @param principal
* @param credentials
* @param authorities
*/
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
//标记成认证
super.setAuthenticated(true); // must use super, as we override
}
4.UsernamePasswordAuthenticationToken 继承自AbstractAuthenticationToken,AbstractAuthenticationToken又实现了接口:Authentication 下面查看接口:Authentication:
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();//权限集合
Object getCredentials();//密码验证
Object getDetails();//用户详细信息
Object getPrincipal();//
boolean isAuthenticated();//是否被授权
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;//设置是否被授权
}
上面都是在阅读源码的过程中进行深入的,下面回到最开始的源码,进行探究授权的相关内容
5.看attemptAuthentication中的最后一步authenticate认证:
将未认证的信息传进去,进行身份的认证
return this.getAuthenticationManager().authenticate(authRequest);
1.getAuthenticationManager—
protected AuthenticationManager getAuthenticationManager() {
return authenticationManager;
}
方法返回AuthenticationManager
2.AuthenticationManager
public interface AuthenticationManager {}
发现他是个接口,找一下实现类
3.找他的实现类:ProviderManager,认证在这里实现
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {}
authenticate的具体操作;
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
//获取传入的Authentication类型.即UsernamePasswordAuthenticationToken.class
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
//之前的代码是通过迭代器得到内容,现在是for循环
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {//判断当前的AuthenticationProvider是否适用于
//即UsernamePasswordAuthenticationToken.class类型的Authentication
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
//成功找到适配当前认证方式的AuthenticationProvider
try {//调用找到的成功找到适配当前认证方式的AuthenticationProvider的authenticate()方法,开始验证
//如果认证成功,会返回一个标记已认证的Authentication对象
result = provider.authenticate(authentication);
if (result != null) {
//认证成功后,将传入的Authentication对象中的details信息拷贝到已经认证的Authentication对象中
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
} catch (AuthenticationException e) {
lastException = e;
}
}
if (result == null && parent != null) {
// Allow the parent to try.
//认证失败,使用父类型AuthenticationManager进行验证
try {
result = parentResult = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException e) {
lastException = parentException = e;
}
}
if (result != null) {//认证成功后,取出result中相关的敏感信息,要求相关类实现CredenttialsContainer接口
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
//去除过程就是调用CredentialsContainer接口的eraseCredentials方法
((CredentialsContainer) result).eraseCredentials();
}
// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
//上面的话也去确定了一定会发布相关信息,如果你没有,就找你爹,反正要有,下面的就保证不重复
//发布认证成功事件
if (parentResult == null) {
eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
// Parent was null, or didn't authenticate (or throw an exception).
//认证失败:抛出信息
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
1.先得到authentication,即之前的UsernamePasswordAuthenticationToken
2.通过迭代器得到内容,现在是for循环得到authentication的内容
6.回到最开始:找到认证失败和成功的方法(刚才是认证成功),去其父类中找
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);
}
/**
* Default behaviour for unsuccessful authentication.
* <ol>
* <li>Clears the {@link SecurityContextHolder}</li>
* <li>Stores the exception in the session (if it exists or
* <tt>allowSesssionCreation</tt> is set to <tt>true</tt>)</li>
* <li>Informs the configured <tt>RememberMeServices</tt> of the failed login</li>
* <li>Delegates additional behaviour to the {@link AuthenticationFailureHandler}.</li>
* </ol>
*/
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);
}
下面看看权限访问流程: 主要涉及到两个过滤器:FilterSecurityInterceptor 和 ExceptionTranslationFilter
1.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);
this.logger.debug("Chain processed normally");
} catch (IOException var9) {
throw var9;
} catch (Exception var10) {
//对抛出的异常,进行捕获,进行处理 AuthenticationException:没有权限...
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var10);
RuntimeException ase = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (ase == null) {
ase = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
}
if (ase == null) {
if (var10 instanceof ServletException) {
throw (ServletException)var10;
}
if (var10 instanceof RuntimeException) {
throw (RuntimeException)var10;
}
throw new RuntimeException(var10);
}
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", var10);
}
this.handleSpringSecurityException(request, response, chain, (RuntimeException)ase);
}
}
2.FilterSecurityInterceptor
根据资源访问权限,判断当前请求是否能访问资源
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
this.invoke(fi);
}
invoke:执行方法:
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if (fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null && this.observeOncePerRequest) {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} else {
if (fi.getRequest() != null && this.observeOncePerRequest) {
fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, (Object)null);
}
}
核心部分:
//根据 资源访问权限,判断当前请求是否能访问资源,,,此处需要经过springmvc才能进行相关操作
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());//访问资源过滤器
} finally {
super.finallyInvocation(token);
}
3.springSecurity请求间 共享认证信息
看源码:
public interface SecurityContext extends Serializable {
//本质是对Authentication进行封装
Authentication getAuthentication();
/**
* Changes the currently authenticated principal, or removes the authentication
* information.
*
* @param authentication the new <code>Authentication</code> token, or
* <code>null</code> if no further authentication information should be stored
*/
void setAuthentication(Authentication authentication);
}
看其实现类中的构造方法就有封装的步骤:
public class SecurityContextImpl implements SecurityContext {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private Authentication authentication;
public SecurityContextImpl() {}
public SecurityContextImpl(Authentication authentication) {
this.authentication = authentication;
}
再来看SecurityContextHolder
做了很多事,主要一个就是把操作和当前线程进行绑定:
public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
其次:在当前线程中找,如果有就返回,没有就新创建一个securityContext对象:
public static SecurityContext getContext() {
return strategy.getContext();
}
最后经过过滤器:SecurityContextPersistenceFilter进行回应:
此过滤器在所有过滤器的最前面,他将认证新区Authentication和session进行绑定,看他的dofilter:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
if (request.getAttribute("__spring_security_scpf_applied") != null) {
chain.doFilter(request, response);
} else {
boolean debug = this.logger.isDebugEnabled();
request.setAttribute("__spring_security_scpf_applied", Boolean.TRUE);
if (this.forceEagerSessionCreation) {
HttpSession session = request.getSession();
if (debug && session.isNew()) {
this.logger.debug("Eagerly created session: " + session.getId());
}
}
//请求来的时候,看当前session中有没有SecurityContext对象,有就返回,没有就建一个新的SecurityContext并返回
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
boolean var13 = false;
try {
var13 = true;
//将上面得到的SecurityContext对象放进去
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
var13 = false;
} finally {
if (var13) {
//到这里才开启回应,为请求取出SecurityContext
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
//移除securityContext
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute("__spring_security_scpf_applied");
if (debug) {
this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute("__spring_security_scpf_applied");
if (debug) {
this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}