上期讨论过OAuth2, 是一种身份认证+资源授权使用模式。通过身份认证后发放授权凭证。用户凭授权凭证调用资源。这个凭证就是一种令牌,基本上是一段没什么意义的加密文,或者理解成密钥也可以。服务方通过这个令牌来获取用户身份信息,也就是说服务端必须维护一个已经获得身份验证的用户信息清单。研究了一下JWT,发现它本身可以携带加密后的一些信息包括用户信息,而这些信息又可以通过同样的加密算法解密恢复。也就是说服务端是可以直接对收到的JWT解密恢复用户信息,这样用起来就方便多了。还记着我们的POS例子里客户端必须构建一个指令,如:http://www.pos.com/logIn?shopid=1001&userid=234 这个Uri里的shopid是明码的,会造成很大安全风险。使用JWT后,我们可以把shopid,单号什么的都放在JWT里就安全多了。

先了解一下JWT:JWT也是一个行业标准:RFC7519,是一个用Json格式传递加密信息的方式。JWT的结构如下:

header.payload.signiture 如:hhhhh.ppppp.ssssss

header:由两部分组成:1、令牌类型,在这里是JWT, 2、签名算法如 HMAC SHA256 or RSA, 下面是个header例子:

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

payload:可以用来承载用户自定义信息,如userid, shopid, vchnum ...

{"shopid": "1101","userid": "102","vchnum": 12
}

signiture: 就是把 加密后的header+加密后的payload+secret 用header提供的签名算法签名,如下:

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

我的目标是把一些用来辨识用户、权限以及状态信息加密存在JWT内发送给用户,用户在请求中提交他的JWT,服务端再解密并取出内部信息然后确定如何处理用户请求。

JWT本身原理并不复杂,应用场景也不是很多,所以不想花太多精力研究它。刚好,找到一个开源的scala JWT工具库jwt-scala. 下面就利用项目源代码来了解一下JWT的操作,包括:加密、解密、验证、获取payload内部claims值。

JWT encode 方法如下:

  /** Encode a JSON Web Token from its different parts. Both the header and the claim will be encoded to Base64 url-safe, then a signature will be eventually generated from it if you did pass a key and an algorithm, and finally, those three parts will be merged as a single string, using dots as separator.** @return $token* @param header $headerString* @param claim $claimString* @param key $key* @param algorithm $algo*/def encode(header: String, claim: String, key: String, algorithm: JwtAlgorithm): String = {val data = JwtBase64.encodeString(header) + "." + JwtBase64.encodeString(claim)data + "." + JwtBase64.encodeString(JwtUtils.sign(data, key, algorithm))}

所以产生JWT的元素都在参数里了。我们可以直接用payload.claims来构建JWT:

 /** An alias to `encode` which will provide an automatically generated header.** @return $token* @param claim $claimString*/def encode(claim: String): String = encode(JwtHeader().toJson, claim)/** An alias to `encode` which will provide an automatically generated header and setting both key and algorithm* to None.** @return $token* @param claim the claim of the JSON Web Token*/def encode(claim: JwtClaim): String = encode(claim.toJson)def encode(header: String, claim: String): String = {JwtBase64.encodeString(header) + "." + JwtBase64.encodeString(claim) + "."}

这样看一个正确的JWT可以没有签名那部分的:hhhhh.ppppp。想想还是要用签名,安全点。用下面这个函数就可以了:

  /** An alias to `encode` which will provide an automatically generated header and allowing you to get rid of Option* for the key and the algorithm.** @return $token* @param claim $claimString* @param key $key* @param algorithm $algo*/def encode(claim: String, key: String, algorithm: JwtAlgorithm): String =encode(JwtHeader(algorithm).toJson, claim, key, algorithm)/** Deserialize an algorithm from its string equivalent. Only real algorithms supported,* if you need to support "none", use "optionFromString".** @return the actual instance of the algorithm* @param algo the name of the algorithm (e.g. HS256 or HmacSHA256)* @throws JwtNonSupportedAlgorithm in case the string doesn't match any known algorithm*/def fromString(algo: String): JwtAlgorithm = algo match {case "HMD5"        => HMD5case "HS224"       => HS224case "HS256"       => HS256case "HS384"       => HS384case "HS512"       => HS512case "RS256"       => RS256case "RS384"       => RS384case "RS512"       => RS512case "ES256"       => ES256case "ES384"       => ES384case "ES512"       => ES512case _             => throw new JwtNonSupportedAlgorithm(algo)// Missing PS256 PS384 PS512}

key可以是任意字符串。

JWT decode 代码如下:

  /** Will try to decode a JSON Web Token to raw strings using a HMAC algorithm** @return if successful, a tuple of 3 strings, the header, the claim and the signature* @param token $token* @param key $key* @param algorithms $algos*/def decodeRawAll(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm], options: JwtOptions): Try[(String, String, String)] = Try {val (header64, header, claim64, claim, signature) = splitToken(token)validate(header64, parseHeader(header), claim64, parseClaim(claim), signature, key, algorithms, options)(header, claim, signature)}def decodeRawAll(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm]): Try[(String, String, String)] =decodeRawAll(token, key, algorithms, JwtOptions.DEFAULT)

另外,验证JWT方法如下:

  /** An alias for `isValid` if you want to directly pass a string as the key for HMAC algorithms** @return a boolean value indicating if the token is valid or not* @param token $token* @param key $key* @param algorithms $algos*/def isValid(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm], options: JwtOptions): Boolean =try {validate(token, key, algorithms, options)true} catch {case _ : Throwable => false}def isValid(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm]): Boolean = isValid(token, key, algorithms, JwtOptions.DEFAULT)

下面是一段示范代码:

import pdi.jwt._
import org.json4s._
import org.json4s.jackson.JsonMethods._object JwtDemo extends App{import scala.util._var clms = JwtClaim() ++ ("shopid" -> "1101") ++ ("userid" -> "102") ++ ("vchnum" -> 23)val token = Jwt.encode(clms,"OpenSesame", JwtAlgorithm.HS256)println(token)println(Jwt.isValid(token,"OpenSesame",Seq(JwtAlgorithm.HS256)))val claims = Jwt.decodeRawAll(token,"OpenSesame",Seq(JwtAlgorithm.HS256))println(claims)claims match {case Success(json) => println(((parse(json._2).asInstanceOf[JObject]) \ "shopid").values)case Failure(err) => println(s"Error: ${err.getMessage}")}}

现在我们把上次的OAuth2示范代码改改,用JWT替换access_token:

import akka.actor._
import akka.stream._
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.directives.Credentials
import pdi.jwt._
import org.json4s._
import org.json4s.jackson.JsonMethods._
import scala.util._//import akka.http.scaladsl.marshallers.sprayjson._
//import spray.json._object JsonMarshaller { // extends  SprayJsonSupport with DefaultJsonProtocol {case class UserInfo(username: String, password: String, appInfo: (String,String))/* 用JWT替代case class AuthToken(access_token: String = java.util.UUID.randomUUID().toString,token_type: String = "bearer",expires_in: Int = 3600)*//* 无需维护这个验证后用户清单了case class AuthUser(credentials: UserInfo,token: AuthToken = new AuthToken(expires_in = 60 * 60 * 8),loggedInAt: String = LocalDateTime.now().toString)val loggedInUsers = mutable.ArrayBuffer.empty[AuthUser]*/val validUsers = Seq(UserInfo("johnny", "p4ssw0rd",("1101", "101")),UserInfo("tiger", "secret", ("1101" , "102")))def getValidUser(credentials: Credentials): Option[UserInfo] =credentials match {case p @ Credentials.Provided(_) =>validUsers.find(user => user.username == p.identifier && p.verify(user.password))case _ => None}/*收到的是JWTdef authenticateUser(credentials: Credentials): Option[(String,String)] =credentials match {case p @ Credentials.Provided(_) =>loggedInUsers.find(user => p.verify(user.token.access_token))case _ => None} */def authenticateJwt(credentials: Credentials): Option[String] =credentials match {case Credentials.Provided(token) =>Jwt.isValid(token,"OpenSesame",Seq(JwtAlgorithm.HS256)) match {case true => Some(token)case _ => None}case _ => None}
/*implicit val fmtCredentials = jsonFormat2(UserInfo.apply)implicit val fmtToken = jsonFormat3(AuthToken.apply)implicit val fmtUser = jsonFormat3(AuthUser.apply)*/
}object Oauth2ServerDemo extends App {implicit val httpSys = ActorSystem("httpSystem")implicit val httpMat = ActorMaterializer()implicit val httpEC = httpSys.dispatcherimport JsonMarshaller._val route =pathEndOrSingleSlash {get {complete("Welcome!")}} ~path("auth") {authenticateBasic(realm = "auth", getValidUser) { user =>post {val claims = JwtClaim() + ("appInfo" , (user.appInfo._1,user.appInfo._2))complete(Jwt.encode(claims,"OpenSesame",JwtAlgorithm.HS256))}}} ~path("api") {authenticateOAuth2(realm = "api", authenticateJwt) { validToken =>val pi = Jwt.decodeRawAll(validToken,"OpenSesame",Seq(JwtAlgorithm.HS256)) match {case Success(parts) => Some(((parse(parts._2).asInstanceOf[JObject]) \ "appInfo").values.asInstanceOf[Map[String,String]].toList.head)case Failure(_) => None}complete(s"It worked! token = $validToken, appInfo = ${pi}")}}val (port, host) = (50081,"192.168.11.189")val bindingFuture = Http().bindAndHandle(route,host,port)println(s"Server running at $host $port. Press any key to exit ...")scala.io.StdIn.readLine()bindingFuture.flatMap(_.unbind()).onComplete(_ => httpSys.terminate())}

下面是客户端测试代码:

import akka.actor._
import akka.stream._
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.headers._
import scala.concurrent._
import akka.http.scaladsl.model._
import pdi.jwt._
import org.json4s._
import org.json4s.jackson.JsonMethods._
import scala.util._
import scala.concurrent.duration._object Oauth2Client  {def main(args: Array[String]): Unit = {implicit val system = ActorSystem()implicit val materializer = ActorMaterializer()// needed for the future flatMap/onComplete in the endimplicit val executionContext = system.dispatcherval helloRequest = HttpRequest(uri = "http://192.168.11.189:50081/")val authorization = headers.Authorization(BasicHttpCredentials("johnny", "p4ssw0rd"))val authRequest = HttpRequest(HttpMethods.POST,uri = "http://192.168.11.189:50081/auth",headers = List(authorization))val futToken: Future[HttpResponse] = Http().singleRequest(authRequest)val respToken = for {resp <- futTokenjstr <- resp.entity.dataBytes.runFold("") {(s,b) => s + b.utf8String}} yield jstrval jstr =  Await.result[String](respToken,2 seconds)println(jstr)scala.io.StdIn.readLine()val parts = Jwt.decodeRawAll(jstr, "OpenSesame", Seq(JwtAlgorithm.HS256)) match {case Failure(exception) => println(s"Error: ${exception.getMessage}")case Success(value) =>val tt: (String,String) = ((parse(value._2).asInstanceOf[JObject]) \ "appInfo").values.asInstanceOf[Map[String,String]].toList.headprintln(tt)}scala.io.StdIn.readLine()val authentication = headers.Authorization(OAuth2BearerToken(jstr))val apiRequest = HttpRequest(HttpMethods.POST,uri = "http://192.168.11.189:50081/api",).addHeader(authentication)val futAuth: Future[HttpResponse] = Http().singleRequest(apiRequest)println(Await.result(futAuth,2 seconds))scala.io.StdIn.readLine()system.terminate()}}

运行后输出结果:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhcHBJbmZvIjp7IjExMDEiOiIxMDEifX0.i46FUinT0n1brYGInFZz-6embOj15SKpIpO9QHkpSZs(1101,101)HttpResponse(200 OK,List(Server: akka-http/10.1.8, Date: Tue, 09 Jul 2019 04:02:12 GMT),HttpEntity.Strict(text/plain; charset=UTF-8,It worked! token = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhcHBJbmZvIjp7IjExMDEiOiIxMDEifX0.i46FUinT0n1brYGInFZz-6embOj15SKpIpO9QHkpSZs, appInfo = Some((1101,101))),HttpProtocol(HTTP/1.1))Process finished with exit code 130 (interrupted by signal 2: SIGINT)

构建环境 build.sbt:

name := "oauth2"version := "0.1"scalaVersion := "2.12.8"libraryDependencies ++= Seq("com.typesafe.akka" %% "akka-http"   % "10.1.8","com.typesafe.akka" %% "akka-stream" % "2.5.23","com.pauldijou" %% "jwt-core" % "3.0.1","de.heikoseeberger" %% "akka-http-json4s" % "1.22.0","org.json4s" %% "json4s-native" % "3.6.1","com.typesafe.akka" %% "akka-http-spray-json" % "10.1.8","com.typesafe.scala-logging" %% "scala-logging" % "3.9.0","org.slf4j" % "slf4j-simple" % "1.7.25","org.json4s" %% "json4s-jackson" % "3.6.7"
)

Akka-CQRS(15)- Http标准安全解决方案:OAuth2+JWT相关推荐

  1. Receiver ED、Link quality indicator (LQI)、Clear channel assessment (CCA)究竟是什么?802.15.4标准

    很多小伙伴都搞不清楚RSSI.ED.LQI.CCA等概念,那我们就看看802.15.4标准文档中的定义吧. 802.15.4标准文档: <IEEE Std 802.15.4™-2015, IEE ...

  2. 可见光成像通信(Optical Camera Communication,OCC)——基于IEEE 802.15.7-2018标准的解读

    之前博客<可见光通信>较为详细的介绍了可见光通信技术.而本博文则是对OCC(optical camera communication)或称可见光成像通信进行深入的介绍 更多本人做的可见光通 ...

  3. PKCS的 15 个标准

    PKCS 目前共发布过 15 个标准: (1)PKCS#1:RSA加密标准.PKCS#1定义了RSA公钥函数的基本格式标准,特别是数字签名.它定义了数字签名如何计算,包括待签名数据和签名本身的格式:它 ...

  4. Spring Security Oauth2 JWT 实现用户认证授权功能

    Spring Security Oauth2 JWT 一 用户认证授权 1. 需求分析 1.1 用户认证与授权 什么是用户身份认证? 用户身份认证即用户去访问系统资源时系统要求验证用户的身份信息,身份 ...

  5. 学成在线-第16天-讲义- Spring Security Oauth2 JWT RSA加解密

    学成在线-第16天-讲义- Spring Security Oauth2 JWT 1 用户认证需求分析 1.1 用户认证与授权 ​ 截至目前,项目已经完成了在线学习功能,用户通过在线学习页面点播视频进 ...

  6. 针对JWT简介与原理,代码实例,以及oauth2+JWT+RSA的集成配置

    JWT简介 JWT 是基于 RFC 7519 标准定义的一种可以安全传输的规范, 这个规范允许我们使用 JWT 在前后端之间传递安全可靠的信息. JWT 由于使用了数字签名,所以是可信任和安全的. 通 ...

  7. 「springcloud 2021 系列」Spring Cloud Gateway + OAuth2 + JWT 实现统一认证与鉴权

    通过认证服务进行统一认证,然后通过网关来统一校验认证和鉴权. 将采用 Nacos 作为注册中心,Gateway 作为网关,使用 nimbus-jose-jwt JWT 库操作 JWT 令牌 理论介绍 ...

  8. Android HTTP2 + Oauth2 + Jwt 接口认证实例

    2019独角兽企业重金招聘Python工程师标准>>> 本文节选自<Netkiller Java 手札> Netkiller Java 手札 Mr. Neo Chan,  ...

  9. Spring boot 2.0 with Oauth2 + Jwt

    2019独角兽企业重金招聘Python工程师标准>>> Spring boot 2.0 with Oauth2 + Jwt 在这篇文章中: Netkiller Spring Clou ...

  10. Spring Cloud OAuth2 JWT 微服务认证服务器得构建

    文章目录 Spring Cloud OAuth2 JWT 微服务认证服务器得构建 前言 认证服务得搭建 `AuthorizationServer` `WebSecurityConfig` `Autho ...

最新文章

  1. 派工单系统 源码_「青鸟报修云」酒店设备报修管理系统
  2. 36岁自学python_Python语言基础
  3. python快速编程入门课本中的名片管理器_python实现名片管理器的示例代码
  4. Apache JMeter 测试 HTTP接口
  5. buildroot自带程序(库)编译并安装
  6. maven-shade-plugin插件将项目打成可执行的jar包
  7. 使用java jpcap实现网络嗅探器
  8. 「雕爷学编程」Arduino动手做(32)——雨滴传感器模块
  9. 如何使用Aspose.pdf读取 增值税发票pdf文件内容 和 解二维码
  10. cad完全卸载教程_怎么把CAD卸载干净,老司机来教你
  11. 五类、超五、六类、超六类网线
  12. 图像坐标球面投影_图像の球面投影算法
  13. BZOJ4567[Scoi2016]背单词
  14. CH343芯片应用—硬件设计指南
  15. 2022081班李亚楠20220914
  16. BZOJ 1909 Berth Allocation
  17. Selenium基础 — 多窗口操作
  18. 网站统计IP PV UV实现原理
  19. MetaTrader LiveUpdate 概览
  20. Qt 字符串合成语音并播放(QTextToSpeech)

热门文章

  1. 孪生网络图像相似度_CVPR-2019 更深更宽的孪生网络实时追踪
  2. 最新版 银图/网银/MOMO模拟按键/Photoshop图像处理
  3. 广数系统加工中心编程_数控铣床与数控加工中心操作与编程
  4. 音频信号频率测试软件,音频测试信号频率详细解说
  5. win7 计算机定时关机脚本,win7定时关机命令是什么 如何设置定时关机【图解】...
  6. 服务器安装、卸载宝塔面板
  7. 电脑小技巧:怎么取消电脑开机密码
  8. Part I. S2. 直觉模糊集理论
  9. MATLAB制作简易小动画入门详解
  10. SCM供应链管理系统介绍:企业SCM供应链系统应用领域、优势、功能详解