spring security认证过程分析

本文深入解析了使用SpringSecurity框架实现OAuth2认证的全过程,包括授权码获取、登录流程、权限判断及重定向机制。揭示了认证过程中的复杂性与自定义扩展的可能性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近做的项目要求进行oauth2认证,使用了spring security框架。使用体验,emmmm应该是肥肠不好。这可能和我自己的个人能力有关,security给人的感觉就是非常的重,扩展起来也比较麻烦。总之下次应该不会回购(希望不要真香)。不过既来之则安之,还是要写一下初步使用体验的。

认证过程

这次的认证过程是标准的授权码过程,简要概括如下:
客户端发出请求-》换取授权码-》授权码换取token-》使用token访问受保护的资源。
demo示例可以参考前一篇博客。

换取授权码过程

换取授权码的过程相对比较复杂,会涉及到几个页面之间的跳转。访问流程如下:

优快云图标

下面对以上的几个判断过程进行分别分析:

跳转login页面

从浏览器访问/oauth/authorize,首先会通过一系列的过滤器,如下所示:
在这里插入图片描述
通过一级一级的过滤器,最终到达最后一个过滤器FilterSecurityInterceptor,在这个过滤器里面的核心逻辑是以下的代码片:

public void invoke(FilterInvocation fi) throws IOException, ServletException {
	if ((fi.getRequest() != null)
				&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
				&& observeOncePerRequest) {
		fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
		}
	else {
			// first time this request being called, so perform security checking
		if (fi.getRequest() != null) {
				fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
		}
			//判断是否有权限访问endpoint
		InterceptorStatusToken token = super.beforeInvocation(fi);

		try {   
			fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
			}finally {
				super.finallyInvocation(token);
			}
			super.afterInvocation(token, null);
		}
	}

通过调用父类的beforeInvocation()方法,使用决策器判断
在这里插入图片描述
默认的决策器是AffirmativeBased,只要有一票deny就会拒绝访问,这里将抛出AccessDeniedException,被ExceptionTranslationFilter所捕获。
捕获异常之后,调用核心方法handleSpringSecurityException()来进行处理。跟进去会发现,这里会把这次的请求保存下来:

protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException {
    SecurityContextHolder.getContext().setAuthentication((Authentication)null);
    //保存本次的请求
    this.requestCache.saveRequest(request, response);
    this.logger.debug("Calling Authentication entry point.");
    //重定向
    this.authenticationEntryPoint.commence(request, response, reason);
    }

另外就是执行commence方法进行重定向,该方法执行的是LoginUrlAuthenticationEntryPoint类中的方法,根据类名和注释就可以很明确的知悉,这里将重定向到登录,首先是生成重定向地址/login,然后进行重定向操作

/**
	 * Performs the redirect (or forward) to the login form URL.
	 */
	public void commence(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException authException) throws IOException, ServletException {

		String redirectUrl = null;

		if (useForward) {

			if (forceHttps && "http".equals(request.getScheme())) {
				// First redirect the current request to HTTPS.
				// When that request is received, the forward to the login page will be
				// used.
				redirectUrl = buildHttpsRedirectUrlForRequest(request);
			}

			if (redirectUrl == null) {
				String loginForm = determineUrlToUseForThisRequest(request, response,
						authException);

				if (logger.isDebugEnabled()) {
					logger.debug("Server side forward to: " + loginForm);
				}

				RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);

				dispatcher.forward(request, response);

				return;
			}
		}
		else {
			// redirect to login page. Use https if forceHttps true

			redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);

		}

		redirectStrategy.sendRedirect(request, response, redirectUrl);
	}

登录操作

重定向后会出现默认的登录页面
在这里插入图片描述
获取这个默认页面的方式很简单,在DefaultLoginPageGeneratingFilter中会生成login页面,核心代码如下:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		boolean loginError = isErrorPage(request);
		boolean logoutSuccess = isLogoutSuccess(request);
		if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
			//生成登录页面
			String loginPageHtml = generateLoginPageHtml(request, loginError,
					logoutSuccess);
			response.setContentType("text/html;charset=UTF-8");
			response.setContentLength(loginPageHtml.length());
			response.getWriter().write(loginPageHtml);

			return;
		}

		chain.doFilter(request, response);
	}

授权流程

点击登陆后,再次进入filter chain,从之前缓存的request中拿到获取授权码的URL进行访问,到这一步,终于进入了AuthorizationEndpoint的/oauth/authorize。这里会先判断用户是否授权,如果没有授权则进入授权页面,源码如下:
在这里插入图片描述

换取授权码

点击授权之后,会再次发起请求,拿到缓存的request,将请求发送至/oauth/authorize的endpoint,这时授权状态变为true,会生成code,并重定向到请求中的URL地址
在这里插入图片描述

总结

spring security通过很多filter来控制认证授权的流程,另外也可以对filter chain进行自定义扩展,来实现自定义的认证。在认证过程中,较为复杂的是换取认证码。
当换取认证码成功后,再次访问相同的换取认证码URL,投票器不再投出deny的票,不会再定向到login page。而如果授权还在有效期之内,也不会再重定向到授权页面,将直接换取code。
参考文献
集成指南
登录分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值