使用 Stormpath 和 Spring Boot 进行 OAuth 2.0 令牌管理

位置:首页>文章>详情   分类: Java教程 > 编程技术   阅读(304)   2023-10-19 16:34:57

构建身份管理,包括身份验证和授权?试试风暴之路!我们的 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=passwordgrant_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 的帖子

JSON Web 令牌 (JWT) 安全 PSA

OAuth 2.0 规范没有指定特定的令牌格式,因此 Stormpath 使用 JWT 来表示访问令牌和刷新令牌。 JWT 将附加信息编码到其中,更重要的是,它们经过加密签名以提供令牌未被篡改的确凿证据。

Stormpath 访问令牌还提供了一个重要的附加安全层——它们始终包含对关联刷新令牌的引用。此引用是由代码 rti 标识的声明。这是 Stormpath 确保它是用于访问受保护资源的访问令牌而不是刷新令牌的方法之一。 OAuth 2.0 的其他实现(无意中)允许刷新令牌充当访问令牌,从而为用户提供比他们应该拥有的更长的访问权限。我们提防那个。

如果您想详细了解 JWT 以及如何安全地使用它们,请查看我们关于如何正确使用 JWT使用 JWT 构建安全用户界面 以及在哪里存储您的 JWT

好的,所有这些都已完成,让我们开始吧!

Spring安全配置

在接下来的部分中,我们将通过对 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.htmlstatic/js/token-management-guided-tour.js

home.html 是一个 Thymeleaf 模板,其中加载了 static/js/token-management-guided-tour.jsJavascript 文件。

下面是 home.html 不同部分的视图:

它分为 7 个步骤,应用程序将引导您完成示例,“导游”风格。

第一步是传递电子邮件和密码并取回访问和刷新令牌。让我们看看实际效果。

使用电子邮件和密码登录

下面是处理对 /oauth/token 端点的初始请求的 jQuery 代码(为简洁起见省略了 errorcomplete 函数):

$('#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 规范,方法将是 POSTContent-Typeapplication/x-www-form-urlencoded(这是 jQuery 的默认设置$.ajax调用)。传递给 /oauth/token 的数据将如下所示:

grant_type=password&username=<username>&password=<password>

这是在第 8 行调用 theForm.serialize() 的结果。

从第 9 行开始的成功处理程序将 access_tokenrefresh_token 存储在局部变量中。它还在您的浏览器中显示 access_token 的部分(标头、有效负载和签名)作为 JWT 的可视化表示:

注意:如屏幕截图所示,示例应用程序仅用于演示目的。在“现实生活”中,您不希望在浏览器中显示访问令牌,并且希望使用最佳实践在客户端应用程序中存储令牌。在此处,我们有一篇关于这些最佳实践的精彩帖子。

Stormpath 访问令牌剖析

在示例应用程序中,如果您单击 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 的帐户信息。

代币管理:令人耳目一新!

我们旅程的下一步是使用刷新令牌获取新的访问令牌。在典型的移动应用程序中,以下情况会在用户不知情的情况下在幕后发生:

  1. 当前访问令牌过期
  2. 用户尝试使用(过期的)访问令牌访问应用程序的受限部分
  3. 应用程序收到一个401(未经授权的)HTTP响应代码
  4. 应用程序使用刷新令牌获取新的访问令牌
  5. 应用程序尝试使用新的访问令牌访问应用程序的受限部分
  6. 用户无需再次登录即可看到该请求的结果

注意:如果 Refresh Token 已过期,则应用程序的用户需要在第 4 步重新登录

Stormpath 在管理控制台中提供了一个 OAuth 策略来设置访问令牌和刷新令牌的过期时间。

查看 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_tokenrefresh_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

相关阅读

Java HashSet 教程展示了如何使用 Java HashSet 集合。 Java哈希集 HashSet 是一个不包含重复元素的集合。此类为基本操作(添加、删除、包含和大小)提供恒定时间性...
SpringApplicationBuilder 教程展示了如何使用 SpringApplicationBuilder 创建一个简单的 Spring Boot 应用程序。 春天 是用于创建企业应...
通道是继 buffers 之后 java.nio 的第二个主要新增内容,我们在之前的教程中已经详细了解了这一点。通道提供与 I/O 服务的直接连接。 通道是一种在字节缓冲区和通道另一端的实体(通...
课程大纲 Elasticsearch 是一个基于 Lucene 的搜索引擎。它提供了一个分布式的、支持多租户的全文搜索引擎,带有 HTTP Web 界面和无模式的 JSON 文档。 Elasti...
解析器是强大的工具,使用 ANTLR 可以编写可用于多种不同语言的各种解析器。 在这个完整的教程中,我们将: 解释基础:什么是解析器,它可以用来做什么 查看如何设置 ANTLR 以便在 Java...
Java 是用于开发各种桌面应用程序、Web 应用程序和移动应用程序的最流行的编程语言之一。以下文章将帮助您快速熟悉 Java 语言,并迈向 API 和云开发等更复杂的概念。 1. Java语言...
Java中的继承是指子类继承或获取父类的所有非私有属性和行为的能力。继承是面向对象编程的四大支柱之一,用于提高层次结构中类之间的代码可重用性。 在本教程中,我们将了解 Java 支持的继承类型,...
Java Message Service 是一种支持正式通信的 API,称为 网络上计算机之间的消息传递。 JMS 为支持 Java 程序的标准消息协议和消息服务提供了一个通用接口。 JMS 提...
Java 项目中的一项常见任务是将日期格式化或解析为字符串,反之亦然。解析日期意味着你有一个代表日期的字符串,例如“2017-08-3”,你想把它转换成一个代表 Java 中日期的对象,例如Ja...
之前,我介绍了spring 3 + hibernate 集成 示例和struts 2 hello world 示例。在本教程中,我将讨论在将 spring 框架与 struts 与 hibern...