add simplejwt source code
This commit is contained in:
@@ -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-----
|
||||
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
|
||||
-----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
|
||||
+3
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
Reference in New Issue
Block a user