在本文中,我们将从鸟瞰的角度了解 Spring Security 的用途以及它能为我们提供什么。网络上的任何东西都可能是攻击的潜在受害者。不幸的是,在这个即使是最富有、最具创新性的技术公司也会受到黑客攻击的世界里,保护 Web 应用程序并实现授权和身份验证等功能并非易事。因此,当我们的老板给我们一个保护应用程序的任务时,我们应该坐等,害怕,不知道该做什么吗?当然不是。
无论您是否要使用 Auth0 来保护您的 Spring 应用程序,您都需要了解 Spring Security 的基础知识,以便快速保护您的应用程序,这使得它成为任何 Spring 开发人员都必须了解的框架。Spring Security 的问题在于:它很难。不是因为它设计得不好或可能更容易使用,而是因为它的领域很复杂:应用程序安全。复杂的问题需要技术复杂的解决方案,而安全就是其中之一。
如果你曾经看过 Spring Security 相关的文档或教程,你可能会害怕它看起来有多复杂。我的意思是,看看维基百科上的这张图片:
感到不知所措是很自然的,特别是如果你也是 Spring 的新手。Spring Security 是一个庞大的框架,但一旦你掌握了基础知识,你就可以通过将其与 Auth0 等 IDaaS 服务集成来轻松完成保护应用程序的任务,而无需自己实现一切。
为什么是 Spring?
Spring 是开发 Web 服务最流行的 Java 框架。无论是企业项目还是初创企业,Spring 都拥有所有工具来帮助您快速开发并专注于应用程序逻辑。
- 非常可靠和成熟的平台(自 2003 年起),拥有庞大的生态系统。这意味着有大量现成的库和项目可以让您的生活更轻松。
- 几乎所有科技巨头都广泛使用它,例如亚马逊、谷歌和 Netflix。
- 它速度快且多线程(与 Node.js 不同),因此非常适合 CPU 密集型任务,例如视频编码、图像处理、财务计算。
- 让您通过 Spring Boot 的帮助以极少的配置专注于业务逻辑。
- 支持 Kotlin。
- JPA 会为您生成大多数基本查询。只有特殊情况,您才需要自己编写查询。
- 使用嵌入式 Web 服务器和 Spring Cloud 轻松开发微服务。
- 安全。Spring 通常用于银行和其他安全关键型应用程序,部分原因在于 Spring Security。
什么是 Spring Security?
Spring Security 是一个专注于为 Spring 应用程序提供身份验证和授权机制的框架。它于 2003 年作为一个名为“Acegi Security”的开源项目启动,之后正式被纳入Spring 项目。除了身份验证和授权之外,还可以配置 Spring Security 以保护您的应用程序免受许多常见攻击,包括但不限于 CSRF、XSS、暴力破解和 MITM(中间人),如果您想了解有关这些攻击的更多信息,您可以查看Holly Lloyd 的这篇文章。
为什么选择 Spring Security?
如果您没有保护应用程序的经验,那么最好的办法之一就是查明您使用的语言/平台是否具有安全框架。通过使用可靠的安全框架,我们将确定架构和实现核心安全功能的责任委托给在该特定框架上工作的该领域专家团队。如果您正在构建 Spring 应用程序,Spring Security 是一个可靠、经过广泛测试且开源的安全框架,它可能是所有语言和平台中最可靠的安全框架之一。
Spring Security主要特征
在本节中,我们将重点介绍 Spring Security 的功能以及它可以为我们提供什么或其用例。同时,我们还简要提到了“如何”这样做。
1. 密码编码
Spring Security 的好处不仅限于帮助我们进行身份验证和授权。它还可以帮助我们应用最佳实践来保存用户,即构建注册功能。
在构建注册功能的过程中,一些常见的错误可能会导致严重的安全问题。
其中一个错误是,以纯文本形式保存用户密码而不进行任何形式的哈希处理。这不仅允许可能访问您服务器的黑客直接查看密码,而且还会使您的用户面临其他帐户被破解的威胁,因为大多数人对所有帐户都使用相同的密码。
另一个常见的错误是尝试重新发明轮子并创建自己的加密/散列算法,而不是使用众所周知的、经过测试的、经过验证的算法。
Spring Security 允许我们为 UserDetails 对象分配一个安全密码编码器,以防止这些错误。默认情况下,它使用 BCrypt 来加密密码,这被认为是一种全面的密码编码算法。还可以设置散列轮数(或参数名称所暗示的强度)以及在此过程中要使用的安全随机算法实现。
java">BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion version, int strength, java.security.SecureRandom random)
在扩展 WebSecurityConfigurerAdapter 的配置类中遇到这样的代码片段并不奇怪:
java">@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {return new BCryptPasswordEncoder();
}@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
2. 内存身份验证
内存身份验证意味着使用驻留在应用程序内存/RAM 中的数据库(一个例子是 h2 数据库)来保存用户并执行身份验证,而无需将其保存到持久数据库中。当您构建概念验证应用程序或为您的应用程序制作原型并且需要开始或测试您的身份验证代码而不必担心数据库时,此类数据库非常有用。如果您在没有模拟的情况下运行测试,内存数据库也很有用,这样您的真实数据库将保持不变,并且测试引起的更改将保留在临时内存中。
Spring Security 支持驻留在内存数据库中的用户身份验证。
java">@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {// Some other configuration code here...@Autowiredpublic void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("temporary").password("temp123").roles("ADMIN");}
}
3. LDAP 身份验证
LDAP 是轻量级目录访问协议的缩写,通常用于验证企业员工的用户帐户。它允许您在层次结构中指定用户/用户组并定义他们的权限。
dn: cn=John Doe,dc=example,dc=com
cn: John Doe
givenName: John
sn: Doe
telephoneNumber: +1 888 555 6789
telephoneNumber: +1 888 555 1232
mail: john@example.com
manager: cn=Barbara Doe,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
Spring Security 有一个名为“spring-security-ldap”的项目,允许我们在 Spring 应用程序中使用 LDAP 身份验证。Spring Boot 还为嵌入式 LDAP 服务器提供自动配置,从而免去了设置 LDAP 身份验证服务器的艰巨任务。您可以查看此官方教程以获取更多信息。
4. 会话管理
在后端和前端紧密耦合的应用程序中,例如 Spring MVC 应用程序或任何其他 MVC 框架:每当用户登录时,服务器中都会存储一个会话,其中包含有关此用户的信息。检索、缓存、销毁此会话等操作称为会话管理。Spring Security 提供了控制此会话对象的机制。它将在用户登录时创建会话,在用户注销时销毁会话,并允许我们设置超时值。
Spring Security 还采取了额外措施来确保以安全的方式使用会话:
- 可以配置禁用 URL 重写来避免会话跟踪攻击。
- 当用户再次登录时,它会自动迁移会话以避免会话固定。
- 它允许我们在会话 cookie 上使用 httpOnly 和安全标志来保护我们的 cookie。
5. 记住我身份验证
记住我身份验证是在会话超时后识别用户的机制,因此用户无需在每次访问网站时都输入其凭据。Spring Security 支持多种方式来实现这种类型的身份验证。
实现“记住我”身份验证的典型方法是使用服务器上的密钥对用户详细信息进行散列,并将其与用户名和到期时间一起编码。
base64(username + ":" + expirationTime + ":" +md5Hex(username + ":" + expirationTime + ":" password + ":" + key))username: As identifiable to the UserDetailsServicepassword: That matches the one in the retrieved UserDetailsexpirationTime: The date and time when the remember-me token expires,expressed in millisecondskey: A private key to prevent modification of the remember-me token
虽然这种方法有效,但也存在一些缺陷。如果攻击者以某种方式捕获了 cookie,则任何人都可以通过任何来源使用该 cookie,直到其过期为止。用户使令牌失效的唯一方法是更改密码。
实现“记住我”身份验证的更好方法是将持久令牌存储在数据库中,而不是“记住我”cookie。令牌的存储方式与会话对象类似,但不同之处在于它保存到数据库中,而不是临时应用程序内存中。
6. 使用 JWT(JSON Web 令牌)实现 API 安全
我们已经提到,服务器端会话可用于确认用户身份并跟踪其角色(允许他们做什么)。由于 REST 是一种无会话协议,因此建议不要在 RESTful API 中存储会话。我们可以改用另一种流行的方法来检查用户是否有权执行某些操作。
在使用 JWT 的应用中,用户登录成功后,服务端会使用秘钥生成一个访问令牌,该令牌通常包含用户身份信息和令牌生成时间戳。在前后端分离的架构中,前端会将该令牌存储在 cookie 中。
可以使用Auth0 JWT库对令牌进行编码/解码,从而为您的应用程序实现 JWT 授权。除此之外,Spring Security 还可用于过滤请求并检查用户的角色,仅允许授权用户通过过滤器。
7. OAuth 2.0
Open Authorization 2.0 是一个开放标准,用于在授权服务器的帮助下检查用户对服务的权限。使用 OpenID Connect 确认用户身份并不罕见,然后 OAuth 可以提供包含此用户权限列表的令牌。用户收到 OAuth 2 访问令牌后,他们将使用它来访问服务器上受保护的资源。OAuth 是一个非常流行的标准,它也用于实现 Facebook 登录或 Google 帐户登录等功能,因此即使您没有意识到它是什么,您也可能曾经在不知情的情况下使用过它。
设置授权服务器并实施 OAuth 和 OpenID Connect 本身就存在风险,而且可能非常耗时。很容易出错并错误地造成漏洞。
与其尝试从头开始构建一切,不如将 Spring Security 与 Auth0 集成,将宝贵的时间花在改进业务逻辑上。您可以查看 Tadej 编写的这篇精彩教程,了解如何在 Auth0 的帮助下保护您的 RESTful API,以快速保护您的 API。
Spring Security 的构建模块
在本节中,我们将介绍 Spring Security 中大量应用的几个概念。关于这些主题的基本知识将作为我们的基础,因为所有这些主题或多或少都是相互联系的。
1. 责任链
责任链是 GoF 的行为设计模式,旨在减少请求处理程序和发送者之间的紧密耦合。“链”指的是有序的请求处理程序列表,就像一个LinkedList结构,请求处理程序可以是方法或实现某个Handler接口的类。责任只是回答请求的一个花哨的词,链上的每个处理程序都可以返回响应(通过承担责任)或将请求传递给链上的下一个处理程序(委托责任)。
2. 筛选器
在开始讨论过滤器之前,熟悉拦截器会很有帮助:拦截器是一个实现接口HandlerInterceptor并在 Web 请求到达控制器之前对其进行拦截的类。它的名称非常清楚地描述了它的实际功能,过滤器可以被认为是拦截器的更专业版本。
Filter 方法在请求到达 Tomcat(或任何其他)Web 服务器之后、到达 servlet 之前运行。这使得 Filter 甚至可以阻止请求到达实际的 servlet,这也解释了它们被称为 Filter 的原因。Spring FilterSecurity 中有许多接口可以实现,以创建自定义过滤器类。然后,我们可以告诉 Spring Security 将我们创建的这些 Filter 添加到其“过滤器链”中。
如果您已经想知道这个“过滤器链”是否与我们的职责链模式有关,那么恭喜您!这就是我们开始学习职责链模式的原因。在我们添加单个自定义过滤器之前,Spring Security 通常默认有大约六个过滤器链(源),并且只要我们将 Spring Security 添加为依赖项,它们就会开始过滤掉请求。
以下是 JWT 授权过滤器的示例代码:
java">public class JWTAuthorizationFilter extends BasicAuthenticationFilter {public JWTAuthorizationFilter(AuthenticationManager authManager) {super(authManager);}@Overrideprotected void doFilterInternal(HttpServletRequest req,HttpServletResponse res,FilterChain chain) throws IOException,ServletException
{String header = req.getHeader(HEADER_STRING);if (header == null || !header.startsWith(TOKEN_PREFIX)) {chain.doFilter(req, res);return;}UsernamePasswordAuthenticationToken authentication = getAuthentication(req);SecurityContextHolder.getContext().setAuthentication(authentication);chain.doFilter(req, res);}
}
您不需要理解所有这些,只需浏览一下此方法即可。它以 a作为参数,其作用是:从请求中获取标头,检查它是否具有令牌,如果没有,则通过调用将其传递到过滤器链:doFilterInternal() FilterChain
java">chain.doFilter(req, res);
这是一个你经常会遇到的模式。请注意,此方法的最后一行也是一样的。
3. 匹配器
您可能会问:“过滤器很不错,但是一旦我将它们添加到我的安全配置中,它们就会应用于每个请求,如果我只想将过滤器应用于单个 REST 资源怎么办?” 这时 URL 匹配器就应该派上用场了。Spring Security 中的 URL 匹配器称为 Ant 匹配器,历史上以 Apache Ant 构建系统命名,它们允许我们指定类似正则表达式的匹配器来确定哪些端点应该接受过滤。
示例配置:
java"> @Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers(HttpMethod.GET, "/public/**").permitAll().anyRequest().authenticated().and().addFilter(new CustomFilter(authenticationManager()).addFilter(new JWTAuthorizationFilter(authenticationManager()))}
此代码片段允许所有以“/public/”开头的 URL 的 GET 请求绕过过滤器。对于任何其他请求,API 使用者都应经过身份验证,并且自定义过滤器也将适用。可以在 Configuration 类中找到类似的代码。
4. 用户角色(基于角色的授权)
现在我们了解了 Ant Matchers,我们可以指定过滤器将应用到的路径,但我们仍然缺乏定义特定于角色的权限的灵活性。例如,我们可能希望只有具有角色 ADMIN 或任意角色集的用户才能访问端点。通过使用基于角色的授权/身份验证,我们可以实现这种行为。我不会提供实现基于角色的安全性的完整代码或教程,因为这本身就是一篇全新文章的主题,但您应该知道 Spring Security 允许您为用户定义角色并根据这些角色应用过滤器,如下所示:
java"> @Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/").permitAll.antMatchers("/new-blog-post").hasAnyAuthority("ADMIN", "AUTH0 EMPLOYEE", "GUEST_WRITER").antMatchers("/edit/**").hasAnyAuthority("ADMIN", "EDITOR").antMatchers("/delete/**").hasAuthority("ADMIN").and().formLogin().permitAll().and().logout().permitAll();}
如果使用 Spring Security 构建,这可能是 Auth0 博客权限的配置。
结论
在本文中,我们首先定义了 Spring Security,并尝试提供有关安全框架提供哪些功能的见解。我希望在阅读功能部分并查看 Spring Security 的示例用例后,会更加清楚。
在了解了它的用途之后,我们了解了一些可以帮助我们更好地理解 Spring Security 的重要概念。
将来,我们还计划在此介绍中添加更多高级教程,例如:
- 更详细的 Spring Security 教程
- Spring Boot 身份验证教程:Web 应用程序
- 保护 Spring 微服务
- 安全的 Spring Serverless 开发