Rust Web Rokcet 系列 3 使用 JWT

JWT即使用的包是jsonwebtoken。是使用token进行验证的一种方式。基于token的认证方式相比传统的session认证方式相比节约服务器资源。 JWT 官网 https://jwt.io/

本次使用的包是jsonwebtoken。代码基于本系列第二篇文章 ,系列地址 https://www.ftls.xyz/series/rust-web-rokcet/

Cargo.toml 增加依赖

1
jsonwebtoken = "8.0.1"

src\module.rs 增加代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#[derive(Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
pub struct Claims {
    pub sub: String,
    pub exp: u64,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(crate = "rocket::serde")]
pub struct UserAuth {
    pub id: i32,
    pub key: String,
}

增加 src\auth.rs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
use crate::module::Claims;
use jsonwebtoken::{decode, DecodingKey, Validation};
use rocket::http::Status;
use rocket::request::{FromRequest, Outcome};

pub const KEY: &[u8] = b"secret";

pub struct Token;
// Bearer Token
impl Token {
    fn from_request(header: &str) -> Option<Token> {
        let split_vec = header.split_whitespace().collect::<Vec<_>>();
        if split_vec.len() != 2 {
            return None;
        }
        if split_vec[0] != "Bearer" {
            return None;
        }
        Self::from_jwt(split_vec[1])
    }
    fn from_jwt(token_string: &str) -> Option<Token> {
        let mut val = Validation::default();
        val.sub = Some("!Yg43#xQtBE357js".to_string());
        match decode::<Claims>(token_string, &DecodingKey::from_secret(KEY), &val) {
            Ok(c) => {
                println!("ExpTime:{:?}", c.claims.exp);
                return Some(Token);
            }
            Err(_) => None,
        }
    }
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for Token {
    type Error = ();
    async fn from_request(request: &'r rocket::Request<'_>) -> Outcome<Self, Self::Error> {
        let header_auth = request.headers().get_one("Authorization");
        if let Some(header_auth) = header_auth {
            if let Some(auth) = Self::from_request(header_auth) {
                return Outcome::Success(auth);
            }
        }
        Outcome::Failure((Status::Unauthorized, ()))
    }
}

src\routes.rs 增加代码: 其他路由使用 Token 验证只需要在函数中添加变量 _auth: Token 即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
use crate::auth::{Token, KEY};
use crate::module::{Claims, UserAuth};
use jsonwebtoken::{encode, EncodingKey, Header};
use std::time::{SystemTime, UNIX_EPOCH};

// get token
#[post("/token", format = "json", data = "<user_auth>")]
pub async fn get_token(user_auth: Json<UserAuth>) -> Value {
    let user = user_auth.into_inner();
    if user.id == 0 && user.key.eq("oR66T*W8y4VaXkh#rTjeZ$$Rby$NCy!nJX") {
        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();
        let token = match encode(
            &Header::default(),
            &Claims {
                sub: String::from("!Yg43#xQtBE357js"),
                exp: timestamp + 5,
            },
            &EncodingKey::from_secret(KEY),
        ) {
            Ok(t) => t,
            Err(_) => panic!(),
        };
        json!({ "token": token })
    } else {
        json!({"token": "Auth Fail"})
    }
}

// get token test
#[get("/token/test")]
pub async fn get_token_test(_auth: Token) -> Value {
    json!({"status":"Auth Success"})
}

src\main.rs 增加

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

mod auth;

#[launch]
fn rocket() -> _ {
    rocket::build()
        // database
        .attach(MainDbConn::fairing())
        .mount("/", routes![index])
        // add api
        .mount("/", routes![get_all_articles, get_article_by_id])
        .mount("/", routes![post_article, put_article])
        .mount("/", routes![delete_all_articles, delete_article])
        // token 本文新增的
        .mount("/", routes![get_token, get_token_test])
}

测试 http://127.0.0.1:8000/token/test 方法,

该请求的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const gettoken = {
    url: ' http://127.0.0.1:8000/token',
    method: "POST",
    header: 'Content-Type: application/json',
    body: {
        mode: 'raw', 
    raw: JSON.stringify({"id": 0, "key": "oR66T*W8y4VaXkh#rTjeZ$$Rby$NCy!nJX"}) 
		}
}
pm.sendRequest(gettoken, function (err, response) {
    console.log(response.json().token);
    pm.collectionVariables.set("token",response.json().token);
});

在该请求的 Header 中添加键Authorization ,值 Bearer {{token}}。

https://cdn.ftls.xyz/images/2022/02/20220216132819.png
Postman测试

这之后就可以正常请求了。

本系列 Postman 分享链接:

https://www.getpostman.com/collections/c89ec512876818f18757

如果链接失效了,请只会一声。或 teach.postman_collection.json 如下

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
{
	"info": {
		"_postman_id": "709bf03a-bdd4-4b98-afac-b01ae46d2b65",
		"name": "teach",
		"schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json",
		"_exporter_id": "16599952"
	},
	"item": [
		{
			"name": "http://127.0.0.1:8000/",
			"request": {
				"method": "GET",
				"header": [],
				"url": "http://127.0.0.1:8000/"
			},
			"response": []
		},
		{
			"name": "获取所有文章",
			"request": {
				"method": "GET",
				"header": [],
				"url": "http://127.0.0.1:8000/article"
			},
			"response": []
		},
		{
			"name": "增加文章",
			"request": {
				"method": "POST",
				"header": [],
				"body": {
					"mode": "raw",
					"raw": "{\r\n    \"title\": \"a title\",\r\n    \"author\": \"恐咖兵糖\",\r\n    \"content\": \"dasdaadas\",\r\n    \"created_at\": \"2022-02-14 \"\r\n}",
					"options": {
						"raw": {
							"language": "json"
						}
					}
				},
				"url": "http://127.0.0.1:8000/article"
			},
			"response": []
		},
		{
			"name": "获取文章 by id",
			"request": {
				"method": "GET",
				"header": [],
				"url": "http://127.0.0.1:8000/article/2"
			},
			"response": []
		},
		{
			"name": "删除文章 by id",
			"request": {
				"method": "DELETE",
				"header": [],
				"url": "http://127.0.0.1:8000/article/3"
			},
			"response": []
		},
		{
			"name": "更新文章",
			"request": {
				"method": "PUT",
				"header": [],
				"body": {
					"mode": "raw",
					"raw": "{\r\n    \"title\": \"a title\",\r\n    \"author\": \"恐咖兵糖\",\r\n    \"content\": \"222ssss2\",\r\n    \"created_at\": \"2022-02-14 \"\r\n}",
					"options": {
						"raw": {
							"language": "json"
						}
					}
				},
				"url": "http://127.0.0.1:8000/article/4"
			},
			"response": []
		},
		{
			"name": "删除所有文章",
			"request": {
				"method": "DELETE",
				"header": [],
				"url": "http://127.0.0.1:8000/article/all"
			},
			"response": []
		},
		{
			"name": "获取token",
			"request": {
				"method": "POST",
				"header": [],
				"body": {
					"mode": "raw",
					"raw": "{\r\n    \"id\": 0,\r\n    \"key\": \"oR66T*W8y4VaXkh#rTjeZ$$Rby$NCy!nJX\"\r\n}",
					"options": {
						"raw": {
							"language": "json"
						}
					}
				},
				"url": "http://127.0.0.1:8000/token"
			},
			"response": []
		},
		{
			"name": "token 测试",
			"event": [
				{
					"listen": "prerequest",
					"script": {
						"exec": [
							"const gettoken = {\r",
							"    url: ' http://127.0.0.1:8000/token',\r",
							"    method: \"POST\",\r",
							"    header: 'Content-Type: application/json',\r",
							"    body: {\r",
							"        mode: 'raw', \r",
							"    raw: JSON.stringify({\"id\": 0, \"key\": \"oR66T*W8y4VaXkh#rTjeZ$$Rby$NCy!nJX\"}) //要将JSON对象转为文本发送\r",
							"\r",
							"    }\r",
							"}\r",
							"pm.sendRequest(gettoken, function (err, response) {\r",
							"    console.log(response.json().token);\r",
							"    pm.collectionVariables.set(\"token\",response.json().token);\r",
							"});"
						],
						"type": "text/javascript"
					}
				}
			],
			"request": {
				"method": "GET",
				"header": [
					{
						"key": "Authorization",
						"value": "Bearer {{token}}",
						"type": "default"
					}
				],
				"url": "http://127.0.0.1:8000/token/test"
			},
			"response": []
		}
	],
	"variable": [
		{
			"key": "token",
			"value": ""
		}
	]
}