【Spring Security详解】第一章 | 概述

从本系列开始,博主将带来大家深入学习Spring Security。博主对该框架的看法是不但要会使用,还有能够理解其源码,要知其然,还要知其所以然。
相信朋友们阅读完博主本系列全部文章之后,定会理解Spring Security,让我们从入门、到理解、最终吊打面试官!

PS:博主早在8月中旬开始写本系列博客,本来想一文搞定Spring Security,但由于Spring Security的细节特别多,已经写了2w字却感觉才将心中所想写了近半不到,因此萌生了想写Spring Security体系一系列文章的想法。还请多多关注博主,不胜感激!

    • 一、 Spring Security简介
    • 二、 Spring Security提供了哪些功能
    • 三、 Spring Security是如何完成认证的
      • 3.1 DelegatingFilterProxy
      • 3.2 FilterChainProxy & SecurityFilterChain(二者关系密切,放一起讲述)
      • 3.3 多个SecurityFilterChain
    • 四、 Spring Security的初步使用
      • 4.1 集成Spring Security
      • 4.2 访问测试
      • 4.3 为什么默认访问资源会返回登录页面?
    • 五、 小结

在本篇内容,博主给大家介绍一下Spring Security在市场上的使用情况,以及Spring Security是通过什么原来完成认证操作的(梗概)。同时也涉及Spring Security的源码结构,可能不太易懂,建议配合本系列文章食用。

一、 Spring Security简介

在Java企业开发中,市面上常见的开源安全框架非常少,主要有以下几种方案:

  • Shiro
  • Spring Security
  • 企业自行开发的方案

几年前,微服务还没有大火的时候,Shiro以其轻量、简单、易于集成的优点独当一面。
而最近今年,随着微服务的大火,Spring Security作为Spring家族的首推的安全框架,在与Spring等其他组件的无缝整合的特点,导致其市面占有率也是逐年提高。

二、 Spring Security提供了哪些功能

Spring Security是Spring全家桶里面的一个项目,提供认证、授权以及应对漏洞攻击的保护。

  • 认证authentication: 可以简单理解成”你是谁“,最简单的例子就是用户登录,这就是认证,下文中登录操作代表认证
  • 授权authorization:可以简单理解成“你有哪些权限,你能做什么”,比如登录进来的用户是具有管理员或是普通用户的权限。
  • 保护protection:应对遭受漏洞利用的保护。

三、 Spring Security是如何完成认证的

Spring Security通过一系列过滤器完成认证与授权的工作。

对于SpringBoot工程,并没有引入其他依赖。
客户端发起请求时,tomcat容器会创建一个包括Filter和Servlet的FilterChain(过滤器链)。通过Filter可以控制请求与响应,以及是否调用下游的过滤器或Servlet。

接来下博主简要说明下Spring Security中起到核心作用的几个类,这是通过这几个类Spring Security才能集成到SpringBoot当中,并发挥作用。


此处参考了Spring Security的官网文档:链接: Spring Security官方文档

3.1 DelegatingFilterProxy

Spring Seucrity实现认证与授权的功能提供了很多过滤器,通过这些过滤器来拦截请求,并做相应处理。那么如何将这些过滤器嵌入到Spring的IOC容器呢,最好的做法就是将Spring Security这些过滤器注册成Bean,这样就可以统一的进行管理了,DelegatingFilterProxy就是为了实现这个目的。

Delegating这个名字很绕口ˈdelɪɡeɪtɪŋ' ,是委托的意思。DelegatingFilterProxy合到一起就是委托过滤器代理
整体意思就是DelegatingFilterProxy是一个代理,他委托了某个类(FilterChainProxy 下文会提到),并让那个类完成后续拦截操作。
可以把他理解成一个胶水,由他连接了web应用的原生过滤器和Spring Security的过滤器。
在这里插入图片描述

DelegatingFilterProxy是一个过滤器,里面有个成员变量

	private volatile Filter delegate;

他就是委托对象。

在客户端请求来临的时候会执行doFilter()方法。
首先会判断delegate是否为空,若为空的话从IOC容器中通过getBean()的方法拿到这个代理对象FilterChainProxy

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
		String targetBeanName = getTargetBeanName();
		Assert.state(targetBeanName != null, "No target bean name set");
		Filter delegate = wac.getBean(targetBeanName, Filter.class);
		if (isTargetFilterLifecycle()) {
			delegate.init(getFilterConfig());
		}
		return delegate;
	}

并执行代理对象的doFilter()方法。

	protected void invokeDelegate(
			Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		delegate.doFilter(request, response, filterChain);
	}

这样后续的操作就交由这个代理对象去做了。

3.2 FilterChainProxy & SecurityFilterChain(二者关系密切,放一起讲述)

FilterChainProxy这个类可以理解成过滤器链代理。DelegatingFilterProxy正是委托给FilterChainProxy,就是上文提到的delegate来完成拦截等操作。

FilterChainProxy是Spring Security发挥作用的入口,一切Spring Security的过滤器都是从这之后开始调用的。

另外值得注意的是,DelegatingFilterProxy是注册到Tomcat容器的一个过滤器,他的生命周期由Tomcat来控制。而FilterChainProxy则是Spring的IOC容器中的一个Bean。
在这里插入图片描述
这幅图展示了客户端client请求到系统中时,经过Tomcat的某些原生过滤器后,到达DelegatingFilterProxy。并委托给FilterChainProxy,而FilterChainProxy通过SecurityFilterChain来代理各种Filter实例。之后再到Tomcat的原生过滤器,最终到达Servet。
简而言之,FilterChainProxy使用SecurityFilterChain确定应对此请求调用哪些Spring Security过滤器。

在这里插入图片描述
可以看到delegate对象中包括一个过滤器链的列表(SecurityFilterChain)。其中DefaultSecurityFilterChain对象就是Spring Security的一个过滤器链,如前一个图片所示的SecurityFilterChain

FilterChainProxy作为一个代理类,他的doFilter()方法最终会调到下面的doFilterInternal()

private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
		HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
		List<Filter> filters = getFilters(firewallRequest);
		if (filters == null || filters.size() == 0) {
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
			}
			firewallRequest.reset();
			chain.doFilter(firewallRequest, firewallResponse);
			return;
		}
		if (logger.isDebugEnabled()) {
			logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
		}
		// 看这里
		VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
		virtualFilterChain.doFilter(firewallRequest, firewallResponse);
	}

将后续的执行操作交由他的一个内部静态类去实现。执行VirtualFilterChain#doFilter()方法。

	@Override
		public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
			if (this.currentPosition == this.size) {
				if (logger.isDebugEnabled()) {
					logger.debug(LogMessage.of(() -> "Secured " + requestLine(this.firewalledRequest)));
				}
				// Deactivate path stripping as we exit the security filter chain
				this.firewalledRequest.reset();
				this.originalChain.doFilter(request, response);
				//退出循环
				return;
			}
			this.currentPosition++;
			// 执行Spring Security的过滤器
			Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(),
						this.currentPosition, this.size));
			}
			nextFilter.doFilter(request, response, this);
		}

在这里面会循环的调用每个Spring Security提供的过滤器进行各种拦截处理操作,并在最后退出循环,进入Tomcat的其他过滤器中…
请添加图片描述

3.3 多个SecurityFilterChain

在Spring Security中,可以配置多个SecurityFilterChain,由FilterChainProxy 决定应使用哪个SecurityFilterChain

FilterChainProxy 会根据请求的路由匹配第一个符合条件的SecurityFilterChain,并执行其过滤器。
在这里插入图片描述

四、 Spring Security的初步使用

很多人对Spring Security的感觉都是太繁琐,其实到了微服务的天下,Spring Security的使用非常简单。
接下来博主以一个简单的例子给大家演示一下。

4.1 集成Spring Security

引入pom依赖。

 		
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-securityartifactId>
        dependency>

Spring Security是通过一系列过滤器来完成认证与授权的功能的。客户端请求之后逐个通过Spring Security的各种过滤器。当引入Spring Security依赖时,其实已经加载了Spring Security提供的许多个默认过滤器。

添加请求URL,当做用来测试的资源URL。

@RequestMapping("hello")
public class HelloController {
    @GetMapping()
    public String hello() {
        return "hello";
    }
}

启动项目,可以看到控制台输出的日志中,包括了如下的内容。
按照Spring Security官网的描述,其实生成了名为user的用户,密码为如下71c36beb-7af5-4116-b807-ab84e484e6fa
并且可以看到控制台打印了Spring Security默认加载的15个过滤器,正是他们支撑着Spring Security做到了认证相关的操作。
稍后博主会挑常见的过滤器给大家说明一下,值得注意的是,这15个过滤器的先后执行顺序就是控制台打印的顺序。

Using generated security password: 71c36beb-7af5-4116-b807-ab84e484e6fa

2022-08-22 20:22:07.179  INFO 10672 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: any request, [
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@4784013e,  
org.springframework.security.web.context.SecurityContextPersistenceFilter@2ca6546f,  
org.springframework.security.web.header.HeaderWriterFilter@aa10649,  
org.springframework.security.web.csrf.CsrfFilter@c4c0b41,  
org.springframework.security.web.authentication.logout.LogoutFilter@3af356f, 
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@267517e4, 
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@231baf51, 
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@6f952d6c, 
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@56ba8773, 
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7923f5b3, 
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@6050462a, 
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@5965844d, 
org.springframework.security.web.session.SessionManagementFilter@37095ded, 
org.springframework.security.web.access.ExceptionTranslationFilter@368d5c00, 
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@1763992e 
]

4.2 访问测试

接下来使用浏览器访问该资源。请求地址http://localhost:8080/hello(SpringBoot默认启动端口为8080)
可以观察到页面直接跳转到了http://localhost:8080/login并打开了一个登录页面。
在这里插入图片描述
F12可以看到页面请求http://localhost:8080/hello之后,返回响应302,并重定向到http://localhost:8080/login接口进行请求,该接口响应为一个页面。让我们完成登录操作。
在这里插入图片描述
输入账号密码后,点击登录(用户:user,密码:71c36beb-7af5-4116-b807-ab84e484e6fa),此时可以看到页面返回了接口hello,这也意味着只有认证成功才会允许访问资源。

这就是Spring Security的魅力。博主只是引入了一个Spring Security依赖就做到了所有资源的保护,那他是怎么做到的呢,且听我慢慢道来。

4.3 为什么默认访问资源会返回登录页面?

在这里插顶顶顶顶

该图片来自《深入浅出Spring Security》

当客户端发起一个资源的请求时(http://localhost:8080/hello),会经过上文所述的15个Spring Security的过滤器依次执行。

直到走到FilterSecurityInterceptor这个过滤器的时候,抛出一个访问被拒绝的异常。在这里插入图片描述
此处代码走到AbstractSecurityInterceptor类的原因是FilterSecurityInterceptor的doFilter()调用到了父类的代码,在父类的方法中抛出了AccessDeniedException,该异常会继续往上抛出。

直到ExceptionTranslationFilter的catch模块捕获到了这个异常。
在这里插入图片描述
并最终调用

authenticationEntryPoint.commence(request, response, reason);

在这里插入图片描述
最终将请求重定向到http://localhost:8080/login页面。
在这里插入图片描述

紧接着,客户端再次向服务请求http://localhost:8080/login

老规矩又开始按顺序执行这15个过滤器,直到到达DefaultLoginPageGeneratingFilter过滤器的时候,会判断若是访问登录请求URL或是登录失败或是退出成功中的一个,会执行下面的逻辑。
在这里插入图片描述
很明显isLoginUrlRequest(request) == true
然后代码来到了generateLoginPageHtml()
可以看到通过StringBuilder拼接了一个HTML的登录页面。
在这里插入图片描述
后续操作就是往response写入了这个html的登录页面,并返回。所以就有了当初请求http://localhost:8080/hello时,出现了一个登录页面。

这便是集成Spring Security后,Spring Security的默认安全策略。

博主简单梳理一下这块逻辑。

  1. 客户端请求一个资源URL。
  2. 请求会按照顺序经过Spring Security默认提供的15个过滤器,在FilterSecurityInterceptor过滤器中发现用户没有认证会抛出AccessDeniedException异常。
  3. 异常会被ExceptionTranslationFilter过滤器被捕获到,并调用authenticationEntryPoint#commence方法将请求重定向到/login接口。
  4. 客户端再次请求/login接口。
  5. 请求被DefaultLoginPageGeneratingFilter过滤器拦截,并生成了一个登录页面并返回给客户端。

五、 小结

本章博主主要给大家介绍了Spring Security在市场上的使用情况,以及Spring Security的整体架构。并举了一个简单的例子说明为什么仅仅引入了Spring Security的maven依赖就对资源做了保护。
接下来博主会带来大家进一步理解Spring Security的认证细节,尽情期待!

你可能感兴趣的:(SpringSecurity,java,后端,spring,boot,web安全)