ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

SpringSecurity源码学习

2021-06-27 10:59:36  阅读:143  来源: 互联网

标签:null request authentication 认证 学习 Authentication 源码 SpringSecurity response


基于源码的学习,只做部分源码的探讨,借鉴的尚硅谷老师的图,很对对源码的解释写在了代码里,
分三块:认证流程,权限访问流程 +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");
            }

        }
}

标签:null,request,authentication,认证,学习,Authentication,源码,SpringSecurity,response
来源: https://blog.csdn.net/m0_48112568/article/details/118265827

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有