0

0

在几分钟内保护您的 API:使用 JWT 的基于令牌的 RSocket

霞舞

霞舞

发布时间:2024-10-13 14:28:47

|

574人浏览过

|

来源于dev.to

转载

rsocket 提供了一个强大的消息传递系统,构建在反应式流框架之上,并支持多种协议,包括 tcp、websocket、http 1.1 和 http 2。其与编程语言无关的交互模型,例如 request_response、request_fnf 、request_stream、request_channel,满足微服务、api网关、sidecar代理、消息队列等多种通信场景。

在保护通信安全时,基于 rsocket 的应用程序可以轻松采用基于 tls 和基于 token 的解决方案。虽然 rsocket 可以在 tcp 或 websocket 上重用 tls,但本文重点介绍基于令牌的实现,以演示基于角色的访问控制 (rbac) 功能。

作为全球最广泛采用的 oauth2 技术,json web token (jwt) 由于其与编程语言无关的性质而成为理想的选择。经过深入研究,我坚信将 rsocket 与 jwt 结合起来是实现服务之间安全通信的绝佳方法,特别是对于 open api。有关保护 api 的更详细指南,请访问computerstechnicians.com。现在,让我们更深入地探讨其中的复杂性。

实现 rsocket 进行安全通信

首要问题是如何在 rsocket 中利用 token 进行服务间通信。

有两种方法将令牌从请求者传输到响应者。一种方法涉及在设置期间将令牌嵌入到元数据 api 中,而另一种方法涉及在每个请求中将令牌作为元数据发送,并附上作为数据的有效负载。

除此之外,路由在授权中起着关键作用,指示响应方的资源。在rsocket扩展中,存在路由元数据扩展来扩展四种交互模型。如果请求者和响应者都支持标签有效负载,那么在顶层定义授权就很简单。

了解 json web 令牌 (jwt)

要理解本文,了解 jwt 以下五个方面就足够了。

  1. jwt 包含 json web 签名 (jws)、json web 加密 (jwe)、json web 密钥 (jwk) 和 json web 算法 (jwa)。

    1. hs256 是一种对称密钥算法,而 rs256/es256 是一种基于公钥基础设施 (pki) 的非对称密钥算法。两者都在 jwa 规范中定义。 hs256 将 hmac(密钥哈希消息验证码)与 sha-256 相结合,而 rs256 使用 rsassa 与 sha-256 配对,es256 采用 ecdsa(椭圆曲线数字签名算法)与 sha-256 一起使用。

    2. 就秘密长度而言,假设采用 hs256 算法来生成令牌,鉴于 hs256 需要至少 256 位的秘密(考虑到 1 个字符相当于 8 位),秘密字符应超过 32 个。

    3. 响应者使用访问令牌进行解码、验证和授权目的,而刷新令牌用于重新生成令牌,特别是当访问令牌已过期或无效时。

    4. 用户退出后需要进行适当的处​​理,尤其是在此期间访问令牌仍然有效的情况下,以防止未经授权的访问。

    安全数据交换

    现在,让我们开始演示吧。我们有两种类型的 api:令牌和资源。只有在令牌经过验证和认证后才能访问资源 api。

    工作流程

    • 我们使用登录 api 为请求者生成令牌,这需要用户名和密码。身份验证成功后,响应方签名、保存并将访问令牌和刷新令牌返回给请求方。
    • 刷新api用于更新令牌,需要刷新令牌。解码授权后,响应方签名、保存并返回access token和refresh token给请求方。

    • 我们定义 info/list/hire/fire 作为资源 api 来演示各种读/写操作。

    • 注销 api 处理令牌被盗的情况,如前所述,以防止未经授权的访问。

    在几分钟内保护您的 API:使用 JWT 的基于令牌的 RSocket

    身份验证

    鉴于我们使用基于角色的访问控制(rbac)作为我们的授权机制,身份验证组件应该提供一个身份存储库(用户-角色-权限)来存储和检索响应者中的身份信息,确保安全身份验证。

    此外,我们提供了一个令牌存储库来存储、撤销和读取令牌,用于验证从令牌解码的身份验证。由于授权信息在令牌内被加密和压缩,因此我们使用存储库信息来仔细检查这些授权。如果匹配,我们就可以确认请求的真实性和合法性。

    身份验证

    api interaction model role
    sign in request/response all users
    sign out fire-and-forget authenticated users
    refresh token request/response all users
    user information request/response user, administrator
    user list request/stream user, administrator
    hire user request/response administrator
    terminate user request/response administrator

    实施具有增强安全性的 spring boot

    令牌签名

    为了实现不同编程语言的无缝集成,在本演示中为加密和压缩中使用的加密算法和常量建立统一的标准至关重要。

    在此实现中,我们选择hs256作为首选算法,访问令牌有效期为5分钟,刷新令牌有效期为7天。

    public static final long access_token_validity_period = 5;
    public static final long refresh_token_validity_period = 7;
    private static final macalgorithm encryption_mechanism = macalgorithm.hs256;
    private static final string hashing_algorithm = "hmacsha256";

    让我们检查生成的访问令牌代码:

    public static usertoken generateaccesstoken(hellouser  user) {
        algorithm encryption_technique = algorithm.hmac256(access_secret_key);
        return generatetoken(user, encryption_technique, access_token_validity_period, chronounit.minutes);
    }
    
    private static usertoken generatetoken(hellouser  user, algorithm encryptiontechnique, long expirationtime, chronounit timeunit) {
        string tokenidentifier = uuid.randomuuid().tostring();
        instant currenttime = instant.now();
        instant expirationtimeinstant;
        if (currenttime.issupported(timeunit)) {
            expirationtimeinstant = currenttime.plus(expirationtime, timeunit);
        } else {
            log.error("unit param is not supported");
            return null;
        }
        string token = jwt.create()
                .withjwtid(tokenidentifier)
                .withsubject(user.getuserid())
                .withclaim("scope", user.getrole())
                .withexpiresat(date.from(expirationtimeinstant))
                .sign(encryptiontechnique);
        return usertoken.builder().tokenid(tokenidentifier).token(token).user(user).build();
    }
    重要提示: 上述代码中的声明键名称不是任意选择的,因为框架中使用“scope”作为从令牌中解码角色的默认方法。

    随后,token解码器代码如下:

    java 公共静态reactivejwtdecoder acquireaccesstokendecoder(){ secretkeyspec secretkey = new secretkeyspec(access_secret_key.getbytes(), hmac_sha_256); 返回 nimbusreactivejwtdecoder.withsecretkey(secretkey) .messageauthenticationalgorithm(mac_algorithm) 。建造(); } 公共静态reactivejwtdecoder jwtaccesstokendecoder(){ 返回新的hellojwtdecoder(acquireaccesstokendecoder()); } //hellojwtdecoder @overridepublic mono 解码(字符串标记)抛出 jwtexception { 返回reactivejwtdecoder.decode(token).doonnext(jwt -> { 字符串 id = jwt.getid(); hellouser auth = tokenrepository.retrieveauthfromaccesstoken(id); 如果(验证==空){ 抛出新的 jwtexception(“无效的 hellouser”); } //待办事项 hellojwtservice.settokenid(id); }); }

    中的解码方法

    hellojwtdecoder 将在每个请求处理周期由框架触发,将 token 字符串值转换为 jwt:

    @beanpayloadsocketacceptorinterceptor authorization(rsocketsecurity rsocketsecurity) {
        rsocketsecurity security = pattern(rsocketsecurity)
                .jwt(jwtspec -> {
                    try {
                        jwtspec.authenticationmanager(jwtreactiveauthenticationmanager(jwtdecoder()));
                    } catch (exception e) {
                        throw new runtimeexception(e);
                    }
                });
        return security.build();
    }
    
    @beanpublic reactivejwtdecoder jwtdecoder() throws exception {
        return tokenutils.jwtaccesstokendecoder();
    }
    
    @beanpublic jwtreactiveauthenticationmanager jwtreactiveauthenticationmanager(reactivejwtdecoder decoder) {
        jwtauthenticationconverter converter = new jwtauthenticationconverter();
        jwtgrantedauthoritiesconverter authoritiesconverter = new jwtgrantedauthoritiesconverter();
        authoritiesconverter.setauthorityprefix("role_");
        converter.setjwtgrantedauthoritiesconverter(authoritiesconverter);
        jwtreactiveauthenticationmanager manager = new jwtreactiveauthenticationmanager(decoder);
        manager.setjwtauthenticationconverter(new reactivejwtauthenticationconverteradapter(converter));
        return manager;
    }

    撤销令牌

    为了简化demo运行的环境,这里撤销token的方式是通过guava缓存来实现的。您可以使用一些强大的组件(例如 redis)来做到这一点。

    时间一到,访问令牌将自动撤销。

    蕉点AI
    蕉点AI

    AI电商商品图生成平台 | 智能商品素材制作工具

    下载

    另一方面,当请求者发送注销时,该缓存将作为事件驱动被调用。

    cache accesstokentable = cachebuilder.newbuilder()
                .expireafterwrite(tokenutils.access_expire, timeunit.minutes).build();
    
    public void deleteaccesstoken(string tokenid) {
      accesstokentable.invalidate(tokenid);
    }

    身份验证

    authenticate 函数专为登录而设计,其运行原理与 http 基本身份验证机制相同,并且非常简单:

    hellouser   user = userrepository.retrieve(principal);
    if (user.getpassword().equals(credential)) {
      return user;
    }

    相比之下,专为刷新而定制的替代身份验证涉及一系列更复杂的步骤:

    • 获取解码器并利用它将令牌字符串值解码为 jwt 对象

    • 实施反应式方法将 jwt 映射到身份验证

    • 从存储库检索身份验证详细信息

    • 验证数据库中的身份验证信息和令牌是否相同

    • 以流式方式返回认证对象

    return reactivejwtdecoder.decode(refreshtoken).map(jwt -> {
        try {
            hellouser   user = hellouser  .builder().userid(jwt.getsubject()).role(jwt.getclaim("scope")).build();
            log.info("verification successful. user: {}", user);
            hellouser   auth = tokenrepository.getauthfromrefreshtoken(jwt.getid());
            if (user.equals(auth)) {
                return user;
            }
        } catch (exception e) {
            log.error("", e);
        }
        return new hellouser  ();
    });

    权限管理

    正如我之前提到的,这个演示是基于基于角色的访问控制(rbac)构建的;路由是至关重要的方面。为了简洁起见,我将不再展示开放 api 版本,而是提供一个简洁的概述:

    // hellosecurityconfig
    protected rsocketsecurity pattern(rsocketsecurity security) {
        return security.authorizepayload(authorize -> authorize
                .route("signin.v1").permitall()
                .route("refresh.v1").permitall()
                .route("signout.v1").authenticated()
                .route("hire.v1").hasrole(admin)
                .route("fire.v1").hasrole(admin)
                .route("info.v1").hasanyrole(user, admin)
                .route("list.v1").hasanyrole(user, admin)
                .anyrequest().authenticated()
                .anyexchange().permitall()
        );
    }
    
    // hellojwtsecurityconfig
    @configuration@enablersocketsecuritypublic class hellojwtsecurityconfig extends hellosecurityconfig {
      @bean  payloadsocketacceptorinterceptor authorization(rsocketsecurity rsocketsecurity) {
        rsocketsecurity security = pattern(rsocketsecurity)
        ...

    我将基于路由的 rbac 定义放在父类中,以便于使用其他方式扩展安全性,例如tls。

    springboot 提供了 messagemapping 注解来让我们定义消息传递的路由,这意味着 rsocket 中的流式 api。

    @messagemapping("signin.v1")    mono signin(hellouser hellouser) {
        ...

    先决条件

    从 2.2.0-release 开始,spring boot 已纳入 rsocket 支持。此外,从2.3版本开始,它还提供了rsocket安全功能。由于在我撰写本文时 2.3.0 尚未普遍可用,因此我展示的版本是 2.3.0.m4。

    • spring-boot.版本2.3.0.m4

    • spring.版本 5.2.5.release

    • spring-security.version 5.3.1.release

    • rsocket.版本1.0.0-rc6

    • reactor-netty.version 0.9.5.release

    • netty.版本 4.1.45.final

    • reactor-core.version 3.3.3.release

    • jjwt.版本 0.9.1

    编译、执行和测试

    bash build.sh
    bash run_responder.sh
    bash run_requester.sh
    bash curl_test.sh

    curl 测试

    echo "Logging in as user"
    read accessToken refreshToken < <(echo $(curl -s "http://localhost:8989/api/signin?u=0000&p=Zero4" | jq -r '.accessToken,.refreshToken'))
    echo "Access Token  :${accessToken}"
    echo -e "Refresh Token :${refreshToken}\\n"
    
    echo "[user] refresh:"
    curl -s "http://localhost:8989/api/refresh/${refreshToken}" | jq
    echo
    
    echo "[user] info:"
    curl "http://localhost:8989/api/info/1"
    echo -e "\\n"
    
    echo "[user] list:"
    curl -s "http://localhost:8989/api/list" | grep data -c
    echo
    
    echo "[user] hire:"
    curl -s "http://localhost:8989/api/hire" \
    -H "Content-Type: application/stream+json;charset=UTF-8" \
    -d '{"id":"18","value":"伏虎羅漢"}' | jq -r ".message"

    隐藏的宝石

    资源 api 组件说明了从招聘到终止的员工生命周期。想要更全面的了解,请探索十八罗汉!

    最后的想法

    最初,我计划提供一个 golang 实现,但不幸的是,golang 的 rsocket 缺乏开放的路由 api,导致实现这一目标不切实际。然而,一线希望依然存在:jeff 很快就会让它们变得可用。

    我发现使用 rust 和 nodejs 等替代语言来演示这一点很有趣。也许我什至会就此主题撰写一系列文章。

    顺便说一下,这个演示的源代码可以在 github 上找到。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

845

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

743

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

740

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

397

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

400

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

447

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

431

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16947

2023.08.03

c++ 根号
c++ 根号

本专题整合了c++根号相关教程,阅读专题下面的文章了解更多详细内容。

25

2026.01.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
快速入门Node.JS全套完整版
快速入门Node.JS全套完整版

共83课时 | 8.4万人学习

nodejs开发基础教程
nodejs开发基础教程

共15课时 | 4.5万人学习

JavaScript设计模式视频教程
JavaScript设计模式视频教程

共28课时 | 5.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号