Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在 Spring 应用上下文中配置的 Bean,充分利用了 Spring IoC(Inversion of Control 控制反转),DI(Dependency Injection 依赖注入)和 AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
Spring Security 对 Web 安全性的支持大量地依赖于 Servlet 过滤器。这些过滤器拦截进入请求,并且在应用程序处理该请求之前进行某些安全处理。Spring Security 提供有若干个过滤器,它们能够拦截 Servlet 请求,并将这些请求转给认证和访问决策管理器处理,从而增强安全性。用户可以根据自己的需要,使用适当的过滤器来保护自己的应用程序。
本项目所使用的开发环境及主要框架版本:
java version “1.8.0_144”
spring boot 2.2.0.RELEASE
spring security 5.2.0.RELEASE
首先新建一个 Spring Boot 项目,然后在根目录下的 pom.xml 文件中引入 spring-boot-starter-security 坐标:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
接下来我们来定义一个 HomeController 并实现一个 home
方法,对外提供一个 /home
接口:
@RestController public class HomeController { @GetMapping("home") public String home() { return "This is home page"; } }
之后我们直接启动 Spring Boot 项目,然后打开浏览器访问 http://localhost:8080/home ,如果不出我所料的话,在当前页面你会看到以下内容:
这是什么情况,你明明访问的是 http://localhost:8080/home ,怎么突然变成 http://localhost:8080/login ,期望的访问路径被系统自动重定向到了登录页,而且还让你输入用户名和密码。相信此时对于刚入门的小伙伴来说,肯定一脸懵逼。当初我遇到这种情形的时候,那时的心情是这样的:
小伙伴们,这里我只能告诉你们用户名,密码还得靠你们自己来发掘。Spring Security 会为我们创建一个默认的用户,对应的用户名是 user ,而密码可以从控制台中获取:
UserDetailsServiceAutoConfiguration : Using generated security password: 14186562-e682-4744-a6a6-02b5f7c3fb19
注意:每次重新启动 Spring Boot 项目时,都会生成一个随机的新密码。
下面我们在登录页面输入正确的用户名和密码后,再重新访问 http://localhost:8080/home 地址时,在当前页面你会看到以下内容:
This is home page
前面我们已经介绍过了,每次重新启动 Spring Boot 项目时,都会生成一个随机的新密码。这在实际项目开发过程中是很另人抓狂的一件事,而针对这个问题 Spring Security 也为我们提供了对应的解决方案,即通过系统默认的配置文件来设置用户名和密码。
下面我们来简单介绍一下如何操作,首先打开项目中的 application.properties 文件,输入以下配置信息:
#设置Spring Security默认的用户名和密码 spring.security.user.name=semlinker spring.security.user.password=123456
在输入完成之后,重新启动一下项目,待项目启动完成之后,就可以访问 http://localhost:8080/login 地址验证上述的配置是否生效,小伙伴们可以自行验证一下。除了设置用户和密码之外,我们还可以设置默认用户的角色,比如:
#设置Spring Security默认的用户名、密码和角色 spring.security.user.name=semlinker spring.security.user.password=123456 spring.security.user.roles=admin
Spring Security 是一款灵活、功能强大的安全框架。为了实现灵活性,它为框架的使用者提供了非常多的配置属性,本节我们已经介绍了以下 3 个配置属性:
Key | Default Value | Description |
---|---|---|
spring.security.user.name | user | 默认用户名 |
spring.security.user.password | 默认密码 | |
spring.security.user.roles | 默认角色 |
如果你对其它的配置属性感兴趣,可以访问 SpringBoot官网 - security-properties 查看所有支持的属性。
前面我们已经介绍了如何在 Spring Boot 项目中快速集成 Spring Security,之后我们介绍了如何从控制台获取默认的登录密码。除此之外,我们还进一步介绍了如何通过系统配置文件修改默认的用户名和密码。最后我们来简单分析一下系统默认的登录页。
当我们在默认登录页输入已知的用户名和密码,点击 Sign in 按钮后,将会发起一个登录请求,具体请求信息如下图所示:
通过观察上图我们可知,在执行登录操作时,会以 POST 方式请求 http://localhost:8080/login
地址,我们在界面中所输入的用户名和密码会以 Form Data 的形式提交到服务器。眼力好的小伙伴,估计也发现了除了 username 和 password 之外,还多了一个 _csrf 属性,这个属性是哪里来的呢?
要揭开这个问题的谜底,我们来看一下登录页中登录表单对应的 HTML 源代码:
<form class="form-signin" method="post" action="/login"> <h2 class="form-signin-heading">Please sign in</h2> <p> <label for="username" class="sr-only">Username</label> <input type="text" id="username" name="username" class="form-control" placeholder="Username" required="" autofocus=""> </p> <p> <label for="password" class="sr-only">Password</label> <input type="password" id="password" name="password" class="form-control" placeholder="Password" required=""> </p> <input name="_csrf" type="hidden" value="e0c83d21-e72f-4693-95ad-60ac3b71a010"> <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button> </form>
在 Sign in 登录按钮上面,多了一个隐藏域,它的 name 就是 Form Data 中的 _csrf
,而 value 值与 Form Data 中的值一模一样。其中 _csrf
的属性名可不是随便起的,而是有它实际的含义。CSRF 是 Cross-site request forgery 的缩写,对应的中文意思是 跨站请求伪造 。它也被称为 one-click attack 或者 session riding ,通常缩写为 CSRF 或者 XSRF , 是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比, XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了 Web 中用户身份验证的一个漏洞: 简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的 。
假如一家银行用以运行转账操作的 URL 地址如下: http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName
那么,一个恶意攻击者可以在另一个网站上放置如下代码:
<img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">
如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失1000 资金。
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着 如果服务端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险 。
透过例子能够看出,攻击者并不能通过 CSRF 攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是 欺骗用户浏览器,让其以用户的名义运行操作 。
HTTP 头中有一个 Referer 字段,这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer 字段应和请求的地址位于同一域名下。以上文银行操作为例,Referer 字段地址通常应该是转账按钮所在的网页地址,应该也位于 www.examplebank.com 之下。而如果是 CSRF 攻击传来的请求,Referer 字段会是包含恶意网址的地址,不会位于 www.examplebank.com 之下,这时候服务器就能识别出恶意的访问。
这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。 但这种办法也有其局限性,因其完全依赖浏览器发送正确的 Referer 字段。 虽然 http 协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段。 并且也存在攻击者攻击某些浏览器,篡改其 Referer 字段的可能。
由于 CSRF 的本质在于攻击者欺骗用户去访问自己设置的地址,所以如果要求在访问敏感数据请求时,要求用户浏览器提供不保存在 cookie 中,并且攻击者无法伪造的数据作为校验,那么攻击者就无法再运行 CSRF 攻击。
这种数据通常是窗体中的一个数据项。服务器将其生成并附加在窗体中,其内容是一个伪随机数。当客户端通过窗体提交请求时,这个伪随机数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪随机数,而通过 CSRF 传来的欺骗性攻击中, 攻击者无从事先得知这个伪随机数的值,服务端就会因为校验 token的值为空或者错误,拒绝这个可疑请求。
了解完 CSRF(跨站请求伪造)相关的攻击细节和防御措施之后,相信小伙伴们也搞清楚登录表单中 _csrf
隐藏域的作用了。至此为止,本文的主要内容都已经介绍完了,后续本人还会继续发布 Spring Security 相关文章,感兴趣的小伙伴请继续关注本人的 Spring Security 专题哟。
全栈修仙之路,及时阅读 Angular、TypeScript、Node.js/Java和Spring技术栈最新文章。