[转]9 种保护 Spring Boot 应用程序的好方法
Spring Boot于2014年首次发布,自那以后更新了很多。就像代码质量和测试一样,安全性已成为开发人员非常关心的问题。
如果你是一名开发人员,却对安全并不关心,那么你应该考虑下,是否应该如此。本文旨在教你如何创建更安全的Spring Boot应用程序。
我与Simon Maple合作,完成了这篇文章,他是一名Java冠军程序员和Snyk的开发者关系总监。我们都在安全行业的公司工作,热爱Java、并希望帮助开发人员,创建更安全的应用程序。
1. 在生产中使用HTTPS
传输层安全性(TLS),是HTTPS的官方名称。你可能听说过它称为SSL(安全套接字层)。SSL是已弃用的名称,TLS是一种加密协议,可通过计算机网络提供安全通信。其主要目标,是确保计算机应用程序之间的隐私和数据完整性。
过去,TLS/SSL证书很昂贵,而且HTTPS被认为很慢。机器的运算速度,极大提升解决了性能问题,而Let’s Encrypt,提供免费的TLS证书。这两项发展改变了现状,并使TLS很快成为主流。
2018年7月24日,Google Chrome已将HTTP网站标记为“不安全”。虽然这在网络社区,引起了相当多的争议,但它已然如此。
知名安全研究员Troy Hunt,创建了一个“为什么不使用HTTPS?”来记录不使用HTTPS的大型网站。你可能没有开发下一个大型网站,但为什么限制自己不使用呢?
生成和更新加密TLS证书可以自动化。由于它们是免费的,所以没有理由不去做!Spring Boot Secured By Let’s Encrypt,是介绍如何做到这一点的有用指南。
要在Spring Boot应用程序中强制使用HTTPS,你可以扩展WebSecurityConfigurerAdapter并要求安全连接。
1 2 3 4 5 6 7 8 |
@Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.requiresChannel().requiresSecure(); } } |
这个配置会强制在开发时也使用HTTPS,这可能会很麻烦,因为你必须使用自签名证书。如果你正在使用Heroku、Cloud Foundry或其他云提供商,更合理的配置,是寻找X-Forwarded-Proto请求头。
1 2 3 4 5 6 7 8 9 10 |
@Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.requiresChannel() .requestMatchers(r -> r.getHeader("X-Forwarded-Proto") != null) .requiresSecure(); } } |
云提供商可以大大简化TLS证书。亚马逊证书管理器与Let’s Encrypt完全相同,但默认情况下,它内置于所有AWS产品或服务中。它允许你提供100%免费的SSL证书、并处理自动续订等,只需零配置工作。Heroku也有自动证书管理。
另一个重要的事情,是使用HTTP严格传输安全性(HSTS)。HSTS是一种Web安全策略机制,可以保护网站免受协议降级攻击和cookie劫持。
服务器使用名为Strict Transport Security的响应头字段,将HSTS策略传送到浏览器。Spring Security默认发送此标头,以避免在开始时,出现不必要的HTTP跃点。
2. 借助Snyk检查你的依赖
你很可能不知道,应用程序使用了多少直接依赖项,你也很可能不知道,应用程序使用了多少传递依赖项。这通常是正常的,尽管依赖性构成了整个应用程序的大部分。
攻击者越来越多地针对开源依赖项,因为它们的重用,为恶意黑客提供了许多受害者。确保应用程序的整个依赖关系树中,没有已知的漏洞非常重要。
Snyk测试你的应用程序构建工件,标记那些已知漏洞的依赖项。它在仪表板为你提供了,在应用程序中使用的软件包中存在的漏洞列表。
此外,它还将建议升级版本、或提供补丁,以通过针对源代码存储库的拉取请求,来修复你的安全问题。Snyk还保护你的环境,在你的存储库上,提交的任何拉取请求,都将通过Webhooks触发自动测试,这样可以确保它们,不会引入新的已知漏洞。
每天都会在现有项目和库中发现新的漏洞,因此监控和保护生产部署也很重要。Snyk创建快照并监控你的部署,以便在发现新漏洞时,你可以通过喜欢的渠道、JIRA、Slack或电子邮件自动接收通知,并创建拉取请求,以提供新漏洞的升级和补丁。
Snyk可通过Web界面和命令行界面获得,因此你可以轻松地,将其与持续集成环境集成,并将其配置为,当存在严重性超出设定阈值的漏洞时中断构建。
你可以免费使用Snyk用于开源项目,也可以用于私有项目,但每月只有有限次数的测试。
3. 升级到最新版本
定期升级应用程序中的依赖项有多种理由,安全性是让你有升级动力的最重要原因之一。start.spring.io启动页面,尽可能使用最新版本的Spring软件包以及依赖项。
我发现在依赖项中寻找漏洞,可能有助于激励人们升级。然而,有大量证据表明并非所有CVE都被报道。一般来说,我发现理想(也许不实用)的解决方案是最新和最好的。 — Rob Winch
基础架构升级,通常不如依赖项升级具有破坏性,因为库的作者对向后兼容性和版本之间的行为更改的敏感性各不相同。话虽如此,当你在配置中发现安全漏洞时,你有三种选择:升级、修补程序或忽略。
在对应用程序进行必要的更改,以使用较新版本之后,就应用程序的整体运行状况而言,升级是最安全的。
对易受攻击的项目的修补,消除了程序包中的漏洞,但通常会留下一个,可能未经过充分测试的配置。由于修补程序,只会更改易受攻击的代码,因此对库的代码更改将会减少,同时可以降低破坏向后兼容性、或引入行为更改的可能性。
像Snyk这样的第三方安全公司,为许多漏洞提供了补丁程序,因此如果无法升级到更新版本的库,你仍然可以使用旧版本的补丁程序。
当然,忽略漏洞是一种选择,但不是一个好选择。也许你知道一个漏洞,但不相信它是可以直接利用的。请记住,它可能不在你的应用程序流程中,但在某些时候,开发人员可能会添加使用易受攻击路径的其他代码。
4. 启用CSRF保护
跨站点请求伪造是一种攻击,强制用户在他们当前登录的应用程序中,执行不需要的操作。如果用户是普通用户,则成功攻击可能涉及状态更改请求,如转移资金或更改其电子邮件地址。如果用户具有提升的权限,则CSRF攻击可能会危及整个应用程序。
Spring Security具有出色的CSRF支持,默认情况下处于启用状态。如果你正在使用Spring MVC的<form:form>标签、或Thymeleaf,并且添加了注解@EnableWebSecurity,则CSRF标记将作为隐藏输入字段自动添加。
如果你正在使用像Angular、或React这样的JavaScript框架,则需要配置CookieCsrfTokenRepository,以便JavaScript可以读取cookie。
1 2 3 4 5 6 7 8 9 10 |
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); } } |
如果你使用的是Angular,这样设置就够了。如果你使用的是React,则需要从cookie中,读取XSRF-TOKEN的值,并作为标头X-XSRF-TOKEN发回。
当请求是HTTPS协议时,Spring Security会自动为XSRF-TOKEN cookie添加安全标志。Spring Security不对CSRF cookie使用SameSite = strict标志,但在使用Spring Session或WebFlux会话会这样处理。
会话cookie对于识别用户是有意义,但它对CSRF cookie没有多少用处,因为CSRF令牌,也需要存放在请求中。
5. 使用内容安全策略来防止XSS攻击
内容安全策略(Content Security Policy,简称“CSP”),是一个增加的安全层,可帮助缓解XSS(跨站点脚本)和数据注入攻击。
要启用它,你需要配置应用程序,以返回Content-Security-Policy标头。你还可以在HTML页面中,使用<meta http-equiv =”Content-Security-Policy”>标记。
Spring Security默认提供了许多安全标头:
1 2 3 4 5 6 7 |
Cache-Control: no-cache, no-store, max-age=0, must-revalidate Pragma: no-cache Expires: 0 X-Content-Type-Options: nosniff Strict-Transport-Security: max-age=31536000 ; includeSubDomains X-Frame-Options: DENY X-XSS-Protection: 1; mode=block |
Spring Security默认不添加CSP。你可以使用以下配置在Spring Boot应用程序中启用CSP标头。
1 2 3 4 5 6 7 8 9 |
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.headers() .contentSecurityPolicy("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/"); } } |
CSP是防止XSS攻击的良好防御。请记住,打开CSP以允许CDN,这也意味着允许访问许多非常古老、且易受攻击的JavaScript库。这意味着使用CDN,通常对应用程序的安全性,没有多大帮助。
你可以使用securityheaders.com,测试你的CSP标头是否与实际是一致的。
6. 使用OpenID Connect进行身份验证
OAuth 2.0是行业标准的授权协议。它使用范围来定义授权用户可以执行的操作的权限。但是,OAuth 2.0不是身份验证协议,并且不提供有关经过身份验证的用户的信息。
OpenID Connect(OIDC)是一个OAuth 2.0扩展,提供用户信息。除了访问令牌之外,它还添加了ID令牌,以及可以从中获取其他信息的/userinfo端点。它还添加了端点发现功能和动态客户端注册。
下图显示了OIDC如何用于身份验证。
如果使用OIDC进行身份验证,则无需担心存储用户、密码或对用户进行身份验证。相反,你将使用身份提供商(IDP)为你执行此操作。你的IDP甚至可能提供多因素身份验证(MFA)等安全附加组件。
要了解如何在Spring Boot应用程序中使用OIDC,请参阅Spring Security 5.0和OIDC入门。要概括如何使用它,你首先需要向项目添加一些依赖项,然后在application.yml文件中配置一些属性。
1 2 3 4 5 6 7 8 9 10 11 12 |
spring: security: oauth2: client: registration: okta: client-id: {clientId} client-secret: {clientSecret} scope: openid email profile provider: okta: issuer-uri: https://{yourOktaDomain}/oauth2/default |
使用issuer-uri仅在Spring Security 5.1中支持,Spring Security 5.1正在积极开发中并计划于2018年9月发布。
你可以使用像Keycloak这样的开源系统来设置自己的OIDC服务器。如果你不想在生产中维护自己的服务器,可以使用Okta的Developer API。立即注册免费帐户,每月在developer.okta.com/signup上获得1000名活跃用户!
如果你想使用OAuth 2.0,OIDC及其允许的不同流程,请参阅https://www.oauth.com/playground。此站点不需要你创建帐户,但它确实使用了Okta的开发人员API。
7. 管理密码? 使用密码哈希!
以纯文本格式存储密码是你可以为应用程序的安全性做的最糟糕的事情之一。幸运的是,Spring Security默认情况下不允许使用纯文本密码。它还附带了一个加密模块,可用于对称加密、密钥生成和密码散列(也就是密码编码)。
PasswordEncoder是Spring Security中密码散列的主要接口,如下所示:
1 2 3 4 |
public interface PasswordEncoder { String encode(String rawPassword); boolean matches(String rawPassword, String encodedPassword); } |
Spring Security提供了几种实现,最常用的是BCryptPasswordEncoder和Pbkdf2PasswordEncoder。
对于一般的密码管理,我们建议使用SCrypt或Argon2。SCrypt现在已经过时了(已经有一段时间了),并且有一个BCrypt没有的额外的复杂因素,这使得暴力成倍地变得更加困难或者昂贵。
它由著名的密码学家/安全人员(Colin Percival)编写,并且在几乎所有编程语言中都有很好的库。SCrypt也得到Latacora的认可。
来自Okta Developer Relations团队的密码专家Randall Degges称:Argon2相对较新(现在已有几年历史),但已经过广泛的审核,并且是许多组织在几年内参与的加密哈希挑战的结果。毫无疑问,它们的“最强”散列算法都增加了scrypt没有的另一层复杂性,这使得与scrypt相比它被暴力破解的难度成倍地增加。Argon2非常棒,我在几种语言中使用它取得了巨大的成功,但如果你担心过于尖锐的scrypt是一个安全的赌注,而不是有争议的。
来自Spring Security负责人Rob Winch认为:我喜欢BCrypt,但一般的建议是单向自适应哈希。某些用户出于合规性原因可能需要使用PBKDF2。有一个为Argon2支持记录的票证,但是我找不到任何Apache 2协议开源的原生Java实现。相反,这些库依赖于他们委托的二进制文件,从我的角度来看,这是不理想的。我们处于到底是考虑备选还是最大化利用二进制委托实现的选择矛盾之中。
对于那些想要使用SCrypt的人,可以通过SCryptPasswordEncoder中的Bouncy Castle在Spring Security中获得支持。Spring Security 5.1(即2018年9月下旬)将附带UserDetailsPasswordService API,允许你升级密码存储。
8. 安全地存储秘密(Secrets)
应谨慎处理敏感信息,如密码、访问令牌等。你不能将它们随意的以纯文本形式传递,或者为了被访问而将它们保存在本地存储中。由于GitHub的历史已经一次又一次证明,开发人员并没有仔细考虑如何存储他们的秘密。
当然,你能够也应该加密你的敏感数据,例如密码。现在你的密码是安全的,你有一个新的秘密:你的解密密钥!你对这个新秘密有什么看法? 也许存放在本地? 也许在另一个地方,你认为攻击者很难找到它。这不能解决问题:它只是暂时被推迟了。如果没有适当的流程,你只是让黑客稍微难以解开你的秘密而已。
一个好的做法是将保密信息存储在保管库中,该保管库可用于存储,还提供对应用程序可能使用的服务的访问权限,甚至生成凭据。HashiCorp的Vault使得存储机密变得轻而易举,并提供了许多额外的服务。
可以配置保险柜,以便没有人可以访问所有数据,不提供单点控制。根密钥Vault定期被更改,仅存储在内存中。它有一个主开关,当被触发时会密封你的保管库,如果出现问题就阻止它共享秘密。Vault使用分配给可以限定特定用户,服务或应用程序的策略的令牌。你还可以与LDAP等常用身份验证机制集成以获取令牌。
除了没有问题的“黄金路径视图”(golden-path view)外,Vault还可以提供应用在被黑客入侵时的备选方案。此时,撤销单个或多个秘密很重要,可能是由特定用户或特定类型。Vault提供了一种自动化的方式,可以在时机成熟时快速完成。
如果你对此感兴趣,请务必花一些时间查看Spring Vault,它为HashiCorp Vault添加抽象,为客户端提供基于Spring注解方式,允许访问、存储和撤销机密而不会迷失在底层架构中。以下代码段显示了使用注释从Spring Vault中提取密码的难易程度。
1 2 3 |
<span class="">@Value</span>(<span class="">"${password}"</span>) <span class="">String</span> password; |
9. 使用OWASP的ZAP测试你的应用程序
OWASP ZAP安全工具是一个代理,可在运行时对你的实时应用程序执行渗透测试。它托管在GitHub上,是一个受欢迎的(超过4K星)免费的开源项目。
OWASP ZAP用于查找漏洞的两种方法是Spider和Active Scan。Spider工具以URL种子开头,它将访问并解析每个响应,识别超链接并将它们添加到列表中。
然后,它将这些新找到的URL并以递归方式继续遍历,为你的Web应用程序创建URL映射。Active Scan工具将根据潜在漏洞列表自动测试你选择的目标。它为你提供了一个报告,显示你的Web应用程序可被利用的地方,以及有关漏洞的详细信息。
https://developer.okta.com/blog/2018/07/30/10-ways-to-secure-spring-boot