常见的服务器鉴权方式
Basic Authentication
basic是最简单的认证机制,客户端向服务器端请求数据时,如果未被认证,服务器会向客户端发送验证请求.
HTTP/1.0 401 Unauthorised
Server: SokEvo/1.0
WWW-Authenticate: Basic realm=”google.com”
Content-Type: text/html
Content-Length: xxx
当客户端收到401返回值时,将自动弹出一个登陆窗口,要求用户输入用户名和密码.
用户输入用户名和密码后,经过浏览器base64编码后,再次发送请求
Get /index.html HTTP/1.0
Host:www.google.com
Authorization: Basic d2FuZzp3YW5n
服务器收到请求后将用户信息取出解密,对比验证.
session-cookie
session
是缓存在服务端的,cookie
则是缓存在客户端,他们都由服务端生成,为了弥补 Http
协议无状态的缺陷。
- 服务器在接受客户端首次访问时在服务器端创建seesion,然后保存seesion(我们可以将seesion保存在内存中,也可以保存在redis中,推荐使用后者),然后给这个session生成一个唯一的标识字符串,然后在 响应头中种下这个唯一标识字符串。
- 签名。这一步通过秘钥对sid进行签名处理,避免客户端修改sid。(非必需步骤)
- 浏览器中收到请求响应的时候会解析响应头,然后将sid保存在本地cookie中,浏览器在下次http请求的请求头中会带上该域名下的cookie信息。
- 服务器在接受客户端请求时会去解析请求头cookie中的sid,然后根据这个sid去找服务器端保存的该客户端的session,然后判断该请求是否合法。
Token
一般 token
由用户信息、时间戳和由 hash
算法加密的签名构成。
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个
Token
,再把这个Token
发送给客户端 - 客户端收到
Token
以后可以把它存储起来,比如放在Cookie
里或者Local Storage
里 - 客户端每次向服务端请求资源的时候需要带着服务端签发的
Token
- 服务端收到请求,然后去验证客户端请求里面带着的
Token
(request头部添加Authorization),如果验证成功,就向客户端返回请求的数据 ,如果不成功返回401错误码,鉴权失败。
token认证可以支持多种客户端,而不仅是浏览器,且不受同源策略的影响.
基于token的解决方案有很多,常用的是JWT.JWT
的原理是,服务器认证以后,生成一个 JSON
对象,这个 JSON
对象肯定不能裸传给用户,那谁都可以篡改这个对象发送请求。因此这个 JSON
对象会被服务器端签名加密后返回给用户,返回的内容就是一张令牌,以后用户每次访问服务器端就带着这张令牌
JWT由三部分组成: Header.Payload.Signature. Header存放元数据、Payload存放实际传递的数据,因为是Base64编码的,所以不能存放秘密信息.Signature是前两个部分的签名,防止数据篡改.
特点:
- JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
- JWT 不加密的情况下,不能将秘密数据写入 JWT。
- JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
- JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
- JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
- 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
OAuth2.0
OAuth 2.0是目前最流行的授权机制,用来授权第三方应用,获取用户数据.
数据所有者告诉系统,同意第三方应用进入系统,获取这些数据.系统从而产生一个短期的进入令牌用来代替密码,供第三方应用使用.
令牌是短期有效的,可随时被数据所有者撤销,并且有权限范围. 以上特点保证了令牌既可以让第三方应用获得权限,同时又随时可控,不会危机系统安全.
四种授权方式
OAuth引入了一个授权层,用来分离两种不同的角色: 客户端和资源所有者.资源所有者同意后,资源服务器向客户端颁发令牌,客户端通过令牌去请求数据.资源服务器会与对应的授权服务器通信、校验令牌来确定客户端是否可以访问资源.
OAuth的核心就是向第三方应用办法令牌, OAuth规定了四种获得令牌的流程:
-
授权码 (authorization-code): 第三方应用先申请一个授权码, 然后再用该码获取令牌
这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。
A网站想要得到B网站的令牌,就会提供B网站的带参数链接,用户跳转到B网站,B网站会要求用户登陆,然后询问"A 网站要求获得 xx 权限,你是否同意?.
Response_type表示要求返回授权码
Client_id 让B知道是谁在请求(一个应用要求 OAuth 授权,必须先到对方网站登记,让对方知道是谁在请求。然后该网站会返回client_id和client_secret)
Scop 表示授权范围
https://b.com/oauth/authorize? response_type=code& client_id=CLIENT_ID& redirect_uri=CALLBACK_URL& scope=read
同意后,B网站跳回redirect_uri参数指定的网址,并附带授权码
https://a.com/callback?code=AUTHORIZATION_CODE
A拿到授权码后,就可以在后端向B网站请求令牌
https://b.com/oauth/token? client_id=CLIENT_ID& client_secret=CLIENT_SECRET& grant_type=authorization_code& code=AUTHORIZATION_CODE& redirect_uri=CALLBACK_URL
B网站收到请求后,向redirect_uri发送一段json数据, A网站在后端就拿到了.
{ "access_token":"ACCESS_TOKEN", "token_type":"bearer", "expires_in":2592000, "refresh_token":"REFRESH_TOKEN", "scope":"read", "uid":100101, "info":{...} }
-
隐藏式 (implicit)
有些 Web 应用是纯前端应用,没有后端。这时就不能用上面的方式了,必须将令牌储存在前端。RFC 6749 就规定了第二种方式,允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,所以称为(授权码)"隐藏式"(implicit)。
https://b.com/oauth/authorize? response_type=token& client_id=CLIENT_ID& redirect_uri=CALLBACK_URL& scope=read
response_type为token表示直接返回令牌.
https://a.com/callback#token=ACCESS_TOKEN Note: #代表网页中的一个位置,当浏览器读取这个URL时,会自动将位置滚动到可视区域. 为网页位置指定标识符,有两个方法。一是使用锚点,比如<a name="print"></a>,二是使用id属性,比如<div id="print" > #是用来指导浏览器动作的,对服务器端完全无用。所以,HTTP请求中不包括#。
注意,令牌的位置是 URL 锚点(fragment),而不是查询字符串(querystring),这是因为 OAuth 2.0 允许跳转网址是 HTTP 协议,因此存在"中间人攻击"的风险,而浏览器跳转时,锚点不会发到服务器,就减少了泄漏令牌的风险。>
这种方式把令牌直接传给前端,是很不安全的。因此,只能用于一些安全要求不高的场景,并且令牌的有效期必须非常短,通常就是会话期间(session)有效,浏览器关掉,令牌就失效了。
-
密码式 (password)
如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为"密码式"(password)。
A网站直接要求用户提供B网站的用户名和密码,拿到以后,A就直接向B请求令牌,B网站验证身份后直接在http回应中给出令牌,不需要跳转.
https://oauth.b.com/token? grant_type=password& username=USERNAME& password=PASSWORD& client_id=CLIENT_ID
-
客户端凭证 (client credentials)
适用于没有前端的命令行应用,即在命令行下请求令牌。
https://oauth.b.com/token? grant_type=client_credentials& client_id=CLIENT_ID& client_secret=CLIENT_SECRET
使用令牌
A 网站拿到令牌以后,就可以向 B 网站的 API 请求数据了。
此时,每个发到 API 的请求,都必须带有令牌。具体做法是在请求的头信息,加上一个Authorization
字段,令牌就放在这个字段里面。
curl -H "Authorization: Bearer ACCESS_TOKEN" \
"https://api.b.com"
更新令牌
OAuth 2.0 允许用户自动更新令牌.B 网站颁发令牌的时候,一次性颁发两个令牌,一个用于获取数据,另一个用于获取新的令牌(refresh token 字段)。令牌到期前,用户使用 refresh token 发一个请求,去更新令牌。
https://b.com/oauth/token?
grant_type=refresh_token&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&
refresh_token=REFRESH_TOKEN