SpringSecurity源码学习

基于源码的学习,只做部分源码的探讨,借鉴的尚硅谷老师的图,很对对源码的解释写在了代码里,
分三块:认证流程,权限访问流程 +springSecurity请求共享认证信息
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方法的操作:


源码

SpringSecurity源码学习
具体原因就是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

SpringSecurity源码学习

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请求间 共享认证信息

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");
            }

        }
}
上一篇:关于Client不能连接Mysql8.0的解决方法


下一篇:asp.net core AuthenticationMiddleware 在WebApi中的的使用