DRF的JWT认证

几种常用的认证机制

HTTP Basic Auth

HTTP Basic Auth 在HTTP中,基本认证是一种用来允许Web浏览器或其他客户端程序在请求时提供用户名和口令形式的身份凭证的一种登录验证方式,通常用户名和明码会通过HTTP头传递。

在发送之前是以用户名追加一个冒号然后串接上口令,并将得出的结果字符串再用Base64算法编码。例如,提供的用户名是Aladdin、口令是open sesame,则拼接后的结果就是Aladdin:open sesame,然后再将其用Base64编码,得到QWxhZGRpbjpvcGVuIHNlc2FtZQ==。最终将Base64编码的字符串发送出去,由接收者解码得到一个由冒号分隔的用户名和口令的字符串

优点 基本认证的一个优点是基本上所有流行的网页浏览器都支持基本认证。

缺点 由于用户名和密码都是Base64编码的,而Base64编码是可逆的,所以用户名和密码可以认为是明文。
所以只有在客户端和服务器主机之间的连接是安全可信的前提下才可以使用。

OAuth

为第三方的认证所设计,但是更难配置。至少在服务器端更难,

名词定义:
Third-party application: 第三方应用程序,又称”客户端”(client)
HTTP service:HTTP服务提供商
Resource Owner:资源所有者,通常称”用户”(user)。
User Agent:用户代理,比如浏览器。
Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。
Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。
                它与认证服务器,可以是同一台服务器,也可以是不同的服务器
OAuth流程图,有点像是再次TCP握手的意思

(A)用户打开客户端以后,客户端要求用户给予授权。

(B)用户同意给予客户端授权。

(C)客户端使用上一步获得的授权,向认证服务器申请令牌。

(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。

(E)客户端使用令牌,向资源服务器申请获取资源。

(F)资源服务器确认令牌无误,同意向客户端开放资源

优点 快速开发 实施代码量小 维护工作减少 如果设计的API要被不同的App使用
    并且每个App使用的方式也不一样,使用OAuth2是个不错的选择。

缺点: OAuth2是一个安全框架,描述了在各种不同场景下,多个应用之间的授权问题。
    有海量的资料需要学习,要完全理解需要花费大量时间。 
    OAuth2不是一个严格的标准协议,因此在实施过程中更容易出错

Cookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器端创建了一个Cookie对象,主要是为了解决HTTP这一无状态协议下服务器如何识别用户的问题;通过客户端带上来Cookie对象来与服务器端的session对象匹配来实现状态管理的。默认的,当我们关闭浏览器的时候,cookie会被删除。但可以通过修改cookie 的expire time使cookie在一定时间内有效

TOKEN

客户端通过一些可靠信息和服务器交换取token,这个token作为客服端再次请求的权限钥匙,当然token也是存在有效时间控制的。 Token通常比密码更加长而且复杂。那么一旦获得了token,在每次调用API的时候都要附加上它。这仍然比直接发送账户和密码更加安全,哪怕是 HTTPS。 把token想象成一个安全的护照。你在一个安全的前台验证你的身份(通过你的用户名和密码),如果你成功验证了自己,你就可以取得这个。当你走进大楼的时候(试图从调用API获取资源),你会被要求验证你的护照,而不是在前台重新验证

优点:
支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,
        前提是传输的用户认证信息通过HTTP头传输.

无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,
        因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.

更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等)
        而你的服务端只要提供API即可.

去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,
        只要在你的API被调用的时候,你可以进行Token生成调用即可.

更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等)时,
        Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。

CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。

性能: 一次网络往返时间(通过数据库查询session信息)
        总比做一次HMACSHA256计算 的Token验证和解析要费时得多.

不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要为登录页面做特殊处理.

基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 
        这个标准已经存在多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft)

JWT(JSON Web Token)

JWT是Auth0提出的通过对JSON进行加密签名来实现授权验证的方案.给客户端的是公钥,然后用公钥把数据加密发送给服务端,服务端在根据来源的信息,使用对应的私钥来解析数据。这样就能保证数据的安全性

编码之后的JWT看起来是这样的一串字符

1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo4LCJ1c2VybmFtZSI6ImZlbW4iLCJleHAiOjE1NDc5NjgyOTAsImVtYWlsIjpudWxsfQ.3TKg7HEHWipAjq7nSY2jMbnIdEvWoFGOWjzjUdP__XI

由 . 分为三段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Hearder: 可以对称解密
{
"alg": "HS256",# 声明类型,这里是jwt
"typ": "JWT" # 声明加密的算法 通常直接使用 HMAC SHA256
}
Payload:载荷(存放有效信息的地方) 可以对称解密
{
"nickname": "goodspeed",
"username": "goodspeed",
}
Signature:签名
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
SECREATE_KEY
)
client 使用 JWT 与server 交互过程

server配置

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
pip install djangorestframework-jwt
# app/settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
# app/urls.py
from rest_framework_jwt.views import obtain_jwt_token
#...
urlpatterns = [
'',
# ...
url(r'^api-token-auth/', obtain_jwt_token),
# 注意不是下面这个TOKEN啊
# url(r'^api-token-auth/', views.obtain_auth_token),
# 获取TokenAuthentication的 TOKEN API 需要INSTALL_APP:'rest_framework.authtoken'
]
#查看JWT
curl -X POST -d "username=femn&password=asdf1234" http:localhost:8000/api-token-auth/
# 或者直接访问 http:localhost:8000/api-token-auth/ 输入对应的用户名和密码
# post请求验证
curl -H "Authorization: JWT token" http://127.0.0.1:8000/api
# 代码实现:手动创建令牌
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return Response({'token':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
<!--使用post方法获取token并存入html的localStorage中-->
<script type="text/javascript">
function post_test() {
$.post("http://10.127.48.204:8000/api-token-auth/",{
'username':'earthchen',
'password':'xxxxxxxx'
},
function(result){
if(result){
localStorage.token=result.token; 存入数据
}
})
}
</script>
<!--在请求数据时需要在头部添加token-->
<script type="text/javascript">
function test(){
$.ajax({
headers:{
'Authorization':'JWT '+localStorage.token //注意:jwt后面有个空格
},
type:"get",
url:"http://10.127.48.204:8000/snippets/1/",
success:function(result){
document.write(result.style);
}
})
}
<script>

或者使用PyJWT实现:

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
import jwt
import time
def create_token(request):
grant_type = request.json.get('grant_type')
username = request.json['username']
password = request.json['password']
if grant_type == 'password':
account = verify_password(username, password)
elif grant_type == 'wxapp':
account = verify_wxapp(username, password)
if not account:
return {}
payload = {
"iss": "gusibi.com",
"iat": int(time.time()),
"exp": int(time.time()) + 86400 * 7,
"aud": "www.xx.com",
"sub": account['_id'],
"username": account['username'],
"scopes": ['open']
}
token = jwt.encode(payload, 'secret', algorithm='HS256')
return True, {'access_token': token, 'account_id': account['_id']}
def verify_bearer_token(token):
# 如果在生成token的时候使用了aud参数,那么校验的时候也需要添加此参数
payload = jwt.decode(token, 'secret', audience='www.xx.com', algorithms=['HS256'])
if payload:
return True, token
return False, token

还需要注意的是:DRF用户认证(Authentication)和用户授权(Authorization)[权限控制Permissions]是两个不同的概念,认证解决的是“有没有”的问题,而授权解决的是“能不能”的问题

Share Comments