一篇读懂解决无链接 cookie&session&token&jwt(JsonWebToken)认证方式

一篇读懂无链接请求 cookie, session, token, jwt(Json/Web/Token)认证方式

前言

代码演示基于 python Django框架阐明,不适请绕道

HTTP是无状态的连接方式, 每次连接都会当成第一次来访问.

web基本都是文档的浏览器, 既然是浏览器, 服务器则不需要记录浏览器中的某一字段, 每次请求都是一个新的HTTP协议, 就是请求响应, 不用记录之前的HTTP请求, 每次请求都是新的.

本文将分析cookie, session. token, jwt的基本概念和应用场景, Django框架下对cookie的管理和使用, 如有错误评论区点出issue.

本文开始

cookie

什么是cookie

其实Cookie是key-value结构,类似于一个python中的字典。随着服务器端的响应发送给客户端浏览器。然后客户端浏览器会把Cookie保存起来,当下一次再访问服务器时把Cookie再发送给服务器。 Cookie是由服务器创建,然后通过响应发送给客户端的一个键值对。客户端会保存Cookie,并会标注出Cookie的来源(哪个服务器的Cookie)。当客户端向服务器发出请求时会把所有这个服务器Cookie包含在请求中发送给服务器,这样服务器就可以识别客户端了!

cookie的原理

cookie的工作原理是:由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断这个是“谁”了。

Cookie规范

  • Cookie大小上限为4KB;
  • 一个服务器最多在客户端浏览器上保存20个Cookie;
  • 一个浏览器最多保存300个Cookie;

上面的数据只是HTTP的Cookie规范,但在浏览器大战的今天,一些浏览器为了打败对手,为了展现自己的能力起见,可能对Cookie规范“扩展”了一些,例如每个Cookie的大小为8KB,最多可保存500个Cookie等!但也不会出现把你硬盘占满的可能!
注意,不同浏览器之间是不共享Cookie的。也就是说在你使用IE访问服务器时,服务器会把Cookie发给IE,然后由IE保存起来,当你在使用FireFox访问服务器时,不可能把IE保存的Cookie发送给服务器。

Cookie的覆盖

如果服务器端发送重复的Cookie那么会覆盖原有的Cookie,例如客户端的第一个请求服务器端发送的Cookie是:Set-Cookie: a=A;第二请求服务器端发送的是:Set-Cookie: a=AA,那么客户端只留下一个Cookie,即:a=AA。

在浏览器中查看cookie

浏览器中按F12,点network—cookies就能看到

Django中操作Cookie

获取Cookie

request.COOKIES['key']
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)

参数:

  • default: 默认值
  • salt: 加密盐
  • max_age: 后台控制过期时间

设置Cookie

rep = HttpResponse(...)
rep = render(request, ...)

rep.set_cookie(key,value)
rep.set_signed_cookie(key,value,salt='加密盐')

参数:

  • key, 键
  • value=’’, 值
  • max_age=None, 超时时间 cookie需要延续的时间(以秒为单位)如果参数是\ None`` ,这个cookie会延续到浏览器关闭为止
  • expires=None, 超时时间(IE requires expires, so set it if hasn’t been already.)
  • path=’/’, Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问,浏览器只会把cookie回传给带有该路径的页面,这样可以避免将cookie传给站点中的其他的应用。
  • domain=None, Cookie生效的域名 你可用这个参数来构造一个跨站cookie。如, domain=".example.com"所构造的cookie对下面这些站点都是可读的:www.example.com 、 www2.example.com 和an.other.sub.domain.example.com 。如果该参数设置为 None ,cookie只能由设置它的站点读取
  • secure=False, 浏览器将通过HTTPS来回传cookie
  • httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

删除Cookie

def logout(request):
    rep = redirect("/login/")
    rep.delete_cookie("user")  # 删除用户浏览器上之前设置的usercookie值
    return rep

Cookie版登录校验

def login_auth(func):
    def inner(request,*args,**kwargs):
        next_url=request.get_full_path()
        if request.COOKIES.get('is_login'):
            return func(request,*args,**kwargs)
        else:
            return redirect('cookie_login/?next=%s'%next_url)
    return inner
@login_auth
def cookie_order(request):
    return HttpResponse('我是订单页面')
@login_auth
def cookie_index(request):
    name=request.COOKIES.get('username')
    return render(request,'cookie_index.html',{'name':name})
def cookie_login(request):
    if request.method =='POST':
        next_url=request.GET.get('next')
        name=request.POST.get('name')
        password=request.POST.get('password')
        if name == 'lqz' and password == '123':
            import datetime
            now=datetime.datetime.now().strftime('%Y-%m-%d %X')
            print(now)
            obj=redirect(next_url)
            obj.set_cookie('is_login',True)
            obj.set_cookie('username',name)
            obj.set_cookie('login_time',now)
            return obj

    return render(request, 'cookie_login.html')


session详情

Session的由来

Cookie虽然在一定程度上解决了“保持状态”的需求,但是由于Cookie本身最大支持4096字节,以及Cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是Session。

问题来了,基于HTTP协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的Cookie就起到桥接的作用。

我们可以给每个客户端的Cookie分配一个唯一的id,这样用户在访问时,通过Cookie,服务器就知道来的人是“谁”。然后我们再根据不同的Cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。

总结而言:Cookie弥补了HTTP无状态的不足,让服务器知道来的人是“谁”;但是Cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过Cookie识别不同的用户,对应的在Session里保存私密的信息以及超过4096字节的文本。

另外,上述所说的Cookie和Session其实是共通性的东西,不限于语言和框架。

x

Django中Session相关方法

# 获取、设置、删除Session中数据
request.session['k1']
request.session.get('k1',None)
request.session['k1'] = 123
request.session.setdefault('k1',123) # 存在则不设置
del request.session['k1']


# 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
request.session.iterkeys()
request.session.itervalues()
request.session.iteritems()

# 会话session的key
request.session.session_key

# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()

# 检查会话session的key在数据库中是否存在
request.session.exists("session_key")

# 删除当前会话的所有Session数据(只删数据库)
request.session.delete()
  
# 删除当前的会话数据并删除会话的Cookie(数据库和cookie都删)。
request.sessionpython.flush() 
    这用于确保前面的会话数据不可以再次被用户的浏览器访问
    例如,django.contrib.auth.logout() 函数中就会调用它。

# 设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
    * 如果value是个整数,session会在些秒数后失效。
    * 如果value是个datatime或timedelta,session就会在这个时间后失效。
    * 如果value是0,用户关闭浏览器session就会失效。
    * 如果value是None,session会依赖全局session失效策略。

Django中使用session时,做的事:

# 生成随机字符串
# 写浏览器cookie -> session_id: 随机字符串
# 写到服务端session:
    # {
    #     "随机字符串": {'user':'alex'}
    # }

Django中的Session配置

1. 数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)

2. 缓存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
SESSION_CACHE_ALIAS = 'default'                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置

3. 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
SESSION_FILE_PATH = None                                    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() 

4. 缓存+数据库
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎

5. 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎

其他公用设置项:
SESSION_COOKIE_NAME = "sessionid"                       # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_PATH = "/"                               # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None                             # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False                            # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否关闭浏览器使得Session过期(默认)
SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认)

Django中使用session

1 存在于服务端的键值对

2 同一个浏览器不允许登录多个账户,不同浏览器可以登录同一个账户

3 session的使用(必须迁移数据)
	-增:request.session['name']=lqz
    -查:request.session['name']
    -改:request.session['name']=egon
    -删:del request.session['name']
    -设置过期时间:request.session.set_expiry(10)
    
4 session的其它使用
	-request.session.setdefault('k1',123)
    -request.session.get('name',None)
    -del request.session['k1']
    
    -request.session.keys()
	-request.session.values()
    -request.session.items()
    -request.session.session_key # 获取那个随机字符串,django_session表中session_key字段
	
    -request.session.clear_expired() # 清除过期的session
    -request.session.exists("session_key") # 判断这个随机字符串(session_key字段),有没有数据
    -request.session.delete() # 删除所有的值,django_session表中删除当前登录者的这条记录
    -request.session.flush()  # 干了上面那个事,把cookie设置为过期

思考,如果第二个人再次再同一个浏览器上登录,django-session表会怎样?

CBV中加装饰器

from django import views
from django.utils.decorators import method_decorator
# @method_decorator(login_auth,name='get')
# @method_decorator(login_auth,name='post')
class UserList(views.View):
    # @method_decorator(login_auth)
    def dispatch(self, request, *args, **kwargs):
        obj=super().dispatch(request, *args, **kwargs)
        return obj

    @method_decorator(login_auth)
    def get(self,request):
        return HttpResponse('我是用户列表')

    def post(self,request):
        return HttpResponse('我是用户列表')

token

  • token是用户认证的令牌, 最简单的token组成为 UID(用户唯一身份标识) , time(当前时间的时间戳) sign(签名, 由token的前几位加盐 通过SHA256算法加密得到一定长度的十六进制字符串, 可以防止第三方恶意篡改, 拼接token向服务器发送请求) 还可以把不变长的参数放进token, 避免对此查库(俗称缓存机制) , 可以有效的减少请求发送次数, 小数据可以直接放到token发送到客户端

一 : 当用户首次登陆成功后(注册也适用) , 服务器就会生成一个token值, 这个值, 会在服务器保存token值, (保存到数据库中) 在将这个值返回客户端,

二: 客户端拿到token值后, 进行本地保存, (SP存储是大家都能比较支持和易于操作的存储)

​ 注 : SP存储 :sp存储专门用来存储一些单一的小数据,有自己独立的空间,想浏览器中自动存储cookie一样,存储的数据类型为:boolean,float,int,long,String,应用卸载会删除数据

三: 当客户端再次发送网络请求(一般不是登录请求)的时候, 就会将客户端携带的token值发送到服务端

四: 服务端接收到客户端请求后, 取出token值与保存在数据库中的token值进行比较

​ 对比一: 如果两个token值相同, 说明用户登录成功过了, 当前用户处于登录状态, 直接定向到相应页面, 更新token时间

​ 对比二: 如果这个token值匹配失败, 说明没有登录成功过, 或登录token值已经失效, 让用户从新登录



理论巨多, 会有适当代码, 后续补上




jwt(Json/Web/Token)

前言:

  • web应用中, 使用jwt替代session并不是什么好事

  • JWT使用场景介绍

  • 什么是JWT:

    来源维基百科解释:

    JSON WEB TokenJWT,读作 [/dʒɒt/]),是一种基于JSON的、用于在网络上声明某种主张的令牌(token)。JWT通常由三部分组成: 头信息(header), 消息体(payload)和签名(signature), 是目前最流行的跨域认证解决方案, 接下来将介绍用法和原理

为了引出jwt, 还是需要啰嗦前面问题的

互联网离不开用户认证, 认证一般是这样的

1. 用户向服务器发送用户名和密码

2. 服务器通过认证后, 对当前对话(session) 保存到相关数据库中, 比如登录时间和session过期时间, 用户其他信息

3. 服务器向用户返回一个 session id 写入用户的cookie中

4. 用户随后的每次访问都需要通过cookie, 将session id 传回服务器

5. 服务器收到session id 后进行校验, 成功后保存到数据库中并更新用户数据

安全方面

使用HMAC算法进行加密, 虽然JWT可以加密以提供各方之间的保密性,但我们将重点关注已签名的令牌。 签名的令牌可以验证其中包含的索赔的完整性,而加密令牌隐藏来自其他方的索赔。 当令牌使用公钥/私钥对进行签名时,签名还证明只有持有私钥的方是签名方.

由于他们的尺寸较小, jwt可以通过url,post参数或http请求头发送出去, 另外, 尺寸越小, 发送的速度越快

JWT原理

jwt是服务认证后, 生成一个json对象, 发送给用户, 如下

{
	"name":"chen",
	"age":18,
	"time":"2020/10/15"
}

以后用户向服务器通信时, 都要携带上方数据, 服务器与对象进行验证, 防止数据被篡改, 服务器生成这个对象时会加上签名, (后文讲到签名)

服务器就不保存session数据了, 也就是说, 服务器变成无状态了, 客户端来记录用户数据, 用户发送请求后拿这个签名进行验证…通过后返回相应界面, 这样大大的给服务端减轻压力

JWT的数据结构

JWT实际长什么样的呢, 如下

GgGvtasvA12.18843KNk7anGhfTl9Wp_k144851.JgGv35972iTSI3nLJgGvtasvpTaSv7yrY1zz1vWo

它是一个很长的字符串, 中间使用( . )分隔三个部分

JWT 的三个部分

  • Header (头部)
  • Payload (负载)
  • Signature (签名)

完整写法

Header.Payload.Signature

Header 部分是一个json对象, 存放着json格式数据, 通常格式如下

{
	"sign":"SHA256",
	"type":"JWT",
}

上方代码中, sign属性表示签名的算法 algorithm, 默认是 HMAC SHA256

type属性表示token令牌的属性, JWT令牌同一写成 JWT

将json数据 使用Base64Url算法 转成字符串

Payload 也是json数据的一部分, 用来存放实际传递是数据, JWT规定7 个官方字段

  • iss (issure) : 签发人
  • exp (expiration time) 过期时间
  • sub (subject) : 主体
  • aud (audience) 受众
  • nbf (not before) : 生效时间
  • iat (jwt ID) : 编号

除了官方字段, 还可以自定义一部分私有字段 , 这个是开放的, 例如

{
	"sub":"chen123",
	"name":"luix",
	"admin":ture
	...
}

注意: 对于已签名的令牌, 此信息尽管受到篡改保护, 但任何人都可以读取到, 所以不要把重要信息放到这部分中, 这个json对象也要使用 加密算法进行加密的

signature 部分是对前两部分的签名进行校验, 防止数据篡改

首先, 需要指定一个秘钥, 这个秘钥只有服务器知道, 不能泄露给用户, 使用Header里面的指定签名算法, 产生签名返回给客户端, 如下

SHA256(Base64UrlEncode(header) + "."+Base64UrlEncode(playload),secret)

得到一段字符串后, 把 Header, Payload, Signature 三个部分拼成一个字符串, 每部分之间使用 "."分隔, 返回给用户浏览器或APP中保存.

JWT使用方式

客户端收到服务器的JWT, 可以存储在cookie中, 也可以存放到其他位置, 客户端每次与服务器通信, 只需要提供JWT, 就能正常的访问服务器, 并且得到相应的用户数据

跨域也是一样的哦

特点

1. JWT默认是不加密的, 但是可以人工加密, 生成token后, 再进行二次加密
2. JWT不加密情况下, 建议不要将重要数据写到JWT中
3. 不仅可以用于认证, 也可以用于信息交换, 有效使用JWT, 可以降低服务器查询数据库次数

缺点
由于服务器不报存session状态, 因此无法使用过程中废弃的某个token, 或者更改token权限, 也就是说, JWT一旦签发, 之间是无法更改的, 除非删除后重新添加

Base64Url 官方自查…   维基百科

JWT使用方式

客户端收到服务器的JWT, 可以存储在cookie中, 也可以存放到其他位置, 客户端每次与服务器通信, 只需要提供JWT, 就能正常的访问服务器, 并且得到相应的用户数据

跨域也是一样的哦

特点

1. JWT默认是不加密的, 但是可以人工加密, 生成token后, 再进行二次加密
2. JWT不加密情况下, 建议不要将重要数据写到JWT中
3. 不仅可以用于认证, 也可以用于信息交换, 有效使用JWT, 可以降低服务器查询数据库次数

缺点
由于服务器不报存session状态, 因此无法使用过程中废弃的某个token, 或者更改token权限, 也就是说, JWT一旦签发, 之间是无法更改的, 除非删除后重新添加

Base64Url 官方自查…   维基百科

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值