add simplejwt source code

This commit is contained in:
taoky
2020-11-05 17:08:28 +08:00
parent ae537004e4
commit 93b323ee0c
9 changed files with 12333 additions and 0 deletions
@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICwDCCAmagAwIBAgIUHo/UysuMf4+WAcChW3hM1NHDQDkwCgYIKoZIzj0EAwIw
gbYxCzAJBgNVBAYTAkNOMQ4wDAYDVQQIDAVBbmh1aTEOMAwGA1UEBwwFSGVmZWkx
HjAcBgNVBAoMFVVTVEMgTGludXggVXNlciBHcm91cDEhMB8GA1UECwwYVGhlIEhh
Y2tlcmdhbWUgQ29tbWl0dGVlMR0wGwYDVQQDDBRoYWNrLmx1Zy51c3RjLmVkdS5j
bjElMCMGCSqGSIb3DQEJARYWaGFja2VyZ2FtZUB1c3RjbHVnLm9yZzAeFw0yMDEw
MDgxNTIwMzZaFw0yMTEwMDgxNTIwMzZaMIG2MQswCQYDVQQGEwJDTjEOMAwGA1UE
CAwFQW5odWkxDjAMBgNVBAcMBUhlZmVpMR4wHAYDVQQKDBVVU1RDIExpbnV4IFVz
ZXIgR3JvdXAxITAfBgNVBAsMGFRoZSBIYWNrZXJnYW1lIENvbW1pdHRlZTEdMBsG
A1UEAwwUaGFjay5sdWcudXN0Yy5lZHUuY24xJTAjBgkqhkiG9w0BCQEWFmhhY2tl
cmdhbWVAdXN0Y2x1Zy5vcmcwVjAQBgcqhkjOPQIBBgUrgQQACgNCAAQcAwLQKJf1
W8podrvryfX7GI3xEneVlEriKNh7ltLe35BfU405TSKVXOKZfxdukiUwKZUPOI4o
zqsAfklnl/Jco1MwUTAdBgNVHQ4EFgQUnHTMWSWk9BgfVDphGi/VhTH5zOYwHwYD
VR0jBBgwFoAUnHTMWSWk9BgfVDphGi/VhTH5zOYwDwYDVR0TAQH/BAUwAwEB/zAK
BggqhkjOPQQDAgNIADBFAiAtkdkJ6vO1abjJt5jCnRDHCnGHTPetOtAQ12Fr1fo1
eQIhAO4T9HjFgSAP5Bg/bzeTS5jGvZAnN14e2sjllR6392J8
-----END CERTIFICATE-----
@@ -0,0 +1,97 @@
<!DOCTYPE html>
<html>
<head>
<title>普通的身份认证器</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="static/user.css">
</head>
<body>
<noscript>
Although not necessary...
It's recommended to enable JavaScript to solve this problem.
</noscript>
<div id="app">
<input v-model.trim="token" placeholder="在这里输入比赛 token...">
<button @click='login'>以 Guest 登录</button>
<button @click='getProfile'>获取用户信息</button>
<div class="text-block">
<p>{{ message }}</p>
</div>
</div>
<!-- Powered by FastAPI, Axios and Vue.js -->
<script src="static/axios.min.js"></script>
<script src="static/vue.js"></script>
<script>
var app = new Vue({
el: '#app',
data: {
message: '请点击登录...',
jwt: null,
token: null,
},
methods: {
login: function () {
if (this.token) {
axios.post('/token', null, {
headers: {
"Hg-Token": this.token
}
}).then(
response => {
this.jwt = response.data.access_token
this.message = '登录成功。'
}
).catch(
error => {
this.message = this.errorOutput(error);
}
)
} else {
this.message = '请输入你的比赛 token。';
}
},
getProfile: function () {
if (!this.jwt) {
this.message = '请先登录。';
}
else if (this.token) {
axios.get('/profile', {
headers: {
Authorization: 'Bearer ' + this.jwt,
"Hg-Token": this.token
}
}).then(
response => {
this.message = response.data;
}
).catch(
error => {
this.message = this.errorOutput(error);
}
);
} else {
this.message = '请输入你的比赛 token。';
}
},
errorOutput: function (error) {
if (error.response) {
return error.response.data.detail + "(" + error.response.status + ")";
} else if (error.request) {
return "未能连接到后端服务器。";
} else {
return "未知错误: " + error.message;
}
}
},
mounted: function () {
const token = new URLSearchParams(window.location.search).get("token");
window.history.replaceState({}, null, '/');
if (token) {
this.token = token;
}
}
});
</script>
</body>
</html>
@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKwIBAAKCAgEAn/KiHQ+/zwE7kY/Xf89PY6SowSb7CUk2b+lSVqC9u+R4BaE/
5tNFeNlneGNny6fQhCRA+Pdw1UJSnNpG26z/uOK8+H7fMb2Da5t/94wavw410sCK
Vbvfft8gKquUaeq//tp20BETeS5MWIXp5EXCE+lEdAHgmWWoMVMIOXwaKTMnCVGJ
2SRr+xH9147FZqOa/17PYIIHuUDlfeGi+Iu7T6a+QZ0tvmHL6j9Onk/EEONuUDfE
lonYM688jhuAM/FSLfMzdyk23mJk3CKPah48nzVmb1YRyfBWiVFGYQqMCBnWgoGO
anpd46Fp1ff1zBn4sZTfPSOus/+00D5Lxh6bsbRa6A1vAApfmTcu026lIb7gbG7D
U1/seDId9s1qA5BJpzWFKO4ztkPGvPTUok8hQBMDaSH1JOoFQgfJIfC7w2CQe+Kb
odQL3akKQDCZhcoA4tf5VC6ODJpFxCn6blML5cD6veOBPJiIk8DBRgmt2AHzOUju
+5nsQcplOVxW5TFYxLqeJ8FPWqQcVekZ749FjchtAwPlUsoWIH0PTSun38ua8usr
wTXbpBlf4r0wz22FPqaecvp7z6Rj/xfDauDGDSU4hmn/TY9Fr+OmFJPW/9k2RAv7
KEFvFCLP/3U3r0FMwSe/FPHmt5fjAtsGlZLj+bZsgwFllYeD90VQU8Ds+KkCAwEA
AQKCAgEAm7GhYEyeVzBJ/e1Yxg7Uppf3tNzu7CEaHmGuRqj9R9KjikdmXpg1PefS
MnwA41sdPCPWIwMqEE0ZAZnv85I48o2ziOhm9pIVRT/+lscImgWJGvdVMRNKAMPN
Gfwe4eMitT+O/AZDQGhy5JCmFhBZVOxyN8JEEM3FpQ3AKcTF6mjijM2UM4yil6jx
wUw2lyyWihKOJtEsF4Y2XgyviOTrliMne9M2XeXwirrJGo1mu1HHZcqDUE4p6FWh
ymq2ptk3N4dPB6VUxbBdoXL6yz9Xn1ppteLAOiP6+Hvm2DteB9yu1CasrLUkg5nx
3dgaqWx2itXxYCBFxO0Z4sNhGx9cSVq3mq1ip0AKzH7sAeCKjXFJtA8HxcnHcECn
jRs91X9eXu4DHMga2o+JkyBde1IHWTVX3XPdeuMfQk67s1AOFEZRNXop0CrGif+9
/FHxn16F7MGoIer2itBo5qVZg3kvYyaPB4ro25kS9y2nNQ3BPO4UdGBPdwWJ3S9N
xDMmwcZKYkwe802ft5hnJ1PKLrYDYy3DkK+AOqV/0fO2p1JylLammtVtlbLkKgsO
cwe8nAKNnpS8IowB2y29cvQHme5et303p7aWoqYZE8Epc5hTHcTM70hrFtPYU1Ae
1PlNa7PCY33By9BTYPKGUlcw9upfE7oCS2DybgPnvbyNpBDs0kkCggEBANCOTgpH
dgX1LrzsqBJBbVTM5fytUQ3sXgvzzbmOLtjWa8rL+5HGAkqACT8aV+faKSKt8YIu
TUdAOkoGjBvikT0nbV7OS4srxgRa+qzVT4nhOn+4q1WSk1eArP98lHrYPXwNgGNv
g7XWiW1IClvHGjq8AWYCX7ky2jps0JsvxMTZcTkMit9WKVLKTdzUeU047S6l0QS8
aKjO0HQVYCBcmzmOMyFwtgaxbYsLc/eux09ttTAhoLaud4brDb5IBbjmssHvoJak
FTwuMZgKN6BSZWGY6NOvxJpHausFPuSoJorw2ZwTqjsgzfegNN5N8FPUKSISdBBB
F7cjfYU8467W1NMCggEBAMRViGRC/jJIewUadl494Qnj3MTAsv6pys4phwI8/RsV
VjCFFAz7deyotfciw6Co4y96D9fZd57P9Kflz3tjC9Zjdqkb6Jv64zONdV1xfg6Z
XT2cfB1/PBN5jA+tOWGZwKjzs1wgwKehj/IYNsmnS1tLdzUJhUf0/ESzRMVg7Mwm
C2JLjsFDmGKKdk+iVbqAKZbKHWoJQpVzXFEDgz5AG3ccHNjwGyExRafbps6gcU62
w9NFeFW207Tgk9vTzMLnpwCw2kaEggn2I3lb+XOt/gMRmBkzb2g0JQGwdIcEMYn9
6K9Gadi/65OhmCLEOfGQefwDbAVFqIrLOoeVp4jv/xMCggEBAIq3zJKru4X08hR5
eMVDvXvlGah6g2o2aMuca57bQq+511K5YOgyAz4YcY4GcKfnhOrrNbM4JvCYeOzZ
9AFsLty1R9sCbl7wS7KD7S5eZ5w3MhjX9SZi6xwNm4QIEh6vjB3iQR5igwHE4/3L
KBCpmvmsKWX3eEIP3/VygUJngILKerPOcuRgQ/YsV6Pls5U4oxIe+qsiwp5diWtc
+GYuEpUyzzGT7Y8AHvOYN5dsKmfbeeO8ylAYNaqI18hT0XKOCCJUx8TK+NDhjN0r
FprNk19aPKrow2U20ZfnElE2wwQxdRyKO/U+OaFWbzPaNVscyAtAqPBSy3pl4cxo
lqypej8CggEBAIRb/WVKXMBdaMIaAu6p3MqkGTNzjbhtk1HFYUU7BI97pO106f2O
kQOJAZOoliX7dZ2ONpwX+bLRE2kVXvkZ3uMbjuWW8Qwm6YDZvuPOHWOONPPSSUJB
gqjGaowvBd4sn1vHX4WedwMLwlU9ycHMzNqxV54j2vyVxnQyPwypuTov88DCm2zj
OxpDqlspX44p1N/ZRlc0AvVqHjRnn9UZtupnw3Lk/AU2iHUPebcXJwwf3ojR7rwq
UzM/q66pbQOA1G98ysp7jodUrrmkLdm01OgQtm9W46ZmcQRgh052n0ceK/3uBHGh
gRm3+S6HB2E5O2OQkwRC38siE0VQuO9yjI0CggEBAJtaBrK7KAS3CtDnFNN5O1uU
DdNpRhpODQsaz1CmZ6Fh+HdZ79m92rY57fXB+hAuVAhAFtCsfcf+C/z3uGhGoTCp
u664ZqaYKasEXk8id8o4SIkv34/jh53kXpQCRoUdUcqaRmymOPCBZgvpr+kmwtm0
6P3MRNJ8chQgLAWhd3IiYFwOy2P2kZS550E3YMYsccAj7uBrrY+/hFxbRffOYB/o
ZQiLZ9ypFuO3jMY4iVma5fpPwJh0JrTms/hmj722cu8pE4RWTJlzvFb2gqRO9Oz0
kwaO7Tjym8KVz38Micz/QOOsq62I4SfjNOzDS/Ur0d9MpWl3H6Mmmde/4UusdJY=
-----END RSA PRIVATE KEY-----
@@ -0,0 +1,13 @@
-----BEGIN RSA PUBLIC KEY-----
MIICCgKCAgEAn/KiHQ+/zwE7kY/Xf89PY6SowSb7CUk2b+lSVqC9u+R4BaE/5tNF
eNlneGNny6fQhCRA+Pdw1UJSnNpG26z/uOK8+H7fMb2Da5t/94wavw410sCKVbvf
ft8gKquUaeq//tp20BETeS5MWIXp5EXCE+lEdAHgmWWoMVMIOXwaKTMnCVGJ2SRr
+xH9147FZqOa/17PYIIHuUDlfeGi+Iu7T6a+QZ0tvmHL6j9Onk/EEONuUDfElonY
M688jhuAM/FSLfMzdyk23mJk3CKPah48nzVmb1YRyfBWiVFGYQqMCBnWgoGOanpd
46Fp1ff1zBn4sZTfPSOus/+00D5Lxh6bsbRa6A1vAApfmTcu026lIb7gbG7DU1/s
eDId9s1qA5BJpzWFKO4ztkPGvPTUok8hQBMDaSH1JOoFQgfJIfC7w2CQe+KbodQL
3akKQDCZhcoA4tf5VC6ODJpFxCn6blML5cD6veOBPJiIk8DBRgmt2AHzOUju+5ns
QcplOVxW5TFYxLqeJ8FPWqQcVekZ749FjchtAwPlUsoWIH0PTSun38ua8usrwTXb
pBlf4r0wz22FPqaecvp7z6Rj/xfDauDGDSU4hmn/TY9Fr+OmFJPW/9k2RAv7KEFv
FCLP/3U3r0FMwSe/FPHmt5fjAtsGlZLj+bZsgwFllYeD90VQU8Ds+KkCAwEAAQ==
-----END RSA PUBLIC KEY-----
@@ -0,0 +1,178 @@
from datetime import datetime, timedelta
from typing import Optional
from fastapi import Depends, FastAPI, HTTPException, status, Header, Request
from fastapi.security import OAuth2PasswordBearer
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
import jwt
from pydantic import BaseModel
from hashlib import sha256
import logging
from fastapi.logger import logger as fastapi_logger
import OpenSSL
import base64
ACCESS_TOKEN_EXPIRE_MINUTES = 30
PUBLIC_KEY = open("jwt.key.pub", "r").read()
PRIVATE_KEY = open("jwt.key", "r").read()
SALT = b'd_Yn2xWvnu'
users_db = {
"admin": {
"username": "admin",
"flag": "flag{just_A_simple_Json_Web_T0ken_exp1oit_",
"disabled": False,
},
"guest": {
"username": "guest",
"flag": "只有 'admin' 用户才有 flag 哦!",
"disabled": False,
}
}
with open("cert.pem") as f:
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, f.read())
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: Optional[str] = None
class User(BaseModel):
username: str
disabled: Optional[bool] = None
class UserInDB(User):
flag: str
class TokenOption(BaseModel):
hg_token: str
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
app = FastAPI()
logger = logging.getLogger(__name__)
app.mount("/static", StaticFiles(directory="static"), name="static")
def get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)
def authenticate_user(db, username: str, password: str):
user = get_user(db, username)
if not user:
return False
if username != "guest":
return False
return user
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, PRIVATE_KEY, algorithm="RS256")
return encoded_jwt
def check_hackergame_token(hg_token):
raise_error = False
if hg_token is None:
raise_error = True
else:
try:
id, sig = hg_token.split(":", 1)
sig = base64.b64decode(sig, validate=True)
OpenSSL.crypto.verify(cert, sig, id.encode(), "sha256")
except Exception as e:
raise_error = True
if raise_error:
raise HTTPException(status_code=403, detail="比赛 token 校验错误。\n如果您正在使用网页完成此题目,请从比赛平台打开题目(推荐),或正确输入比赛 token。\n如果您正在使用非网页方式请求后端,请确认 HTTP 请求头 hg-token 设置正确。")
return True
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, PUBLIC_KEY)
username: str = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
except:
raise credentials_exception
user = get_user(users_db, username=token_data.username)
if user is None:
raise credentials_exception
return user
async def get_current_active_user(current_user: User = Depends(get_current_user)):
if current_user.disabled:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
@app.post("/token", response_model=Token)
async def login_for_access_token(hg_token: Optional[str] = Header(None)):
check_hackergame_token(hg_token)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": "guest"}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/profile", response_model=UserInDB)
async def read_users_me(raw: Request,
current_user: User = Depends(get_current_active_user),
hg_token: Optional[str] = Header(None)):
check_hackergame_token(hg_token)
if current_user.username == 'admin':
# format flag
user = current_user.copy()
user.flag = user.flag + \
sha256(SALT + hg_token.encode('utf-8')).hexdigest()[:6] + '}'
# log user data for future analysis
fastapi_logger.warn(f"User with token {hg_token[:10]} gets flag with jwt {raw.headers['authorization']}")
else:
user = current_user
return user
@app.get("/")
def index():
return FileResponse('index.html', media_type='text/html')
@app.post("/debug")
async def debug():
return {
"ACCESS_TOKEN_EXPIRE_MINUTES": ACCESS_TOKEN_EXPIRE_MINUTES,
"PUBLIC_KEY": PUBLIC_KEY
}
@@ -0,0 +1,6 @@
fastapi==0.61.0
uvicorn==0.11.8
PyJWT==1.5.0 # intentionally here!
cryptography==3.1.1
aiofiles==0.5.0
pyOpenSSL==19.1.0 # dynamic flag check
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
.text-block {
white-space: pre;
}
File diff suppressed because it is too large Load Diff