基于源码的学习,只做部分源码的探讨,借鉴的尚硅谷老师的图,很对对源码的解释写在了代码里,
分三块:认证流程,权限访问流程 +springSecurity请求共享认证信息
主要依托于过滤器: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中的方法进行认证,即查数据)
具体代码:
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);
//得到 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中的具体操作的详细解释)
主要两点,未认证成功(即上述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 }
public interface Authentication extends Principal, Serializable { Collection<? extends GrantedAuthority> getAuthorities();//权限集合 Object getCredentials();//密码验证 Object getDetails();//用户详细信息 Object getPrincipal();// boolean isAuthenticated();//是否被授权 void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;//设置是否被授权 }
上面都是在阅读源码的过程中进行深入的,下面回到最开始的源码,进行探究授权的相关内容
将未认证的信息传进去,进行身份的认证
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的内容
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); }
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); }
看源码:
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"); } } }