基于JWT的RuoYi开发框架与EMQX的系统集成方法

2023年 8月 9日 43.9k 0

背景

RuoYi是一款基于Spring Boot、Spring Security和MyBatis的快速开发框架,它目前在中后台管理系统开发领域拥有大量的使用者。但是它当前缺少对websocket协议双工通信信道的支持,即长链接管理这块的功能比较弱。导致像实时通知(后台服务与前端的消息推送,异步通知),实时数据可视化(后端获取数据并推送到前端)等功能不好实现。本文介绍如果结合第三方消息代理服务器EMQX实现上述功能。

准备

docker安装EMQX

docker pull emqx/emqx:5.0.21
docker run -d --name emqx -p 1883:1883 -p 8083:8083 -p 8084:8084 -p 8883:8883 -p 18083:18083 emqx:5.0.21

EMQX访问配置

登录EMQX后台,点击菜单Access Control ->Authentication。

新增配置,选择JWT,因为RuoYi的框架使用的是JWT,这个地方就是让EMQX的认证方式和RuoYi的一致,这样RuoYi返回的token,我们可以直接通过它来调用EMQX的restful接口,实现发送消息。

新增配置认证

登录EMQX管理后台,配置认证

其中JWT from这个地方我们选择username,然后算法根据你的RuoYi框架实现的token的算法,这个地方我们使用的是hmac.其他的实践可以查看这https://www.emqx.io/docs/en/v5.0/access-control/authn/jwt.html#configure-with-dashboard。

配置RuoYi的token生成

我们使用JwtAccessTokenConverter来生成jwt token,JwtAccessTokenConverter默认使用的signer是MacSigner,而MacSigner默认的算法是HMACSHA256。下面这个地方配置的signkey一定要与上面配置EMQX的JWT认证时的Secret一致,这样就可以让EMQX验证token的真假。

@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    converter.setSigningKey("mytokenkey");// token key
    converter.setAccessTokenConverter(new NewTokenConverter());
    return converter;
}

JwtAccessTokenConverter的signer

我们想扩展ken的额外信息,便可以继承AccessTokenConverter,写一个新的。在这个转化器中,我们可以加入额外的属性identity,这样token里面就包含了这些有用的信息。

package com.ruoyi.framework.auth.config.tokenconvert;

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;

import java.util.*;

public class NewTokenConverter implements AccessTokenConverter {
private UserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();
private boolean includeGrantType;
private String scopeAttribute = "scope";
private String clientIdAttribute = "client_id";

public NewTokenConverter() {
}

public void setUserTokenConverter(UserAuthenticationConverter userTokenConverter) {
this.userTokenConverter = userTokenConverter;
}

public void setIncludeGrantType(boolean includeGrantType) {
this.includeGrantType = includeGrantType;
}

public void setScopeAttribute(String scopeAttribute) {
this.scopeAttribute = scopeAttribute;
}

public void setClientIdAttribute(String clientIdAttribute) {
this.clientIdAttribute = clientIdAttribute;
}

public Map convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
Map response = new HashMap();
OAuth2Request clientToken = authentication.getOAuth2Request();
if (!authentication.isClientOnly()) {
response.putAll(this.userTokenConverter.convertUserAuthentication(authentication.getUserAuthentication()));
} else if (clientToken.getAuthorities() != null && !clientToken.getAuthorities().isEmpty()) {
response.put("authorities", AuthorityUtils.authorityListToSet(clientToken.getAuthorities()));
}

if (token.getScope() != null) {
response.put(this.scopeAttribute, token.getScope());
}

if (token.getAdditionalInformation().containsKey("jti")) {
response.put("jti", token.getAdditionalInformation().get("jti"));
}

if (token.getExpiration() != null) {
response.put("exp", token.getExpiration().getTime() / 1000L);
}
if (authentication.getName()!=null) {
response.put("identity", authentication.getName());
}

if (this.includeGrantType && authentication.getOAuth2Request().getGrantType() != null) {
response.put("grant_type", authentication.getOAuth2Request().getGrantType());
}

response.putAll(token.getAdditionalInformation());
response.put(this.clientIdAttribute, clientToken.getClientId());
if (clientToken.getResourceIds() != null && !clientToken.getResourceIds().isEmpty()) {
response.put("aud", clientToken.getResourceIds());
}

return response;
}

public OAuth2AccessToken extractAccessToken(String value, Map map) {
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(value);
Map info = new HashMap(map);
info.remove("exp");
info.remove("aud");
info.remove(this.clientIdAttribute);
info.remove(this.scopeAttribute);
if (map.containsKey("exp")) {
token.setExpiration(new Date((Long)map.get("exp") * 1000L));
}

if (map.containsKey("jti")) {
info.put("jti", map.get("jti"));
}

token.setScope(this.extractScope(map));
token.setAdditionalInformation(info);
return token;
}

public OAuth2Authentication extractAuthentication(Map map) {
Map parameters = new HashMap();
Set scope = this.extractScope(map);
Authentication user = this.userTokenConverter.extractAuthentication(map);
String clientId = (String)map.get(this.clientIdAttribute);
parameters.put(this.clientIdAttribute, clientId);
if (this.includeGrantType && map.containsKey("grant_type")) {
parameters.put("grant_type", (String)map.get("grant_type"));
}

Set resourceIds = new LinkedHashSet((Collection)(map.containsKey("aud") ? this.getAudience(map) : Collections.emptySet()));
Collection

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论