基于M实现的JWT解决方案

news2024/9/26 5:19:39

文章目录

  • 基于`M`实现的`JWT`解决方案
    • 简介
    • 现状
    • 原理
    • JWT 组成结构
      • 头部`Header`
      • 有效载荷`Payload`
      • 哈希签名`Signature`
      • JWT完整结果
    • `JWT`基于`M`的使用流程
    • 总结
    • 完整代码

基于 M实现的 JWT解决方案

简介

JWT 英文名是 Json Web Token ,是一种用于通信双方之间传递安全信息的简洁的、URL安全的表述性声明规范,经常用在跨域身份验证。

JWTJSON 对象的形式安全传递信息。因为存在数字签名,因此所传递的信息是安全的。

现状

在讲解 JWT 之前我们先来看一个问题。我们都知道互联网服务的身份验正过程是这样的,客户端向服务器发送登录名和登录密码,服务器验证后将对应的相关信息保存到当前会话中,这些信息包括权限、角色等数据。

服务器向客户端返回 SessionSession 信息都会写入到客户端的 Cookie 中,后面的请求都会从 Cookie 中读取 Session 发送给服务器,服务器在收到 Session 后会对比保存的数据来确认客户端身份。

但是上述模式存在一个问题,无法横向扩展。在服务器集群或者面向服务且跨域的结构中,需要数据库来保存 Session 会话,实现服务器之间的会话数据共享。

在单点登录中我们会遇到上述问题,当有多个网站提供同一拨服务,那么我们该怎么实现在甲网站登陆后其他网站也同时登录呢?

其中一种方法是持久化 Session 数据,也就是上面所说的将 Session 会话存到数据库中。这个方法的优点是架构清晰明了。

但是缺点也非常明显,就是架构修改很困难,验证逻辑需要重写,并且整体依赖于数据库,如果存储 Session 会话的数据库挂掉那么整个身份认证就无法使用,进而导致系统无法登录。要解决这个问题我们就用到了 JWT

原理

客户端身份经过服务器验证通过后,会生成带有签名的 JSON 对象并将它返回给客户端。客户端在收到这个 JSON 对象后存储起来。

在以后的请求中客户端将 JSON 对象连同请求内容一起发送给服务器,服务器收到请求后通过 JSON 对象标识用户,如果验证不通过则不返回请求的数据。

验证不通过的情况有很多,比如签名不正确、无权限、过期等。在 JWT 中服务器不保存任何会话数据,使得服务器更加容易扩展。

Base64URL 算法

在讲解 JWT 的组成结构前我们先来讲解一下 Base64URL 算法。这个算法和 Base64 算法类似,但是有一点区别。

我们通过名字可以得知这个算法使用于 URL 的,因此它将 Base64 中的 +/= 三个字符替换成了 -_ 删除掉了 = 。因为这个三个字符在 URL 中有特殊含义。

基于M实现的方法:

ClassMethod Base64UrlEncryption(str As %String)
{
	s ret = $zcvt(str, "O", "UTF8")
	s ret = ##class(%SYSTEM.Encryption).Base64Encode(ret, 1)
	s ret = ..ConvertUrl(ret)
	q ret
}

ClassMethod ConvertUrl(str)
{
	q $tr(str, "+/=", "-_")
}

JWT 组成结构

JWT 是由三段字符串和两个 . 组成,每个字符串和字符串之间没有换行(类似于这样:abc.def.xyz),每个字符串代表了不同的功能,我们将这三个字符串的功能按顺序列出来并讲解:

头部Header

JWT 头描述了 JWT 元数据,是一个 JSON 对象,它的格式如下:

{
	"alg": "HS256",
	"typ": "JWT"
}

这里的 alg 属性表示签名所使用的算法,JWT 签名默认的算法为 HMAC SHA256alg 属性值 HS256 就是 HMAC SHA256 算法。typ 属性表示令牌类型,这里就是 JWT

基于M实现的方法:

ClassMethod GenerateHeaderPart()
{
	s header = {}
	s header.alg = "HS256"
	s header.typ = "JWT"
	s headerPart = ..Base64UrlEncryption(header.%ToJSON())
	q headerPart
}
USER>w ##class(M.Jwt).GenerateHeaderPart()
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

有效载荷Payload

有效载荷是 JWT 的主体,同样也是个 JSON 对象。

JWT指定七个默认字段供选择,也可以省略,一般建议使用。包括以下内容:

  • issjwt的签发者/发行人
  • sub:主题
  • aud:接收方
  • expjwt过期时间
  • nbfjwt生效时间
  • iat:签发时间
  • jtijwt唯一身份标识,可以避免重放攻击

也可以自定义字段:

{
	"name": "yx",
	"code": "yaoxin",
	"desc": "engineer",
}

下面这个代码段就是定义了一个有效载荷:

{
	"iss": "yx",
	"jti": "78006423",
	"exp": "2023-01-15 09:30:04",
	"nbf": "2023-01-08 09:30:04",
	"iat": "2023-01-08 09:30:04"
}

基于M实现的方法:

Parameter Sign = "yx";

ClassMethod GeneratePayloadPart()
{
	s payload = {}
	s payload.iss = ..#Sign
	s payload.jti = ##class(%SYSTEM.Encryption).GenCryptToken()
	s payload.exp = $zd(($h + 7), 3) _ " " _$zt($p($h, ",", 2), 1)
	s payload.nbf = $zdt($h, 3)
	s payload.iat = $zdt($h, 3)
	s payloadPart = ..Base64UrlEncryption(payload.%ToJSON())
	q payloadPart
}
USER>w ##class(M.Jwt).GeneratePayloadPart()
eyJpc3MiOiJ5eCIsImp0aSI6IjYzMjg1OTgyIiwiZXhwIjoiMjAyMy0wMS0yNSAwOTozNTozMCIsIm5iZiI6IjIwMjMtMDEtMTggMDk6MzU6MzAiLCJpYXQiOiIyMDIzLTAxLTE4IDA5OjM1OjMwIn0

哈希签名Signature

哈希签名的算法主要是确保数据不会被篡改。它主要是对前面所讲的两个部分进行签名,通过 JWT 头定义的算法生成哈希。哈希签名的过程如下:

  1. 指定密码,密码保存在服务器中,不能向客户端公开。

  2. 使用JWT 头指定的算法进行签名,进行签名前需要对 JWTHeader和有效载荷payload进行 Base64URL 编码,结果之间需要用 . 来连接。

示例如下:

HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)

基于M实现的方法:

Parameter DOT = ".";

Parameter Key = 00000000111111112222222233333333;

ClassMethod GenerateSignaturePart(headerPart, payloadPart)
{
	s content = headerPart _ ..#DOT _ payloadPart
	s signaturePart = ..HMACSHABase64Encode(content, ..#Key)
	q signaturePart
}

JWT完整结果

JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串。

Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)

基于M实现的方法:

ClassMethod GenerateJwt()
{
	s headerPart = ..GenerateHeaderPart()
	s payloadPart = ..GeneratePayloadPart()
	s signaturePart = ..GenerateSignaturePart(headerPart, payloadPart)
	s jwt = headerPart _ ..#DOT _ payloadPart _ ..#DOT _ signaturePart
	q jwt
}
USER>w ##class(M.Jwt).GenerateJwt()
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ5eCIsImp0aSI6IjA0MzYzNzA1IiwiZXhwIjoiMjAyMy0wMS0yNSAwOTo0ODowMSIsIm5iZiI6IjIwMjMtMDEtMTggMDk6NDg6MDEiLCJpYXQiOiIyMDIzLTAxLTE4IDA5OjQ4OjAxIn0.XxgI9PG8AznJIyS6neze2UC4XNG-V0dP46pkp_JNhBY

可以在jwt.io(地址:https://jwt.io/)上验证一下:

在这里插入图片描述

JWT基于M的使用流程

在这里插入图片描述

  1. 首先创建后端门面接口,用户处理通用权限校验和接收反射需要调用的方法:
Class M.Broker Extends (%CSP.Page, %CSP.REST)
{
/// desc:返回页面数据方法
ClassMethod OnPage() As %Status
{
	s pClassName = $g(%request.Data("ClassName",1))
	s pMethodName = $g(%request.Data("MethodName",1)) 
	
	/* 输出返回值到页面上 */
	w ..RunMethod(pClassName, pMethodName)
	q $$$OK
}

/// desc:运行通用方法
ClassMethod RunMethod(pClassName, pMethodName) [ ProcedureBlock = 0 ]
{
	s $zt = "Error"
	d ..GetInfo()
	
	q:(pMethodName'="Login")&&('$d(%request.CgiEnvs("HTTP_AUTHORIZATION"))) ..Failure2Json("请求头中AUTHORIZATION不存在!")
	
	q:(pClassName = "") ..Failure2Json("类不能为空!")
	q:(pMethodName = "") ..Failure2Json("方法不能为空!")
	
	d:(pMethodName'="Login") ##class(M.Jwt).VerifyJwt()
	
	
	s requestMethod = $lb("POST", "GET")
	q:('$lf(requestMethod, %request.CgiEnvs("REQUEST_METHOD")))
	
	try {
		
		s result = $classmethod(pClassName, pMethodName)
		
	} catch e {
		
		s msg = e.Name _ e.Location _ " *" _ data _ $ze
		ret ..Failure2Json(msg)
	}
	
    q ..Success2Json(result)
Error 
	s $zt = ""
	ret ..Failure2Json("意外没有捕捉错误的错误:" _ $ze)
}
}
  1. 模拟客户端使用用户名和密码请求登录返回Jwt。服务端收到请求,验证用户名和密码。验证成功后,服务端会签发一个 JWT token,再把这个JWT token返回给客户端。

后端登录方法:

Class M.Jwt Extends %RegisteredObject
{

ClassMethod Login()
{
	#; todo 验证用户密码信息
	#; 返回JWT
	q ..GenerateJwt()
}

}

使用PostMan模拟接口登录:

在这里插入图片描述

{
    "code": 200,
    "msg": "",
    "data": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ5eCIsImp0aSI6IjQ3MDY0MDQ3IiwiZXhwIjoiMjAyMy0wMS0yNSAxMDoyMzo1NCIsIm5iZiI6IjIwMjMtMDEtMTggMTA6MjM6NTQiLCJpYXQiOiIyMDIzLTAxLTE4IDEwOjIzOjU0In0.IcYRp5BqGtslB94lNYmgIrvIwuyne347HjmHkNmAceo"
}
  1. 客户端收到token后可以把它存储起来,比如放到sessionStorage中。客户端每次向服务端请求资源时需要携带服务端签发的JWT token,可以在header中携带。

使用PostMan模拟请求数据:

  • 把请求到的JWT信息放到 PostMan -> Authorization -> TYPE = Bear Token -> 设置Token 中。
  • 前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSSXSRF问题)。

在这里插入图片描述

  1. 服务端收到请求,然后去验证客户端请求里面带着的JWT token,如果验证成功,就向客户端返回请求数据。

解析验证JWT后端方法:

ClassMethod VerifyJwt()
{
	#; 验证是否携带JWT
	q:'$d(%request) ""
	q:'$d(%request.CgiEnvs("HTTP_AUTHORIZATION")) $$$JwtException("请求头中AUTHORIZATION不存在!")
	
	#; 获取JWT
	s token = %request.CgiEnvs("HTTP_AUTHORIZATION")
	s jwt = $p(token," " ,2)
	s headerPart = $p(jwt, ".", 1)
	s payloadPart = $p(jwt, ".", 2)
	s signaturePart = $p(jwt, ".", 3)
	
	#; 解析header部分
	s header = ..Base64Decryption(headerPart)
	s headerObj = {}.%FromJSON(header)
	
	#; 解析payload部分
	s payload = ..Base64Decryption(payloadPart)
	s payloadObj = {}.%FromJSON(payload)
	
	#; 解析重新验签判断数据是否被修改过
	s content = headerPart _ ..#DOT _ payloadPart
	s signatureConetent = ..HMACSHABase64Encode(content, ..#Key)
	q:(signatureConetent '= signaturePart) $$$JwtException("签名校验异常")
	
	#; 其他验证信息,根据需求自行判断
	q:(payloadObj.iss '= ..#Sign) $$$JwtException("IIS发行人校验异常")
	s exp = payloadObj.exp
	#; 这里只计算了日期,需要对比时间根据需要自行判断
	q:(+$zdth(exp, 3) < +$h) $$$JwtException("JWT已经过期")
	 
	q $$$OK
}

在这里插入图片描述

后端数据方法:

ClassMethod Data()
{
	q $$$OK
}

总结

使用场景:

  • 支持跨域访问:cookie是无法跨域的,而token由于没有用到cookie(前提是将token放到请求头中),所以跨域后不会存在信息丢失问题。
  • 无状态:token机制在服务端不需要存储session信息,因为token自身包含了所有登录用户的信息,所以可以减轻服务端压力。
  • 更适用于移动端:当客户端是非浏览器平台时,cookie是不被支持的,此时采用token认证方式会简单很多。
  • 无需考虑CSRF:由于不再依赖cookie,所以采用token认证方式不会发生CSRF,所以也就无需考虑CSRF的防御。
  • 单点登录。

在使用 JWT 时需要注意以下事项:

  1. JWT 默认不加密,如果要写入敏感信息必须加密,可以用生成的原始令牌再次对内容进行加密。

  2. JWT无法使服务器保存会话状态,当令牌生成后在有效期内无法取消也不能更改。

  3. JWT 包含认证信息,如果泄露了,任何人都可以获得令牌所有的权限;因此 JWT 有效期不能太长,对于重要操作每次请求都必须进行身份验证。

完整代码

  • M.Broker.cls
Class M.Broker Extends (%CSP.Page, %CSP.REST)
{

/// 设置为"utf-8",否则中文乱码
Parameter CHARSET = "utf-8";

ClassMethod OnPreHTTP() As %Boolean
{
	#dim %response as %CSP.Response
	#dim %session as %CSP.Session
	#; js是否可读cookie
	#; s %response.UseHttpOnly = 1
	
	
	/* 星号表示所有的域都可以接受 */
	d %response.SetHeader("Access-Control-Allow-Origin", ..GetOrigin())
	
	#; 允许请求方式,例如get。post
	d %response.SetHeader("Access-Control-Allow-Methods", "*")
	
	#; 允许头信息
	d %response.SetHeader("Access-Control-Allow-Headers", "x-requested-with,content-type")
	
	#; 允许验证
	d %response.SetHeader("Access-Control-Allow-Credentials", "true")
	
	#; 额外暴露头信息
	#;d %response.SetHeader("Access-Control-Expose-Headers", "cookie")
	
	#; 超时时间
	d %response.SetHeader("Access-Control-Max-Age", 3600)
	
	/* 设置返回结构为Json */
	d %response.SetHeader("Content-Type", "application/json")
	
	#; 每次请求将Session超时时间重新设置
	s %session.AppTimeout = 900

	if ($zv [ "IRIS") {
		#; 确定是否应该使用 sessionId cookie 发送 "Secure" 标志的内部属性
		s %session.SecureSessionCookie = 1
		
		#; 确定如何严格限制 sessionId cookie 域的属性。选项有 None (0)Lax (1)Strict (2),其中 Strict 表示 cookie 只能在当前应用程序中使用。默认为 CSP 应用程序的相应设置。除非另有配置,否则应用程序默认为 Strict。请注意,None 对于不安全 (HTTP) 连接无效
		s %session.SessionScope = 0
		
		#; 用于确定要与用户创建的 cookie 一起发送的 SameSite 属性的属性。选项有 None (0)Lax (1)Strict (2)。默认为 CSP 应用程序的相应设置。除非另有配置,否则应用程序默认为 Strict。请注意,None 对于不安全 (HTTP) 连接无效。
		s %session.UserCookieScope = 0
	}


	q 1
}

/// desc:返回页面数据方法
ClassMethod OnPage() As %Status
{
	s pClassName = $g(%request.Data("ClassName",1))
	s pMethodName = $g(%request.Data("MethodName",1)) 
	
	/* 输出返回值到页面上 */
	w ..RunMethod(pClassName, pMethodName)
	q $$$OK
}

/// desc:运行通用方法
ClassMethod RunMethod(pClassName, pMethodName) [ ProcedureBlock = 0 ]
{
	s $zt = "Error"
	d ..GetInfo()
	
	q:(pMethodName'="Login")&&('$d(%request.CgiEnvs("HTTP_AUTHORIZATION"))) ..Failure2Json("请求头中AUTHORIZATION不存在!")
	
	q:(pClassName = "") ..Failure2Json("类不能为空!")
	q:(pMethodName = "") ..Failure2Json("方法不能为空!")
	
	d:(pMethodName'="Login") ##class(M.Jwt).VerifyJwt()
	
	
	s requestMethod = $lb("POST", "GET")
	q:('$lf(requestMethod, %request.CgiEnvs("REQUEST_METHOD")))
	
	try {
		
		s result = $classmethod(pClassName, pMethodName)
		
	} catch e {
		
		s msg = e.Name _ e.Location _ " *" _ data _ $ze
		ret ..Failure2Json(msg)
	}
	
    q ..Success2Json(result)
Error 
	s $zt = ""
	ret ..Failure2Json("意外没有捕捉错误的错误:" _ $ze)
}

/// 返回消息Json
ClassMethod Msg(code, msg = "", data = "") As %DynamicObject
{
    s ret = {}
    s ret.code = code
    s ret.msg = msg
    s ret.data = data
    q ret
}

/// 无Session判断
ClassMethod Session() As %DynamicObject
{
    q ..Msg(-2, "", "")
}

/// 返回前台的成功消息转Json
ClassMethod Session2Json() As %String
{
    q ..Session().%ToJSON()
}

/// 返回前台的成功消息
ClassMethod Success(data) As %DynamicObject
{
    q ..Msg(200, "", data)
}

/// 返回前台的成功消息转Json
ClassMethod Success2Json(data) As %String
{
    q ..Success(data).%ToJSON()
}

/// 返回前台分页数据的成功消息转Json
ClassMethod Success2Json4Page(data, total) As %String
{
    s ret = ..Success(data)
    d ret.%Set("size" ,$g(%request.Data("size", 1)) ,"number")
    d ret.%Set("current" ,$g(%request.Data("current", 1)) ,"number")
    d ret.%Set("total" ,total ,"number")
    q ret.%ToJSON()
}

/// 返回前台的失败消息
ClassMethod Failure(msg, data = "") As %DynamicObject
{
    q ..Msg(-1, msg, data)
}

/// 返回前台的失败消息转Json
ClassMethod Failure2Json(msg, data = "") As %String
{
    q ..Failure(msg, data).%ToJSON()
}

/// 返回前台的警告消息
ClassMethod Warning(msg, data = "") As %DynamicObject
{
    q ..Msg(0, msg, data)
}

/// 返回前台的警告消息转Json
ClassMethod Warning2Json(msg, data = "") As %String
{
    q ..Warning(msg, data).%ToJSON()
}

ClassMethod GetOrigin()
{
	q:'$d(%request) ""
	q:'$d(%request.CgiEnvs("HTTP_ORIGIN")) ""
	s origin = %request.CgiEnvs("HTTP_ORIGIN")
	q origin
}

ClassMethod GetAuthorization()
{
	q:'$d(%request) ""
	q:'$d(%request.CgiEnvs("HTTP_AUTHORIZATION")) ""
	s token = %request.CgiEnvs("HTTP_AUTHORIZATION")
	q token
}

}

  • M.Jwt.cls
Include M.Jwt

Class M.Jwt Extends %RegisteredObject
{

ClassMethod Login()
{
	#; todo 验证用户密码信息
	#; 返回JWT
	q ..GenerateJwt()
}

ClassMethod Data()
{
	q $$$OK
}

Parameter DOT = ".";

Parameter Sign = "yx";

Parameter Key = 00000000111111112222222233333333;

/// w ##class(M.Jwt).GetJwt()
ClassMethod VerifyJwt()
{
	#; 验证是否携带JWT
	q:'$d(%request) ""
	q:'$d(%request.CgiEnvs("HTTP_AUTHORIZATION")) $$$JwtException("请求头中AUTHORIZATION不存在!")
	
	#; 获取JWT
	s token = %request.CgiEnvs("HTTP_AUTHORIZATION")
	s jwt = $p(token," " ,2)
	s headerPart = $p(jwt, ".", 1)
	s payloadPart = $p(jwt, ".", 2)
	s signaturePart = $p(jwt, ".", 3)
	
	#; 解析header部分
	s header = ..Base64Decryption(headerPart)
	s headerObj = {}.%FromJSON(header)
	
	#; 解析payload部分
	s payload = ..Base64Decryption(payloadPart)
	s payloadObj = {}.%FromJSON(payload)
	
	#; 解析重新验签判断数据是否被修改过
	s content = headerPart _ ..#DOT _ payloadPart
	s signatureConetent = ..HMACSHABase64Encode(content, ..#Key)
	q:(signatureConetent '= signaturePart) $$$JwtException("签名校验异常")
	
	#; 其他验证信息,根据需求自行判断
	q:(payloadObj.iss '= ..#Sign) $$$JwtException("IIS发行人校验异常")
	s exp = payloadObj.exp
	#; 这里只计算了日期,需要对比时间根据需要自行判断
	q:(+$zdth(exp, 3) < +$h) $$$JwtException("JWT已经过期")
	 
	q $$$OK
}

/// w ##class(M.Jwt).SetJwt()
ClassMethod GenerateJwt()
{
	s headerPart = ..GenerateHeaderPart()
	s payloadPart = ..GeneratePayloadPart()
	s signaturePart = ..GenerateSignaturePart(headerPart, payloadPart)
	s jwt = headerPart _ ..#DOT _ payloadPart _ ..#DOT _ signaturePart
	q jwt
}

/// w ##class(M.Jwt).GenerateHeaderPart()
/// eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0aHJvd3giLCJqaWQiOjEwMDg3LCJleHAiOjE2MTMyMjc0NjgxNjh9.7skduDGxV-BP2p_CXyr3Na7WBvENNl--Pm4HQ8cJuEs
/// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0aHJvd3giLCJqaWQiOjEwMDg3LCJleHAiOjE2MTMyMjc0NjgxNjh9.RkVBMjAwOEYwNzIwRDdENzI0MURCMzUxRjhGMzcyOUI5Mjc3MEYzOENFMjgxQjcyNTdDRTU3QTBEQTJDNTlCMw==
ClassMethod GenerateHeaderPart()
{
	s header = {}
	s header.alg = "HS256"
	s header.typ = "JWT"
	s headerPart = ..Base64UrlEncryption(header.%ToJSON())
	q headerPart
}

/// w ##class(M.Jwt).GeneratePayloadPart()
ClassMethod GeneratePayloadPart()
{
	s payload = {}
	s payload.iss = ..#Sign
	s payload.jti = ##class(%SYSTEM.Encryption).GenCryptToken()
	s payload.exp = $zd(($h + 7), 3) _ " " _$zt($p($h, ",", 2), 1)
	s payload.nbf = $zdt($h, 3)
	s payload.iat = $zdt($h, 3)
	s payloadPart = ..Base64UrlEncryption(payload.%ToJSON())
	q payloadPart
}

/// w ##class(M.Jwt).GenerateSignaturePart()
/// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0aHJvd3giLCJqaWQiOjEwMDg3LCJleHAiOjE2MTMyMjc0NjgxNjh9._qIAjwcg19ckHbNR-PNym5J3DzjOKBtyV85XoNosWbM
ClassMethod GenerateSignaturePart(headerPart, payloadPart)
{
	s content = headerPart _ ..#DOT _ payloadPart
	s signaturePart = ..HMACSHABase64Encode(content, ..#Key)
	q signaturePart
}

ClassMethod Base64UrlEncryption(str As %String)
{
	s ret = $zcvt(str, "O", "UTF8")
	s ret = ##class(%SYSTEM.Encryption).Base64Encode(ret, 1)
	s ret = ..ConvertUrl(ret)
	q ret
}

ClassMethod ConvertUrl(str)
{
	q $tr(str, "+/=", "-_")
}

ClassMethod Base64Decryption(str As %String)
{
	s ret = ##class(%SYSTEM.Encryption).Base64Decode(str)
	q ret
}

/// w ##class(M.Jwt).HMACSHABase64Encode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ5eCIsImp0aSI6IjY2MzkyMTU5IiwiZXhwIjoiMjAyMy0wMS0xOSAxMjoxNDozNCIsIm5iZiI6IjIwMjMtMDEtMTIgMTI6MTQ6MzQiLCJpYXQiOiIyMDIzLTAxLTEyIDEyOjE0OjM0In0","00000000111111112222222233333333")
ClassMethod HMACSHABase64Encode(str As %Text, key As %String) As %String
{
	s str = $zcvt(str, "O", "UTF8")
	s key = $zcvt(key, "O", "UTF8")
	s ret = ##class(%SYSTEM.Encryption).HMACSHA("256", str, key)
	
	s ret = ##class(%SYSTEM.Encryption).Base64Encode(ret, 1)
	s ret = ..ConvertUrl(ret)
	q ret
}

}
  • M.Jwt.inc
#define JwtException(%msg) ##class(M.JwtException).%New("",-100, %msg)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/170478.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023年,PMP认证考试的心得分享

对于刚开始要准备参加PMP考试的人&#xff0c;大多应该都是不知道怎么去考试复习好的。PMP认证考试虽是美国的考试&#xff0c;但其实这跟国内其它的考试复习也差不多&#xff0c;没有什么很特别之处&#xff0c;只是多了一个中英互译&#xff0c;再就是学习的内容不一样&#…

windows系统中环境系统变量和用户变量的区别

前言 -- 什么是环境变量一般我们安装软件之后&#xff0c;为了能够在cmd命令行运行软件&#xff0c;一般都需要设置一下环境变量&#xff0c;否则就会出现找不相关命令的错误提示。所谓环境变量&#xff0c;可以简单理解为就是给操作系统定义的一些路径和名称。比如使用最常使用…

个人对粗糙集的一些理解和简单举例

文章目录1、 数据价值密度低的解决方案1.1 粗糙集中对应的概念&#xff1a;属性约简1.2 属性约简的好处1.3 粗糙集的应用2、粗糙集的简介--->原理2.1 粗糙集的概念2.2 从例子看粗糙集2.3 粗糙集模型的分类及其评估标准3、粗糙集的主要研究方向3.1 模型创新3.2 属性约简3.3 提…

浅析正则表达式+范围规则校验表达式+js从字符串中截取数字

平时项目中经常需要用到正则表达式&#xff0c;可惜之前太懒(当然最主要是太菜也不会写)都是直接网上搜。之前用的也简单&#xff0c;无非是校验手机号码格式、校验邮箱格式、偶尔有校验密码这种&#xff0c;网上一搜一大堆&#xff0c;根本不用自己写&#xff0c;结果前段时间…

【ONE·C || 函数与数组】

总言 C语言&#xff1a;函数、数组初步认识。 文章目录总言1、函数1.1、是什么1.1.1、基本介绍1.1.2、库函数使用演示(strcpy、memset)1.1.3、自定义函数使用演示1.2、函数参数、传值调用和传址调用1.3、相关练习1.3.1、写一个函数&#xff1a;可以判断一个数是不是素数1.3.2、…

集成学习-理论概述

1、集成学习概述集成学习(ensemble learning)本身不是一个单独的机器学习算法&#xff0c;而是通过构建并结合多个机器学习器来完成学习任务。集成学习的特点&#xff1a;集成方法是一种将几种机器学习技术组合成一个预测模型的元算法&#xff0c;以减小方差&#xff08;baggin…

python-文件和异常

1. 从文件中读取数据1.1. 读取整个文件在同目录下创建textA文本文件123 456 789a. open函数&#xff1a;要以任何方式去使用文件&#xff0c;都需要先打开文件&#xff0c;它接受一个参数——要打开文件的名称。 open()返回一个表示文件的对象。b. 关键字with在不再需要访问文件…

可以自动生成日报的清单工具

用过了很多todolist工具&#xff08;Microsoft_ _To D、oodoist、滴答清单、印象笔记、有道笔记、&#xff09; 最终稳定一直在用的就这一个“闪点清单” 我的核心诉求就两点 1. 可以实时记录任务&#xff0c;并标记是否完成 2. 可以按天、周导出&#xff0c;自动整合成日报…

《计算机构造与解释》读书笔记(4)

文章目录1. 写在最前面2. 并发&#xff1a;时间是一个本质问题2.1 并发系统中时间的性质2.1.1 并发程序的正确行为2.2 控制并发的机制2.2.1 对共享变量的串行访问2.2.2 Schema 里的串行化2.2.3 使用多重共享资源的复杂性2.2.4 串行化实现2.2.5 死锁2.2.6 并发性、时间和通信3. …

Linux学习记录——구 进程概念的基础理解

文章目录一、操作系统概念理解二、进程的基本理解1、什么是进程&#xff1f;2、进程的属性1、指令查看进程2、目录查看进程3、进程与进程之间1、父子进程概念2、创建子进程---fork的基础使用方法3、fork原理的初级理解1、fork的操作2、fork如何看待代码和数据3、fork如何看待两…

【Docker概念和实践 2】虚拟机 ubuntu18上安装docker

一、说明 已经安装了N遍Docker了&#xff0c;逐步成了一套习惯&#xff0c;这里专门记录之&#xff1b;总之&#xff0c;安装前必须回答得问题是&#xff1a;何种操作系统、何种版本、是否虚拟机、云数据源等问题。一个环境如果装得好&#xff0c;就不需要重装&#xff0c;如果…

C语言刷题之摩尔投票法

目录 1.引入 2.摩尔投票算法 3.基本步骤 摩尔投票法分为两个阶段&#xff1a; 1.抵消阶段 2.检验阶段 4.代码实现 5.扩展沿伸 6.总结 1.引入 我们来看一个问题&#xff1a; 假设有一个无序数组长度为n&#xff0c;要求找出其中出现次数超过n/2的数&#xff0c;要求时间复…

vue3-环境搭建(docker版本)

序 大大小小项目经历无数&#xff0c;之前都是写的vue2的项目&#xff0c;因为项目需要&#xff0c;边学边用vue3&#xff0c;也算能转的开&#xff0c;但心里一直想系统的理顺一下vue3。 看了看极客时间&#xff0c;掘金小课&#xff0c;都没有能达到心里预期的“系统学习”…

免费内网穿透软件一步设置实现外网访问

在工作和生活中&#xff0c;有很多类似内网搭建服务器和外网连接内网的需求&#xff0c; 例如在任何地方都能访问自己家里的主机电脑笔记本上的应用&#xff0c;让出差外网和任何地方都能访问到公司内部局域网的服务器……这些需求我们可以统一用一个方案解决&#xff0c;那就是…

网络编程UDP+TCP

日升时奋斗&#xff0c;日落时自省 目录 1、网络编程基本概念 2、UDP数据报套接字编程 2.1、UDP相关API 2.1.1、DatagramSocket API 2.1.2、DatagramPacket API 2.2、UDP版本服务器 2.3、UDP版本客户端 2.4、UDP连接操作 2.5、翻译业务 2.6、总结 3、TCP流套接字编程 …

【项目实战】一文入门项目中Lombok的常用注解

一、Lombok介绍 1.1 Lombok是什么&#xff1f; 一个Java库&#xff0c;用于简化Java代码。 Lombok是一个非常神奇的 java 类库&#xff0c;会利用注解自动生成 java Bean 中烦人的 Getter、Setting&#xff0c;还能自动生成 logger、ToString、HashCode、Builder 等 java特色…

【GD32F427开发板试用】开发一款网络音乐播放器

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;守勤 资源介绍 非常荣幸能够参与到这次GD32F427开发板试用的活动中来&#xff0c;开发板的设计非常简洁&#xff0c;板载了一颗GD32F103C8T6和…

Python中的递归及案例演示

目录 一.什么是递归 二.案例 递归找文件 步骤 os模块中的三个方法 演示 最终代码 三.总结 一.什么是递归 递归在编程中是一种非常重要的算法 递归:即方法(函数)自己调用自己的一种特殊编程写法 如&#xff1a; 函数调用自己&#xff0c;即称之为递归调用。 二.案例 递…

C++ 引用! 他是坤坤也是鸡哥

&#x1f451;专栏内容&#xff1a;C学习笔记⛪个人主页&#xff1a;子夜的星的主页&#x1f495;座右铭&#xff1a;日拱一卒&#xff0c;功不唐捐 目录一、前言二、引用1、引用的概念2、引用的声明3、引用的特性Ⅰ、 引用在定义时必须初始化Ⅱ、 一个变量可以有多个引用Ⅲ、引…

深度学习PyTorch 之 DNN-多分类

前面讲了深度学习&PyTorch 之 DNN-二分类&#xff0c;本节讲一下DNN多分类相关的内容&#xff0c;这里分三步进行演示 结构化数据 我们还是以iris数据集为例&#xff0c;因为这个与前面的流程完全一样&#xff0c;只有在模型定义时有些区别 损失函数不一样 二分类时用的损…