构建身份管理,包括身份验证和授权?试试风暴之路!我们的 REST API 和强大的 Java SDK 支持可以消除您的安全风险,并且可以在几分钟内实施。 注册,再也不会构建身份验证了!
OAuth 2.0 令牌管理经常被误解并且难以正确实施。幸运的是,借助 Stormpath 的 SDK 和集成,我们使令牌管理变得简单 - 甚至有趣。这个 20 分钟的教程将向您展示如何使用 Stormpath 的 Spring Boot 和 Spring Security 集成来实施令牌管理。
虽然 Spring Security 确实内置了 OAuth 2.0 支持,但 Spring Boot 中没有本机令牌管理支持,并且已知使用 OAuth 协议会导致荨麻疹、冷汗和长时间的自发爆发“前台服务。”
Stormpath 的 Spring Boot 集成支持两个 OAuth 流程:grant_type=password
和 grant_type=refresh_token
。密码授予类型允许您传入用户名和密码并取回访问令牌和刷新令牌。刷新令牌授予类型允许您传入刷新令牌并取回新的访问令牌。
它们都通过单个端点 /oauth/token
访问,该端点与 Stormpath 集成开箱即用。所以,不要害怕! Stormpath 的令牌管理完成了所有繁重的工作。您只需使用下面讨论的一些基本规则来创建 HTTP 请求。
到本文结束时,您将拥有将用户名和密码交换为一组令牌所需的一切,这些令牌允许用户访问应用程序中的受限资源。您还可以刷新和撤销令牌,以更好地控制用户访问您的应用程序的方式以及他们保持登录状态的时间。
支持这篇文章的代码在这里。
您可以在以下网址查看示例的运行情况:https://jquery-spa.herokuapp.com(由于 Heroku sleep 政策,您可能需要等待几秒钟以便应用程序最初响应),您可以使用下面的按钮立即将其部署到您的 Heroku 帐户。
虽然令牌管理通常是幕后事务,但此示例使用 SPA 或单页应用程序。为了保持示例简单(并且与 Javascript 框架无关),所有调用都是使用 jQuery 的 ajax 功能进行的。
在支持 OAuth 2.0 令牌管理的现代应用程序中,用户会话通常有一个过期较短的访问令牌和一个过期较长的刷新令牌。当Access Token过期后,应用程序会使用Refresh Token获取新的Access Token。重复此过程,直到刷新令牌到期。此时,用户需要再次登录应用程序。
要深入了解 OAuth 令牌及其工作原理,请查看我同事 Randall 的详细探讨 OAuth 的帖子。
OAuth 2.0 规范没有指定特定的令牌格式,因此 Stormpath 使用 JWT 来表示访问令牌和刷新令牌。 JWT 将附加信息编码到其中,更重要的是,它们经过加密签名以提供令牌未被篡改的确凿证据。
Stormpath 访问令牌还提供了一个重要的附加安全层——它们始终包含对关联刷新令牌的引用。此引用是由代码 rti
标识的声明。这是 Stormpath 确保它是用于访问受保护资源的访问令牌而不是刷新令牌的方法之一。 OAuth 2.0 的其他实现(无意中)允许刷新令牌充当访问令牌,从而为用户提供比他们应该拥有的更长的访问权限。我们提防那个。
如果您想详细了解 JWT 以及如何安全地使用它们,请查看我们关于如何正确使用 JWT、使用 JWT 构建安全用户界面 以及在哪里存储您的 JWT。
好的,所有这些都已完成,让我们开始吧!
在接下来的部分中,我们将通过对 Spring Boot 应用程序中的后端 /oauth/token
端点进行的 jQuery 调用来探索示例应用程序。
在我们开始之前,让我们看一下这个示例应用程序的 Spring Security 配置:
@Configuration public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.apply(stormpath()).and() .authorizeRequests() .antMatchers("/").permitAll() .antMatchers("/decode").permitAll() .antMatchers("/static/**").permitAll(); } }
Stormpath 的 Spring Security 集成遵循 Spring Security 的默认访问配置,即所有路径都被锁定。
在上述配置中,我们允许未经身份验证访问 /
和 /decode
以及 /static
中的任何路径。我们将在下面的示例中看到这一点。
在我们的 SPA 示例中有两个主要的静态文件:home.html
和 static/js/token-management-guided-tour.js
。
home.html
是一个 Thymeleaf 模板,其中加载了 static/js/token-management-guided-tour.js
Javascript 文件。
下面是 home.html
不同部分的视图:
它分为 7 个步骤,应用程序将引导您完成示例,“导游”风格。
第一步是传递电子邮件和密码并取回访问和刷新令牌。让我们看看实际效果。
下面是处理对 /oauth/token
端点的初始请求的 jQuery 代码(为简洁起见省略了 error
和 complete
函数):
$('#login-form').submit(function (e) { var theForm = $('#login-form'); $.ajax({ type: 'post', url: '/oauth/token', data: theForm.serialize(), success: function (data) { accessToken = data.access_token; refreshToken = data.refresh_token; var accessTokenParts = accessToken.split('.'); $.each(['header', 'payload', 'signature'], function (index, value) { $('#access-token-' + value).html(accessTokenParts[index]); }); $('#login-error').hide(); showOnlyDiv('access-token-div'); } }); e.preventDefault(); });
第 3 行为我们提供了表单的句柄。
根据 OAuth 规范,方法将是 POST
而 Content-Type
是 application/x-www-form-urlencoded
(这是 jQuery 的默认设置$.ajax
调用)。传递给 /oauth/token
的数据将如下所示:
grant_type=password&username=<username>&password=<password>
这是在第 8 行调用 theForm.serialize()
的结果。
从第 9 行开始的成功处理程序将 access_token
和 refresh_token
存储在局部变量中。它还在您的浏览器中显示 access_token
的部分(标头、有效负载和签名)作为 JWT 的可视化表示:
注意:如屏幕截图所示,示例应用程序仅用于演示目的。在“现实生活”中,您不希望在浏览器中显示访问令牌,并且希望使用最佳实践在客户端应用程序中存储令牌。在此处,我们有一篇关于这些最佳实践的精彩帖子。
在示例应用程序中,如果您单击 Decode
按钮,您将看到如下内容:
这向您显示了作为访问令牌的 JWT 的标头和有效负载部分。要了解我们如何解码访问令牌,我们需要查看示例应用程序中 decode
中的 APIController
方法:
@RequestMapping("/decode") public Jws<Claims> decode(@RequestParam String token) throws UnsupportedEncodingException { Jws<Claims> claims = Jwts.parser() .setSigningKey(client.getApiKey().getSecret().getBytes("UTF-8")) .parseClaimsJws(token); return claims; }
该方法将 JWT(在本例中是我们的访问令牌)作为参数,并使用 JJWT 库来解析声明。它还验证 JWT 签名作为解析过程的一部分是否有效。为此,它使用了最初用于创建 JWT 的 Stormpath 客户端的秘密。
最后,该方法返回 Jws<Claims>
对象。这是 Spring Boot 的自动 Jackson JSON 映射器启动并将该对象转换为 JSON 的地方。这是原始响应的样子:
{ "header": { "kid": "R92SBHJC1U4DAIMGQ3MHOGVMX", "alg": "HS256" }, "body": { "jti": "6UBPQ975cDDiz8ckHqWIZF", "iat": 1456242057, "iss": "https://api.stormpath.com/v1/applications/2nBCvauLgETX8wO0VvS9mQ", "sub": "https://api.stormpath.com/v1/accounts/49CK1VvY2jQwUBH7UnP5zC", "exp": 1456245657, "rti": "6UBPQ5n0hcukMJWt5af1xB" }, "signature": "Pm30FjdXOmx_fMGhfku85Z9xc6qE-EZgKHI4mV46KO8" }
请注意,解码后的 JWT 的 body
(有效负载)在 sub
(主题)声明中包含对 Stormpath 帐户的引用。正如我们接下来将看到的,访问受限资源时使用的正是这个帐户。
解码访问令牌后,我们的示例应用程序导览中的下一步是使用该访问令牌访问受限资源。下面是实现该功能的 jQuery 代码:
$('#restricted').click(function () { $('#account-info-table tbody').empty(); $.ajax({ type: 'get', url: '/restricted', success: function (data) { var newRowContent = '<tr><td>' + data.fullName + '</td><td>' + data.email + '</td></tr>'; $('#account-info-table tbody').append(newRowContent); showOnlyDiv('account-info-div'); } }) });
您可能想知道上述代码中的身份验证在哪里。当您登录时,在上述步骤中在浏览器中设置了 Cookie。如果您查看浏览器中的检查器,您会看到 access_token
被传递到 /restricted
的请求 端点。在单独的浏览器会话中,您可以尝试点击:http://localhost:8080/restricted
。您将被重定向到 /login
。这是我们的 Spring Security 配置的一个功能。 /restricted
路径不是我们在 Spring Security 配置中明确允许的路径之一,因此,请求必须经过身份验证。要了解 /restricted
的响应,让我们看一下 restricted
中的 APIController
方法:
@RequestMapping("/restricted") public AccountInfo restricted(HttpServletRequest req) { Account account = AccountResolver.INSTANCE.getAccount(req); return new AccountInfo(account.getFullName(), account.getEmail()); }
这只是返回一个 AccountInfo
模型对象,其中包含经过身份验证的用户的全名和电子邮件地址。示例应用程序中定义的 AccountInfo
对象可以通过我们在上面看到的相同自动 Spring Boot 映射器过程轻松转换为 JSON。
上面的 jQuery success
函数显示从请求返回到 /restricted
的帐户信息。
我们旅程的下一步是使用刷新令牌获取新的访问令牌。在典型的移动应用程序中,以下情况会在用户不知情的情况下在幕后发生:
401
(未经授权的)HTTP响应代码注意:如果 Refresh Token 已过期,则应用程序的用户需要在第 4 步重新登录
Stormpath 在管理控制台中提供了一个 OAuth 策略来设置访问令牌和刷新令牌的过期时间。
以下是 OAuth 策略在 Stormpath 管理控制台中的样子:
访问令牌的默认生存时间为 1 小时,刷新令牌的默认 TTL 为 60 天。
当您在示例应用程序中单击 Refresh the Access Token
按钮时,您会收到如下所示的响应:
这将显示在刷新过程中检索到的新访问令牌。为了了解那里发生了什么,我们回到我们的 jQuery 代码(为简洁起见省略了成功处理程序):
$('#refresh').click(function () { $.ajax({ type: 'post', url: '/oauth/token', data: 'grant_type=refresh_token&refresh_token=' + refreshToken, success: function (data) { } }) });
我们再次向 POST
端点发送 HTTP /oauth/token
。与 grant_type=password
流程不同,在本例中,我们使用 grant_type=refresh_token
并传入之前保存的刷新令牌。
这个 ajax 调用的响应是一个新的访问令牌,然后它显示在旧访问令牌下面的浏览器中。
我们的令牌管理之旅的最后一部分是撤销访问和刷新令牌。这是通过点击(内置)/logout
端点来实现的,该端点提供访问令牌作为 Bearer 令牌,就像我们之前点击受限资源时所做的那样。让我们看一下完成此操作的 jQuery 代码:
$('#revoke').click(function () { $.ajax({ type: 'post', url: '/logout', success: function () { showOnlyDiv('revoke-div'); } }) });
我们正在向 POST
发送 HTTP /logout
请求。此时删除包含 access_token
和 refresh_token
的 cookie,并在 Stormpath 后端撤销令牌。这是在幕后自动处理的。
示例应用程序再次尝试访问 /restricted
路径。结果如下所示:
结果表明,尝试使用已删除的访问令牌将不起作用。
Stormpath 的 Spring Boot 集成中开箱即用的 /oauth/token
端点支持现代令牌管理系统的所有功能。
在这篇文章中,我们介绍了什么是访问令牌和刷新令牌,以及如何获取、使用、刷新和撤销它们。
您可以在以下网址查看整个帖子中使用的示例应用程序:https://jquery-spa.herokuapp.com 或者,您可以使用下面的按钮将应用程序部署到您自己的 Heroku 帐户。
构建身份管理,包括身份验证和授权?试试风暴之路!我们的 REST API 和强大的 Java SDK 支持可以消除您的安全风险,并且可以在几分钟内实施。 注册,再也不会构建身份验证了!
标签2: Java教程地址:https://www.cundage.com/article/jcg-oauth-2-0-token-management-stormpath-spring-boot.html