从本系列开始,博主将带来大家深入学习Spring Security。博主对该框架的看法是不但要会使用,还有能够理解其源码,要知其然,还要知其所以然。
相信朋友们阅读完博主本系列全部文章之后,定会理解Spring Security,让我们从入门、到理解、最终吊打面试官!
PS:博主早在8月中旬开始写本系列博客,本来想一文搞定Spring Security,但由于Spring Security的细节特别多,已经写了2w字却感觉才将心中所想写了近半不到,因此萌生了想写Spring Security体系一系列文章的想法。还请多多关注博主,不胜感激!
在本篇内容,博主给大家介绍一下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依赖。
<!--标识一个springboot的web工程-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入Spring Security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</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的默认安全策略。
博主简单梳理一下这块逻辑。文章来源:https://www.toymoban.com/news/detail-402128.html
- 客户端请求一个资源URL。
- 请求会按照顺序经过Spring Security默认提供的15个过滤器,在FilterSecurityInterceptor过滤器中发现用户没有认证会抛出AccessDeniedException异常。
- 异常会被ExceptionTranslationFilter过滤器被捕获到,并调用authenticationEntryPoint#commence方法将请求重定向到/login接口。
- 客户端再次请求/login接口。
- 请求被DefaultLoginPageGeneratingFilter过滤器拦截,并生成了一个登录页面并返回给客户端。
五、 小结
本章博主主要给大家介绍了Spring Security在市场上的使用情况,以及Spring Security的整体架构。并举了一个简单的例子说明为什么仅仅引入了Spring Security的maven依赖就对资源做了保护。
接下来博主会带来大家进一步理解Spring Security的认证细节,尽情期待!文章来源地址https://www.toymoban.com/news/detail-402128.html
到了这里,关于【Spring Security详解】第一章 | 概述的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!