@zhongdao
2022-03-10T16:34:08.000000Z
字数 12971
阅读 1329
未分类
公钥加密的机制:只有拥有私钥的人才能解开
私钥数字签名的机制: 所有人都可以通过对方公钥验证信息来自对方
总结:
用公钥加密,公共传输,私钥解密
反过来,用私钥签名,和数据一起公共传输,公钥验证签名
签名与验证展示:
签名与验证过程:
对消息的散列值进行签名:
公钥密码算法比较慢, 通过生成消息的散列值,然后再签名, 比较轻松.
对消息的散列值签名和验证的时间过程:
用于加密内容的秘钥和用于加密秘钥的秘钥:
混合密码加解密示意:
用混合密码加密过程:
用混合密码解密过程:
https://www.sohamkamani.com/golang/rsa-encryption/
rsa.go
https://gist.github.com/sohamkamani/08377222d5e3e6bc130827f83b0c073e
https://tutorialedge.net/golang/authenticating-golang-rest-api-with-jwts/
彻底搞懂 Cookie、Session、Token、JWT
https://www.jianshu.com/p/6623416161ff
什么是认证(Authentication)
通俗地讲就是验证当前用户的身份,证明“你是你自己”
什么是授权(Authorization)
用户授予第三方应用访问该用户某些资源的权限
实现授权的方式有:cookie、session、token、OAuth
什么是凭证(Credentials)
实现认证和授权的前提是需要一种媒介(证书)来标记访问者的身份。
一般网站(如掘金)会有两种模式,游客模式和登录模式。游客模式下,可以正常浏览网站上面的文章,一旦想要点赞/收藏/分享文章,就需要登录或者注册账号。当用户登录成功后,服务器会给该用户使用的浏览器颁发一个令牌(token),这个令牌用来表明你的身份,每次浏览器发送请求时会带上这个令牌,就可以使用游客模式下无法使用的功能。
什么是 Token(令牌)
Acesss Token
访问资源接口(API)时所需要的资源凭证。
简单 token 的组成: uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)。
Refresh Token
另外一种 token——refresh token
refresh token 是专用于刷新 access token 的 token。如果没有 refresh token,也可以刷新 access token,但每次刷新都要用户输入登录用户名与密码,会很麻烦。有了 refresh token,可以减少这个麻烦,客户端直接用 refresh token 去更新 access token,无需用户进行额外的操作。
https://developer.okta.com/blog/2017/08/17/why-jwts-suck-as-session-tokens
JWT 对大多数网站登录验证然后数据库增删改查的网站都没用。然而,话虽如此,在某些情况下 JWT 会很有用。
If you’re building API services that need to support server-to-server or client-to-server (like a mobile app or single page app (SPA)) communication, using JWTs as your API tokens is a very smart idea. In this scenario:
如果您正在构建需要支持服务器到服务器或客户端到服务器(如移动应用程序或单页应用程序 (SPA))通信的 API 服务,那么使用 JWT 作为 API 令牌是一个非常聪明的想法。在这种情况下:
您将拥有一个身份验证 API,客户端对其进行身份验证,并取回 JWT
客户端然后使用此 JWT 将经过身份验证的请求发送到其他 API 服务
这些其他 API 服务使用客户端的 JWT 来验证客户端是否可信,并且可以执行某些操作而无需执行网络验证
For these types of API services, JWTs make perfect sense because clients will be making requests frequently, with limited scope, and usually authentication data can be persisted in a stateless way without too much dependence on user data.
If you’re building any type of service where you need three or more parties involved in a request, JWTs can also be useful. In this case the requesting party will have a token to prove their identity, and can forward it to the third (or 4th … nth) service without needing to incur a real-time validation each and every time.
https://blog.angular-university.io/angular-jwt/
JWT 的目标是使服务器无状态吗?
使服务器无状态是一个很好的副作用,但 JWT 的主要好处是发出 JWT 的身份验证服务器和验证 JWT 的应用程序服务器可以是两个完全独立的服务器。
这意味着在应用服务器级别只需要一些最小的身份验证逻辑——我们只需要检查 JWT!
完整的应用程序集群可以将登录/注册委托给单个身份验证服务器。
这意味着应用程序服务器更简单、更安全,因为许多身份验证功能都集中在身份验证服务器上并在应用程序之间重用。
https://blog.nextzy.me/implementing-json-web-token-jwt-to-secure-your-app-c8e1bd6f6a29
JSON Web Token 深入介绍
https://www.bezkoder.com/jwt-json-web-token/
身份验证是几乎应用程序中最重要的部分之一,从桌面应用程序到 Web 应用程序或移动应用程序。本 JWT 教程是 JSON Web Token 的深入介绍,可帮助您了解:
基于会话的身份验证与基于令牌的身份验证(JWT 诞生的原因)
JWT 的工作原理。
如何创建 JWT。
我们如何保护我们的应用程序并验证 JWT。
使用任何网站、移动应用程序或桌面应用程序……您几乎需要创建一个帐户,然后使用它登录以访问应用程序的功能。我们称该动作为Authentication。
那么,如何验证账户呢?
首先,我们来看看流行网站过去使用的一种简单方法:基于会话的身份验证。
在上图中,当用户登录网站时,服务器Session将为该用户生成一个并将其存储(在内存或数据库中)。Server 还返回一个SessionId供Client将其保存在 Browser Cookie中。
服务器上的会话有一个过期时间。在那之后,此会话已过期,用户必须重新登录才能创建另一个会话。
如果用户已登录且 Session 尚未过期,则 Cookie(包括 SessionId)始终与所有对服务器的 HTTP 请求一起使用。服务器会将其SessionId与存储的进行比较以Session进行身份验证并返回相应的响应。
没关系。但是为什么我们需要基于令牌的身份验证?
答案是我们不只有网站,那里有很多平台。
假设我们有一个与 Session 配合得很好的网站。有一天,我们想为移动(本地应用程序)实现系统,并使用与当前 Web 应用程序相同的数据库。我们应该做什么?我们无法使用基于会话的身份验证来对使用 Native App 的用户进行身份验证,因为这些类型没有 Cookie。
我们是否应该构建另一个支持 Native Apps 的后端项目?
或者我们应该为 Native App 用户编写一个身份验证模块?
这就是基于令牌的身份验证诞生的原因。
使用这种方法,服务器将用户登录状态编码为JSON Web Token (JWT) 并发送给客户端。现在许多 RESTful API 都使用它。让我们进入下一部分,我们将知道它是如何工作的。
看下面的流程:
可以看到它很容易理解。服务器Session没有创建一个,而是从用户登录数据生成一个并将其发送给客户端。客户端保存并且从现在开始,客户端的每个请求都应附加(通常在header处)。服务器将验证 JWT 并返回响应
对于在客户端存储 JWT,这取决于您使用的平台:
For storing JWT on Client side, it depends on the platform you use:
Browser: Local Storage
IOS: Keychain
Android: SharedPreferences
这是基于令牌的身份验证流程的概述。
首先,您应该了解 JWT 的三个重要部分:
标题Header
有效载荷
签名
Header 回答了这个问题:我们将如何计算 JWT?
现在看一个例子header,它是一个像这样的 JSON 对象:
{
"typ": "JWT",
"alg": "HS256"
}
– typis 'type',表示这里的Token类型是JWT。
–alg代表“算法”,它是用于生成 Token 的哈希算法signature。在上面的代码中,HS256是 HMAC-SHA256 - 使用Secret Key的算法。
Payload 帮助我们回答:我们想在 JWT 中存储什么?
这是一个有效载荷示例:
{
"userId": "abcd12345ghijk",
"username": "bezkoder",
"email": "contact@bezkoder.com",
// standard fields
"iss": "zKoder, author of bezkoder.com",
"iat": 1570238918,
"exp": 1570238992
}
在上面的 JSON 对象中,我们存储了 3 个用户字段:userId、username、email。您可以保存所需的任何字段。
我们也有一些标准字段。它们是可选的。
iss (Issuer):谁发行 JWT
iat (发布时间):JWT 的发布时间
exp (过期时间):JWT 过期时间
您可以在以下位置查看更多标准字段:
https ://en.wikipedia.org/wiki/JSON_Web_Token#Standard_fields
这部分是我们使用我上面告诉你的哈希算法的地方。
查看下面获取签名的代码:
const data = Base64UrlEncode(header) + '.' + Base64UrlEncode(payload);
const hashedData = Hash(data, secret);
const signature = Base64UrlEncode(hashedData);
让我们解释一下。
– 首先,我们对 Header 和 Payload 进行编码,用点连接它们.
- 接下来,我们使用字符串对datausing Hash 算法(在 Header 中定义)进行哈希处理。– 最后,我们对哈希结果进行编码以获得Signature。secret
在有了 Header、Payload、Signature 之后,我们将它们组合成 JWT 标准结构:header.payload.signature.
const encodedHeader = base64urlEncode(header);
/* Result */
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"
const encodedPayload = base64urlEncode(payload);
/* Result */
"eyJ1c2VySWQiOiJhYmNkMTIzNDVnaGlqayIsInVzZXJuYW1lIjoiYmV6a29kZXIiLCJlbWFpbCI6ImNvbnRhY3RAYmV6a29kZXIuY29tIn0"
const data = encodedHeader + "." + encodedPayload;
const hashedData = Hash(data, secret);
const signature = base64urlEncode(hashedData);
/* Result */
"crrCKWNGay10ZYbzNG3e0hfLKbL7ktolT7GqjUMwi3k"
// header.payload.signature
const JWT = encodedHeader + "." + encodedPayload + "." + signature;
/* Result */
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJhYmNkMTIzNDVnaGlqayIsInVzZXJuYW1lIjoiYmV6a29kZXIiLCJlbWFpbCI6ImNvbnRhY3RAYmV6a29kZXIuY29tIn0.5IN4qmZTS3LEaXCisfJQhrSyhSPXEgM1ux-qXsGKacQ"
JWT 根本不会隐藏、隐藏、保护数据。可以看到生成 JWT(Header, Payload, Signature)的过程只对数据进行编码和哈希处理,而不是对数据进行加密。
JWT 的目的是证明数据是由真实来源生成的。
那么,如果有中间人攻击可以获取JWT,然后解码用户信息呢?是的,这是可能的,因此请始终确保您的应用程序具有 HTTPS 加密。
在上一节中,我们使用Secret字符串来创建Signature。这个Secret字符串对于每个应用程序都是唯一的,并且必须安全地存储在服务器端。
When receiving JWT from Client, the Server get the Signature, verify that the Signature is correctly hashed by the same algorithm and Secret string as above. If it matches the Server’s signature, the JWT is valid.
当从 Client 接收 JWT 时,Server 获取 Signature,验证 Signature 是否通过与上述相同的算法和 Secret 字符串的正确散列相同。如果它与服务器的签名匹配,则 JWT 是有效的。
Experienced programmers can still add or edit Payload information when sending it to the server. What should we do in this case?
We store the Token before sending it to the Client. It can ensure that the JWT transmitted later by the Client is valid.
In addition, saving the user’s Token on the Server will also benefit the Force Logout feature from the system.
有经验的程序员在发送到服务器时仍然可以添加或编辑Payload信息。在这种情况下我们应该怎么做?
我们在将 Token 发送给客户端之前存储它。可以保证Client后面传输的JWT是有效的。
此外,将用户的 Token 保存在服务器上也将有利于系统的强制注销功能。
对于您希望跨多个平台扩展到大量用户的应用程序,首选 JWT 身份验证,因为 Token 将存储在客户端。
https://github.com/appleboy/gin-jwt
https://github.com/appleboy/gin-jwt/blob/master/_example/basic/server.go
go run server.go
go run server.go
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] POST /login --> github.com/appleboy/gin-jwt/v2.(*GinJWTMiddleware).LoginHandler-fm (3 handlers)
[GIN-debug] GET /auth/refresh_token --> github.com/appleboy/gin-jwt/v2.(*GinJWTMiddleware).RefreshHandler-fm (3 handlers)
[GIN-debug] GET /auth/hello --> main.helloHandler (4 handlers)
apt install httpie
客户端admin登录, server 返回expire 和 token
#http -v --json POST localhost:8000/login username=admin password=admin
POST /login HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 42
Content-Type: application/json
Host: localhost:8000
User-Agent: HTTPie/0.9.2
{
"password": "admin",
"username": "admin"
}
HTTP/1.1 200 OK
Content-Length: 212
Content-Type: application/json; charset=utf-8
Date: Mon, 24 Jan 2022 06:43:08 GMT
{
"code": 200,
"expire": "2022-01-24T15:43:08+08:00",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDMwMTAxODgsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTY0MzAwNjU4OH0.qw064sIQeR1GSR8vrx6lPDw_X6BWoy4lSR5wCiQDPpA"
}
客户端用一个非法的token来访问api: /auth/refresh_token ,Server返回非法token和401 Unauthorized
http -v -f GET localhost:8000/auth/refresh_token "Authorization:Bearer xxxxxxxxx" "Content-Type: application/json"
GET /auth/refresh_token HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Bearer xxxxxxxxx
Connection: keep-alive
Content-Type: application/json
Host: localhost:8000
User-Agent: HTTPie/0.9.2
HTTP/1.1 401 Unauthorized
Content-Length: 69
Content-Type: application/json; charset=utf-8
Date: Mon, 24 Jan 2022 06:44:56 GMT
Www-Authenticate: JWT realm=test zone
{
"code": 401,
"message": "token contains an invalid number of segments"
}
将客户端的Authorization中的token换成第一次Server返回的token字符串,Server返回正常
# http -v -f GET localhost:8000/auth/refresh_token "Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDMwMTAxODgsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTY0MzAwNjU4OH0.qw064sIQeR1GSR8vrx6lPDw_X6BWoy4lSR5wCiQDPpA" "Content-Type: application/json"
GET /auth/refresh_token HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDMwMTAxODgsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTY0MzAwNjU4OH0.qw064sIQeR1GSR8vrx6lPDw_X6BWoy4lSR5wCiQDPpA
Connection: keep-alive
Content-Type: application/json
Host: localhost:8000
User-Agent: HTTPie/0.9.2
HTTP/1.1 200 OK
Content-Length: 212
Content-Type: application/json; charset=utf-8
Date: Mon, 24 Jan 2022 06:45:22 GMT
{
"code": 200,
"expire": "2022-01-24T15:45:22+08:00",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDMwMTAzMjIsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTY0MzAwNjcyMn0.yC0uEaYW18d36fHPYJeECdJHCK1_0rkdfsxo-1e40wc"
}
过期之后,用同样的token访问,提示 Expired过期, 所以验证失败401 Unauthorized
http -v -f GET localhost:8000/auth/refresh_token "Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDMwMTAxODgsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTY0MzAwNjU4OH0.qw064sIQeR1GSR8vrx6lPDw_X6BWoy4lSR5wCiQDPpA" "Content-Type: application/json"
GET /auth/refresh_token HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDMwMTAxODgsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTY0MzAwNjU4OH0.qw064sIQeR1GSR8vrx6lPDw_X6BWoy4lSR5wCiQDPpA
Connection: keep-alive
Content-Type: application/json
Host: localhost:8000
User-Agent: HTTPie/0.9.2
HTTP/1.1 401 Unauthorized
Content-Length: 41
Content-Type: application/json; charset=utf-8
Date: Mon, 24 Jan 2022 08:52:04 GMT
Www-Authenticate: JWT realm=test zone
{
"code": 401,
"message": "token is expired"
}
重新用admin用户名密码登录, Server返回新的一个Token和新的过期时间Expire
# http -v --json POST localhost:8000/login username=admin password=admin POST /login HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 42
Content-Type: application/json
Host: localhost:8000
User-Agent: HTTPie/0.9.2
{
"password": "admin",
"username": "admin"
}
HTTP/1.1 200 OK
Content-Length: 212
Content-Type: application/json; charset=utf-8
Date: Mon, 24 Jan 2022 08:54:43 GMT
{
"code": 200,
"expire": "2022-01-24T17:54:43+08:00",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDMwMTgwODMsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTY0MzAxNDQ4M30.4T2_4DUg56NJUiLDOW6d4BTnqyO3a0Mar2s8F9Tf8Jw"
}
用新得到的token放在Authorization字段里,这次访问hello接口,授权得到验证通过,api正常访问
#http -v -f GET localhost:8000/auth/hello "Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDMwMTgwODMsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTY0MzAxNDQ4M30.4T2_4DUg56NJUiLDOW6d4BTnqyO3a0Mar2s8F9Tf8Jw" "Content-Type: application/json"
GET /auth/hello HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDMwMTgwODMsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTY0MzAxNDQ4M30.4T2_4DUg56NJUiLDOW6d4BTnqyO3a0Mar2s8F9Tf8Jw
Connection: keep-alive
Content-Type: application/json
Host: localhost:8000
User-Agent: HTTPie/0.9.2
HTTP/1.1 200 OK
Content-Length: 59
Content-Type: application/json; charset=utf-8
Date: Mon, 24 Jan 2022 09:08:48 GMT
{
"text": "Hello World.",
"userID": "admin",
"userName": "admin"
}
用test登录,然后将其token替换到Authorization中, 然后再访问hello接口,因为test用户对hello没有权限,所以提示没权限
#http -v --json POST localhost:8000/login username=test password=test
#http -f GET localhost:8000/auth/hello "Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDMwMTkxNTUsImlkIjoidGVzdCIsIm9yaWdfaWF0IjoxNjQzMDE1NTU1fQ.CZY5U6Of0TUykypjy-LmmmDgEp6ZL0iif9cpClEEjtk" "Content-Type: application/json"
HTTP/1.1 403 Forbidden
Content-Length: 74
Content-Type: application/json; charset=utf-8
Date: Mon, 24 Jan 2022 09:15:55 GMT
Www-Authenticate: JWT realm=test zone
{
"code": 403,
"message": "you don't have permission to access this resource"
}
JWT Middleware for Gin Framework
https://github.com/appleboy/gin-jwt
Using JWT for Authentication in a Golang Application
https://learn.vonage.com/blog/2020/03/13/using-jwt-for-authentication-in-a-golang-application-dr/
在 Golang 中使用 gin 进行 JWT 身份验证
https://medium.com/wesionary-team/jwt-authentication-in-golang-with-gin-63dbc0816d55
在 Golang 中动手操作 JWT
https://betterprogramming.pub/hands-on-with-jwt-in-golang-8c986d1bb4c0
http.Handle 和 http.HandleFunc 的区别?
https://stackoverflow.com/questions/21957455/difference-between-http-handle-and-http-handlefunc