点击python编程从入门到实践,置顶 公众号重磅 python入门资料,第一时间送达javascript
仍是牛java
读完须要python
速读仅需 5 分钟web
/ python 生产实战 安全认证的那些事儿 /算法
系统安全可能每每是被你们所忽略的,咱们的不少系统说是在互联网上"裸奔"一点都不夸张,很容易受到攻击,系统安全实际上是一个复杂且庞大的话题,若要详细讲来估计用几本书的篇幅都讲不完,基于此本篇及下一篇会着重讲解在咱们开发系统过程当中遇到的一些安全校验机制,但愿能起到抛砖引玉的做用,望各位在开发过程当中多多思考不要只局限于功能实现上,共勉~数据库
在系统安全、身份验证以及权限受权方面一般来讲有各类各样的处理方式,但大多都比较复杂。在不少框架和系统里,涉及安全和身份验证的工做每每都比较繁琐,而且代码量也巨大,基于此也出现了一些相关的协议和相关库 咱们今天就一块儿来了解一下相关的内容编程
1api
常见认证规范/协议浏览器
1.1安全
OAuth2
OAuth2 是一种协议规范,定义了几种用来身份验证和权限受权的处理方式。它是一种可扩展的协议规范,涵盖了几种复杂的使用场景。而且包含了基于第三方身份验证的处理方法。咱们常见的"使用微信登录"、"使用 QQ 登录"等第三方登录方式的底层技术就是基于 OAuth2 实现的。
1.2
OpenID Connect
OpenIDConnect 是另外一种基于 OAuth2 的协议规范。它扩展了 OAuth2 的部分功能,让之前相对模糊的功能变得可操做性更强。常见的 Google 登录就是基于 OpenID Connect 实现的。
1.3
OpenAPI
OpenAPI 是一套构建 API 的开放标准。FastAPI 是基于 OpenAPI 构建而成。
OpenAPI 支持如下几种安全机制:
1.apiKey:应用指定的 key 来自于
(1) 查询参数
(2) header 信息
(3) cookie 信息
2.http:支持标准的 http 身份验证系统,包括:
bearer:头信息 Authorization 的内容中带有 Bearer 和 token 信息,继承自 OAuth2
HTTP 基本认证
HTTP 摘要认证
3.oauth2
4.openIdConnect
FastAPI 经过引入 fastapi.security 模块,能够支持以上全部安全机制,而且简化了使用方法。
2
JWT
2.1
JWT 的概念
JSON Web Token(JWT)是一个很是轻巧的规范。这个规范容许咱们使用 JWT 在用户和服务器之间传递安全可靠的信息。
2.2
JWT 的组成
一个 JWT 实际上就是一个字符串,它由三部分组成:头部、载荷与签名。将这三段信息文本用.连接一块儿就构成了 Jwt 字符串。就像这样:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
头部(Header)
JWT 的头部承载两部分信息:
一、声明类型,这里是 jwt
二、声明加密的算法,一般直接使用 HMAC SHA256
咱们使用 base64 解析一下 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 这个子串 能够获得:
{ "typ": "JWT", "alg": "HS256"}
能够看出: 在头部指明了签名算法是 HS256 算法。
2.3
载荷(Payload)
载荷就是存放有效信息的地方。这些有效信息包含三个部分
一、标准中注册的声明
二、公共的声明
三、私有的声明
标准中注册的声明 (建议但不强制使用) :
一、iss(Issuer): 签发人
二、sub(Subject): 主题
三、aud(Audience): 受众
四、exp(Expiration Time): 过时时间,这个过时时间必需要大于签发时间
五、nbf(Not Before): 生效时间
六、iat(Issued At): 签发时间
七、jti(JWT ID) : JWT 的惟一身份标识,主要用来做为一次性 token,从而回避重放攻击。
公共的声明:
公共的声明能够添加任何的信息,通常添加用户的相关信息或其余业务须要的必要信息。但不建议添加敏感信息,由于该部分在客户端可解密。
私有的声明:
私有声明是提供者和消费者所共同定义的声明,通常不建议存放敏感信息,由于 base64 是对称解密的,意味着该部分信息能够归类为明文信息。
咱们使用 base64 解析一下 eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9 就能够获得 以前定义的一个 payload:
{ "sub": "1234567890", "name": "John Doe", "admin": true}
2.4
签名(Signature)
JWT 的第三部分是一个签证信息,这个签证信息由三部分组成:
一、header (base64 后的)
二、payload (base64 后的)
三、secret
这个部分须要 base64 加密后的 header 和 base64 加密后的 payload 使用.链接组成的字符串,而后经过 header 中声明的加密方式进行加盐 secret 组合加密,而后就构成了 JWT 的第三部分。
Signature = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret);Token = base64(头部).base64(载荷).Signature
注意:secret 是保存在服务器端的,在任何场景都不该该流露出去。
2.5
使用
在请求头 headers 中加入 Authorization,并加上 Bearer 标注
headers = {...'Authorization': 'Bearer ' + token...}
3
基于 JWT 的 Token 的认证过程
3.1
登录认证过程
1.第一次认证:第一次登陆,用户从浏览器输入用户名/密码,提交后到服务器的登陆处理的 Action 层(Login Action)
2.Login Action 调用认证服务进行用户名密码认证,若是认证经过,LoginAction 层调用用户信息服务获取用户信息(包括完整的用户信息及对应权限信息)
3.返回用户信息后,Login Action 从配置文件中获取 Token 签名生成的秘钥信息,进行 Token 的生成
4.生成 Token 的过程当中能够调用第三方的 JWT Lib 生成签名后的 JWT 数据
5.完成 JWT 数据签名后,将其设置到 COOKIE 对象中,并重定向到首页,完成登陆过程
咱们再经过完整的图来看一下登录的整个认证过程:
3.2
请求认证
1.基于 Token 的认证机制会在每一次请求中都带上完成签名的 Token 信息,这个 Token 信息可能在 COOKIE 中,也可能在 HTTP 的 Authorization 头中
2.客户端(APP 客户端或浏览器)经过 GET 或 POST 请求访问资源(页面或调用 API)
3.认证服务做为一个 Middleware HOOK 对请求进行拦截,首先在 COOKIE 中查找 Token 信息,若是没有找到,则在 HTTP Authorization Head 中查找
4.若是找到 Token 信息,则根据配置文件中的签名加密秘钥,调用 JWT Lib 对 Token 信息进行解密和解码
5.完成解码并验证签名经过后,对 Token 中的 exp、nbf、aud 等信息进行验证
6.所有经过后,根据获取的用户的角色权限信息,进行对请求的资源的权限逻辑判断
若是权限逻辑判断经过则经过 Response 对象返回;不然则返回 HTTP 401
咱们再经过完整的图来看一下登录的整个请求认证过程:
3.3
基于 JWT 的 Token 认证的几点总结:
1.一个 Token 就是一些信息的集合,是一个字符串信息
2.在 Token 中包含足够多的信息,以便在后续请求中减小查询数据库的概率
3.服务端须要对 COOKIE 和 HTTP Authrorization Header 进行 Token 信息的检查
4.基于上一点,能够用一套 Token 认证代码来面对浏览器类客户端和非浏览器类客户端
5.由于 Token 是被签名的,因此咱们能够认为一个能够解码认证经过的 Token 是由咱们系统发放的,其中带的信息是合法有效的
4
获取 Token 实战
在写代码以前咱们先来了解一下 OAuth2PasswordBearer 这个类的功能。OAuth2PasswordBearer 是接收 URL 做为参数的一个类:客户端会向该 URL 发送 username 和 password 参数,而后获得一个 Token 值。OAuth2PasswordBearer 并不会建立相应的 URL 路径操做,只是指明了客户端用来获取 Token 的目标 URL。
当请求到来的时候,FastAPI 会检查请求的 Authorization 头信息,若是没有找到 Authorization 头信息,或者头信息的内容不是 Bearer Token,它会返回 401 状态码(UNAUTHORIZED)。咱们再从源码上认识一下:
class OAuth2PasswordBearer(OAuth2): def __init__( self, tokenUrl: str, scheme_name: str = None, scopes: dict = None, auto_error: bool = True, ): if not scopes: scopes = {} flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes}) super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
async def __call__(self, request: Request) -> Optional[str]: authorization: str = request.headers.get("Authorization") scheme, param = get_authorization_scheme_param(authorization) if not authorization or scheme.lower() != "bearer": if self.auto_error: raise HTTPException( status_code=HTTP_401_UNAUTHORIZED, detail="Not authenticated", headers={"WWW-Authenticate": "Bearer"}, ) else: return None return param
为了完成咱们接下来的功能,须要你们进行这两个模块的安装:
pip install pyjwtpip install python-multipart
简单解释一下:
pyjwt 是用来产生和校验 JWT token
python-multipart 是由于 OAuth2 须要经过表单数据来发送 username 和 password 信息
在生产实践过程当中,获取 token 的代码:
from datetime import datetime, timedeltafrom typing import Optional
from fastapi import Depends, FastAPIfrom fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestFormimport jwtfrom pydantic import BaseModel
# to get a string like this run:# openssl rand -hex 32SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"ALGORITHM = "HS256"ACCESS_TOKEN_EXPIRE_MINUTES = 30
class Token(BaseModel): access_token: str token_type: str
app = FastAPI()
# oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
# 生成tokendef 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, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt
# 请求接口@app.post("/token", response_model=Token)async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()): access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={"sub": "test"}, expires_delta=access_token_expires )return {"access_token": access_token, "token_type": "bearer"}
咱们运行一下代码能够看一下效果:
咱们对 access_token 进行一下解析来看一下每一部分的组成:
1. 子串"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"的解析结果为:
2. 子串"eyJzdWIiOiJ0ZXN0IiwiZXhwIjoxNjE4MTExNTA4fQ"的解析结果为:
3.子串"pwoiwQmQZbIFVvmdlmkSPXdoHrtZyoNNTRhoWAZWU9o"的解析结果为:
5
本期总结
1.介绍了常见的 认证规范/协议
2.对 JWT 进行了深刻的研究和分析
3.在实际生产过程当中如何产生一个有效的 Token 在代码层面进行落地
4.本篇不只可让"守"方清楚了如何有效的制做一个 Token 来进行防护,另外一方面如果作逆向的"攻"方也了解了如何进行破防,下一期咱们会重点站在实践的角度去走一个登录请求的认证的全流程
原创不易,只愿能帮助那些须要这些内容的同行或刚入行的小伙伴,你的每次 点赞、分享 都是我继续创做下去的动力,我但愿能在推广 python 技术的道路上尽我一份力量,欢迎在评论区向我提问,我都会一一解答,记得一键三连支持一下哦!
加入python学习交流微信群,请后台回复「入群」
往期推荐
本文分享自微信公众号 - python编程军火库(PythonCoder1024)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。