大家好,欢迎来到IT知识分享网。
一、什么是鉴权,为什么要鉴权
鉴权:是指验证用户是否有访问系统的权利。
为什么要鉴权 :对用户进行鉴权,防止非法用户占用网络资源,非法用户接入网络,被骗取关键信息
二、鉴权方式
Basic Auth
basic auth是一种用来允许网页浏览器或其他客户端程序在请求时提供用户名和口令形式的身份凭证的一种登录验证方式。
注:Response 中的 WWW-Authenticate 字段会指示浏览器弹出询问用户名和密码的提示框。
cookie
概念
存储在客户端(浏览器)本地的小型文本文件,用于辨别用户身份,保持浏览器会话。
- cookie一般小于4kb
- cookie是以key-value 格式存储的
- expires属性
- 会话型(默认) 存储在浏览器内存中,浏览器画面关闭自动删除
- 持久型,存储在本地文件中,到期后或者浏览器强制删除后失效
- path属性:定义站点上可以访问到该cookie的目录
- domain属性:设置cookie所属的域
- secure属性:指定是否使用https协议
- httponly属性:用于防止客户端脚本通过document.cookie属性访问Cookie,有助于保护Cookie不被跨站脚本攻击窃取或篡改
使用flask设置cookie
# -*- coding:utf-8 -*- from flask import Flask, request, make_response app = Flask(__name__) @app.route('/set_cookie') def set_cookie(): """ 设置cookie :return: """ resp = make_response('set cookie success') resp.set_cookie('user', 'zhangsan') return resp @app.route('/source') def source(): """ 资源目录 :return: """ user = request.cookies.get('user') print(user) if not user: return '无权限' else: return '有权访问' if __name__ == '__main__': # 启动app, 开启调试模式 app.run(debug=True)
浏览器端访问
设置简单cookie
访问 http://testsite.api:5000/set_cookie
设置好cookie之后,再次访问资源路径http://testsite.api:5000/source
与上部分代码相同 @app.route('/set_cookie') def set_cookie(): """ 设置cookie :return: """ resp = make_response('set cookie success') resp.set_cookie('user', 'zhangsan', max_age=30, path='/source', domain='testsite.api', httponly=True) return resp
通过上面设置后的cookie:
- 有效时长是30s
- 设置路径是/source下面的,不能是其他路径,/content就访问不了
- 只有域名testsite.api 有效
- httponly 设置为True ,访问前端窃取cookie
浏览器操作cookie
原生js操作cookie
function setCookie(cname, cvalue, exdays) {
var d = new Date(); d.setTime(d.getTime() + (exdays*24*60*60*1000)); // var expires = "expires="+ d.toUTCString(); var expires = "expires="+ d.toGMTString(); document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/;"; } cookie设置的过期时间比较的是GMT时间 getTime() 方法获取1970年01月01日午夜至今的毫秒数(GMT时间) setTime() 的作用是设置日期对象,例如设置后的日期对象为:Thu Sep 17 2020 17:03:00 GMT+0800 (中国标准时间)。返回值是毫秒数。 toGMTString() 方法把日期对象转换为GMT时间字符串
jquery 操作cookie
<script src="https://cdn.staticfile.org/jquery/3.4.0/jquery.min.js"></script> <script src="https://cdn.staticfile.org/jquery-cookie/1.4.1/jquery.cookie.min.js"></script> <script> // 创建 cookie: $.cookie('name', 'value'); // 创建 cookie,并设置 7 天后过期: $.cookie('name', 'value', {
expires: 7 }); </script>
cookie的缺点
cookie是不够安全的,很容易被黑客截获cookie,CSRF攻击就是利用了这个安全漏洞
session
步骤一:客户端把用户ID 和密码等登录信息放入报文的实体部分,通常是以POST 方法把请求发送给服务器。而这时,会使用HTTPS 通信来进行HTML 表单画面的显示和用户输入数据的发送。
步骤二:服务器会发放用以识别用户的Session ID。通过验证从客户端发送过来的登录信息进行身份认证,然后把用户的认证状态与Session ID 绑定后记录在服务器端。
向客户端返回响应时,会在首部字段Set-Cookie 内写入Session ID(如PHPSESSID=028a8c…)。
你可以把Session ID 想象成一种用以区分不同用户的等位号。然而,如果Session ID 被第三方盗走,对方就可以伪装成你的身份进行恶意操作了。因此必须防止Session ID 被盗,或被猜出。为了做到这点,Session ID 应使用难以推测的字符串,且服务器端也需要进行有效期的管理,保证其安全性。
另外,为减轻跨站脚本攻击(XSS)造成的损失,建议事先在Cookie 内加上httponly 属性。
步骤三:客户端接收到从服务器端发来的Session ID 后,会将其作为Cookie 保存在本地。下次向服务器发送请求时,浏览器会自动发送Cookie,所以Session ID 也随之发送到服务器。服务器端可通过验证接收到的Session ID 识别用户和其认证状态。
# -*- coding:utf-8 -*- from flask import Flask, session app = Flask(__name__) app.secret_key = 'secret key string' @app.route('/set_session') def set_session(): """ 设置session :return: """ session['user'] = 'zhangsan' return 'set session success!' @app.route('/clear_session') def clear_session(): """ 清理session :return: """ session.clear() return 'clear session success!' @app.route('/source') def source(): """ 资源 :return: """ if not session.get('user'): return '无权访问' return '有权访问' if __name__ == '__main__': app.run(debug=True)
- 占用服务器资源,降低效率
- 因为依赖cookie,依然存在安全性问题
- 分布式部署时,session共享是必须要考虑的
token
使用基于 Token 的身份验证方法,大概的流程是这样的:
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
- 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
- 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
总的来说就是客户端在首次登陆以后,服务端再次接收http请求的时候,就只认token了,请求只要每次把token带上就行了,服务器端会拦截所有的请求,然后校验token的合法性,合法就放行,不合法就返回401(鉴权失败)。
乍一看好像和前面的seesion-cookie有点像,seesion-cookie是通过seesionid来作为浏览器和服务端的链接桥梁,而token验证方式貌似是token来起到seesionid的角色。其实这两者差别是很大的。
- sessionid 他只是一个唯一标识的字符串,服务端是根据这个字符串,来查询在服务器端保持的seesion,这里面才保存着用户的登陆状态。但是token本身就是一种登陆成功凭证,他是在登陆成功后根据某种规则生成的一种信息凭证,他里面本身就保存着用户的登陆状态。服务器端只需要根据定义的规则校验这个token是否合法就行。
- session-cookie是需要cookie配合的,居然要cookie,那么在http代理客户端的选择上就是只有浏览器了,因为只有浏览器才会去解析请求响应头里面的cookie,然后每次请求再默认带上该域名下的cookie。但是我们知道http代理客户端不只有浏览器,还有原生APP等等,这个时候cookie是不起作用的,或者浏览器端是可以禁止cookie的(虽然可以,但是这基本上是属于吃饱没事干的人干的事)…,但是token 就不一样,他是登陆请求在登陆成功后再请求响应体中返回的信息,客户端在收到响应的时候,可以把他存在本地的cookie,storage,或者内存中,然后再下一次请求的请求头重带上这个token就行了。简单点来说cookie-session机制他限制了客户端的类型,而token验证机制丰富了客户端类型。
- 时效性。session-cookie的sessionid实在登陆的时候生成的而且在登出事时一直不变的,在一定程度上安全就会低,而token是可以在一段时间内动态改变的。
- 可扩展性。token验证本身是比较灵活的,一是token的解决方案有许多,常用的是JWT,二来我们可以基于token验证机制,专门做一个鉴权服务,用它向多个服务的请求进行统一鉴权。
jwt
- 传统token方式和jwt在认证方面有什么差异?
- 传统token方式
用户登录成功后,服务端生成一个随机token给用户,并且在服务端(数据库或缓存)中保存一份token,以后用户再来访问时需携带token,服务端接收到token之后,去数据库或缓存中进行校验token的是否超时、是否合法。
- jwt方式
用户登录成功后,服务端通过jwt生成一个随机token给用户(服务端无需保留token),以后用户再来访问时需携带token,服务端接收到token之后,通过jwt对token进行校验是否超时、是否合法。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
由 . 分为三段,通过解码可以得到
- 头部(Header)
包括类别(typ)、加密算法(alg);
{
"alg": "HS256", "typ": "JWT" }
jwt的头部包含两部分信息:
- 声明类型,这里是jwt
- 声明加密的算法 通常直接使用 HMAC SHA256
然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
- 载荷(payload)
载荷就是存放有效信息的地方,这些有效信息包含三个部分
- 标准中注册的声明
- 公共的声明
- 私有的声明
标准中注册的声明 (建议但不强制使用) :
- iss: 该JWT的签发者,一般是服务器,是否使用是可选的;
- iat(issued at): 在什么时候签发的(UNIX时间),是否使用是可选的;
- exp(expires): 什么时候过期,这里是一个Unix时间戳,是否使用是可选的;
- aud: 接收该JWT的一方,是否使用是可选的;
- sub: 该JWT所面向的用户,userid,是否使用是可选的;
其他还有:
- nbf (Not Before):如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟;,是否使用是可选的;
- jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
举例
{
"iss": "test server", "iat": , "exp": , "aud": "terminal", "sub": "user id", "nickname": "goodspeed", "username": "goodspeed", "scopes": ["admin", "user"] }
将上面的JSON对象进行base64编码可以得到下面的字符串。这个字符串我们将它称作JWT的Payload(载荷)。
eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE0MTY3OTc0MTksImV4cCI6MTQ0ODMzMzQxOSwiYXVkIjoid3d3Lmd1c2liaS5jb20iLCJzdWIiOiIwMTIzNDU2Nzg5Iiwibmlja25hbWUiOiJnb29kc3BlZWQiLCJ1c2VybmFtZSI6Imdvb2RzcGVlZCIsInNjb3BlcyI6WyJhZG1pbiIsInVzZXIiXX0
由于这里用的是可逆的base64 编码,所以第二部分的数据实际上是明文的。我们应该避免在这里存放不能公开的隐私信息。
- 签名(signature)
根据alg算法与私有秘钥进行加密得到的签名字串;
这一段是最重要的敏感信息,只能在服务端解密;
HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload), SECREATE_KEY)
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
- header (base64后的)
- payload (base64后的)
- secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
优点
- 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
- 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
- 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
- 它不需要在服务端保存会话信息, 所以它易于应用的扩展
缺点
- 安全性,由于jwt的payload是使用base64编码的,并没有加密,因此jwt中不能存储敏感数据。
- 性能,jwt太长。由于是无状态使用JWT,所有的数据都被放到JWT里,如果还要进行一些数据交换,那载荷会更大,经过编码之后导致jwt非常长,cookie的限制大小一般是4k,cookie很可能放不下,所以jwt一般放在local
storage里面。并且用户在系统中的每一次http请求都会把jwt携带在Header里面,http请求的Header可能比Body还要大。而sessionId只是很短的一个字符串,因此使用jwt的http请求比使用session的开销大得多。 - 一次性,无状态是jwt的特点,但也导致了这个问题,jwt是一次性的。想修改里面的内容,就必须签发一个新的jwt。
token由于自包含信息,因此一般数据量较大,而且每次请求都需要传递,因此比较占带宽。另外,token的签名验签操作也会给cpu带来额外的负担。但是随着硬件的提升和带宽的提高,这些缺点变得越来越微不足道。
安全相关
- 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
- 保护好secret私钥,该私钥非常重要。
- 尽量使用https协议
生成jwt
可以自己拼接jwt字符串,也可以使用现有的库,一般开发中都会使用库
- 安装pyjwt
- 使用
# -*- coding:utf-8 -*- import jwt import datetime SECRET_KEY = 'iv%x6xo7l7_u9bf_u!9#g#m*)*=ej@bek5)(@u3kh*72+unjv=' def create_token(): """ 生成json web token :return: """ # 构造header headers = {
'typ': 'jwt', 'alg': 'HS256' } # 构造payload payload = {
'user_id': 1, # 自定义用户ID 'username': 'zs', # 自定义用户名 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=5) # 超时时间 } print(payload) result = jwt.encode(payload=payload, key=SECRET_KEY, algorithm="HS256", headers=headers) return result def get_payload(token): """ 根据token获取payload :param token: :return: """ try: verified_payload = jwt.decode(jwt=token, key=SECRET_KEY, algorithms=['HS256']) return verified_payload except jwt.exceptions.ExpiredSignatureError: print('token已失效') except jwt.DecodeError: print('token认证失败') except jwt.InvalidTokenError: print('非法的token') if __name__ == '__main__': token = create_token() print(type(token)) # <class 'str'> print(token) # eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InpzIiwiZXhwIjoxNjYyNjkxMjc5fQ.21EiFPyo07Cn_DudXRYbjW-DX7-xT-FFy22u6x84gGA payload = get_payload(token) print(type(payload)) # <class 'dict'> print(payload)
oauth2.0
OAuth 就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。
举例:
快递员进入某小区需要密码,有门禁进不来,可以输入密码进入,但出于安全的考虑并不想告诉他密码。此时门禁有一个高级按钮“一键获取XXX授权”,只要业主这边同意,他会获取到一个有效期 n小时的令牌(token)正常出入。
授权方式
OAuth2.0 的授权简单理解其实就是获取令牌(access token)的过程,OAuth 协议定义了四种获得令牌的授权方式(authorization grant )如下:
授权码(authorization-code)
隐藏式(implicit)
密码式(password):
客户端凭证(client credentials)
不管我们使用哪一种授权方式,在三方应用申请令牌之前,都必须在系统中去申请身份唯一标识:客户端 ID(client ID)和 客户端密钥(client secret)。
比如要使用企业微信授权,则需要在企业微信平台注册一个企业,生成企业id和密钥,
授权码
授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。
这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。
github授权登录
1,注册应用到github 设置回调url 设置完成得到client id和secret
使用client id获取code
https://github.com/login/oauth/authorize?client_id=4520e5d3f7afa74b26c0&redirect_uri=http://localhost:5000/customer/github/redirect
回调url获取到code http://localhost:5000/customer/github/redirect?code=XXX
根据code获取token
企业微信扫码登录
企业微信扫码登录举例请求方式: GET(HTTPS):
1,获取access token
使用企业id和密钥获取access token
请求地址: https://qyapi.weixin..com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET
2,获取code参数
请求地址:https://open.weixin..com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect
假设回调域为https://www.hyvdi.com/redirect 则授权同意侯会回调到https://www.hyvdi.com/redirect ?code=AUTH_CODE
3,用户身份认证
拿到access token 和 code 就可以获取用户信息 若存在则认证通过
请求地址:https://qyapi.weixin..com/cgi-bin/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
企业微信接入文档
隐藏式
有些 Web 应用是纯前端应用,没有后端。这时就不能用上面的方式了,必须将令牌储存在前端,允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,所以称为(授权码)“隐藏式”(implicit)。
第一步,A 网站提供一个链接,要求用户跳转到 B 网站,授权用户数据给 A 网站使用。
https://b.com/oauth/authorize?response_type=token&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read
上面 URL 中,response_type参数为token,表示要求直接返回令牌。
第二步,用户跳转到 B 网站,登录后同意给予 A 网站授权。这时,B 网站就会跳回redirect_uri参数指定的跳转网址,并且把令牌作为 URL 参数,传给 A 网站。
https://a.com/oauth/callback?token=TOKEN
这种方式把令牌直接传给前端,是很不安全的。因此,只能用于一些安全要求不高的场景,并且令牌的有效期必须非常短,通常就是会话期间(session)有效,浏览器关掉,令牌就失效了。
密码式
如果你高度信任某个应用,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为”密码式”(password)。
第一步,A 网站要求用户提供 B 网站的用户名和密码。拿到以后,A 就直接向 B 请求令牌。
上面 URL 中,grant_type参数是授权方式,这里的password表示”密码式”,username和password是 B 的用户名和密码。
第二步,B 网站验证身份通过后,直接给出令牌。注意,这时不需要跳转,而是把令牌放在 JSON 数据里面,作为 HTTP 回应,A 因此拿到令牌。
这种方式需要用户给出自己的用户名/密码,显然风险很大,因此只适用于其他授权方式都无法采用的情况,而且必须是用户高度信任的应用。
第一步,A 应用在命令行向 B 发出请求。
上面 URL 中,grant_type参数等于client_credentials表示采用凭证式,client_id和client_secret用来让 B 确认 A 的身份。
第二步,B 网站验证通过以后,直接返回令牌。
这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,即有可能多个用户共享同一个令牌。
缺点:
优点:
授权token是短期的,到期会自动失效,用户自己无法修改。
授权token可以被数据所有者撤销,会立即失效,比如服务器删除某个token。
token有权限范围(scope),对于网络服务来说,只读令牌就比读写令牌更安全。
这些设计,保证了令牌既可以让第三方应用获得权限,同时又随时可控,不会危及系统安全。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/136524.html