HTTP认证:Oauth2, JWT, BASIC

Posted 爱吃橘子的小朋友

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HTTP认证:Oauth2, JWT, BASIC相关的知识,希望对你有一定的参考价值。

一、OAuth2认证

OAuth2是一个关于授权的开放标准,核心思路是通过各类认证手段(具体什么手段OAuth2不关心)认证用户身份,并颁发token(令牌),
使得第三方应用可以使用该token(令牌)在限定时间、限定范围访问指定资源。

Oauth请求流程


OAuth2相关概念:

资源所有者(resources owner):拥有被访问资源的用户
客户端/第三方应用(client):第三方应用,获取资源服务器提供的资源
授权服务器(authorization server):认证服务器,提供授权许可code、令牌token等
资源服务器(resource server):资源服务器,拥有被访问资源的服务器,需要通过token来确定是否有权限访问

OAuth2几种模式:
获取令牌的方式主要有四种,分别是授权码模式、隐式授权码模式(简单模式)、密码模式和客户端模式。

1)授权码模式(authorization code)
这种模式是最安全的OAuth2的授权模式。设计了auth code,通过这个code再获取token,最后通过token获取资源。支持refresh token。

应用场景:
各大应用内的qq,微信,微博登录等。比如某应用内的qq登录,过程如下:
a.用户点击qq登录,会先跳转到qq登录页面,这时请求已经跳转到qq服务器了,然后用户输入账号或者扫码登录,这时所有请求都在qq服务器完成。
b.用户正确登录后,qq服务器返回用户的code给第三方应用,然后第三方应用再使用code去授权服务器请求获取token。(这一步用户不可见)
c.第三方应用获取到token后,再使用token获取用户的qq名称,头像等信息。

优缺点:
优点:用户可以控制自身的一些权限是否给第三方,第三方只能获取到用户临时产生的一个访问的code,安全性。
缺点:认证过程繁琐。

2)隐式授权码模式/简单模式(implicit)
和授权码模式类似,只不过少了获取code的步骤,是直接获取令牌token的,适用于公开的浏览器单页应用,令牌直接从授权服务器返回,不支持刷新令牌,且没有code安全保证,令牌容易因为被拦截窃听而泄露。
不支持refresh token

3)密码模式(resource owner password credentials)
这种模式是最不推荐的,因为client可能存了用户密码
这种模式主要用来做遗留项目升级为oauth2的适配方案
当然如果client是自家的应用,也是可以
支持refresh token

4)客户端模式(client credentials)
这种模式直接根据client的id和密钥即可获取token,无需用户参与
这种模式比较合适消费api的后端服务,比如拉取一组用户信息等
不支持refresh token,主要是没有必要

二、JWT机制

JWT概念:

JWT全称为Json Web Token,随着微服务架构的流行而越来越火,号称新一代的认证技术。
OAuth2中使用token验证用户登录合法性,但token最大的问题是不携带用户信息,资源服务器无法在本地进行验证,每次对于资源的访问,资源服务器都需要向认证服务器发起请求,一是验证token的有效性,二是获取token对应的用户信息。如果有大量的此类请求,无疑处理效率是很低,且认证服务器会变成一个中心节点,这在分布式架构下很影响性能。
JWT就是在这样的背景下诞生的,从本质上来说,jwt和OAuth2没有可比性。普通的oauth2颁发的就是一串随机hash字符串,本身无意义,而jwt使用一种特殊格式的token,token是有特定含义的,分为三部分:
1. Header
2. Payload
3. Signature
这三部分均用base64进行编码,并使用.进行分隔。一个典型的jwt格式的token类似xxxxx.yyyyy.zzzzz。

jwt其实并不是什么高深莫测的技术,相反非常简单。认证服务器通过对称或非对称的加密方式利用payload生成signature,并在header中申明签名方式,仅此而已。通过这种本质上极其传统的方式,jwt可以实现分布式的token验证功能,即资源服务器通过事先维护好的对称或者非对称密钥(非对称的话就是认证服务器提供的公钥),直接在本地验证token,这种去中心化的验证机制非常适合分布式架构。jwt相对于传统的token来说,解决以下两个痛点:
1、通过验证签名,对于token的验证可以直接在资源服务器本地完成,不需要连接认证服务器;
2、在payload中可以包含用户相关信息,这样就轻松实现了token和用户信息的绑定;
如果认证服务器颁发的是jwt格式的token,那么资源服务器就可以直接自己验证token的有效性并绑定用户,这无疑大大提升了处理效率且减少了单点隐患。

JWT 标准的 Token 有三个部分:

  • header

  • payload

  • signature

中间用点分隔开,并且都会使用 Base64 编码,所以真正的 Token 看起来像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

Header

header 部分主要是两部分内容,一个是 Token 的类型,另一个是使用的算法,比如下面类型就是 JWT,使用的算法是 HS256。

{ "typ": "JWT", "alg": "HS256"}

上面的内容要用 Base64 的形式编码一下,所以就变成这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Payload


Payload 里面是 Token 的具体内容,这些内容里面有一些是标准字段,你也可以添加其它需要的内容。下面是标准字段:

  • iss:Issuer,发行者

  • sub:Subject,主题

  • aud:Audience,观众

  • exp:Expiration time,过期时间

  • nbf:Not before

  • iat:Issued at,发行时间

  • jti:JWT ID

比如下面这个 Payload ,用到了 iss 发行人,还有 exp 过期时间。另外还有两个自定义的字段,一个是 name ,还有一个是 admin 。

{ "iss": "ninghao.net", "exp": "1438955445", "name": "wanghao", "admin": true}

使用 Base64 编码以后就变成了这个样子:

eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ


Signature


JWT 的最后一部分是 Signature ,这部分内容有三个部分,先是用 Base64 编码的 header.payload ,再用加密算法加密一下,加密的时候要放进去一个 Secret ,这个相当于是一个密码,这个密码秘密地存储在服务端。

  • header

  • payload

  • secret

$encodedString = base64_encode(header) + "." + base64_encode(payload);HMACSHA256($encodedString, 'secret');

处理完成以后看起来像这样:

SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

最后这个在服务端生成并且要发送给客户端的 Token 看起来像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

客户端收到这个 Token 以后把它存储下来,下回向服务端发送请求的时候就带着这个 Token 。服务端收到这个 Token ,然后进行验证,通过以后就会返回给客户端想要的资源。

适用场景:
一次性的身份认证、api的鉴权等,这些场景能充分发挥jwt无状态以及分布式验证的优势。

不适用的场景:
不要试图用jwt去代替session。这种模式下其实传统的session+cookie机制工作的更好,jwt因为其无状态和分布式,事实上只要在有效期内,是无法作废的,用户的签退更多是一个客户端的签退,服务端token仍然有效,你只要使用这个token,仍然可以登陆系统。另外一个问题是续签问题,使用token,无疑令续签变得十分麻烦,当然你也可以通过redis去记录token状态,并在用户访问后更新这个状态,但这就是硬生生把jwt的无状态搞成有状态了,而这些在传统的session+cookie机制中都是不需要去考虑的。

三、HTTP Basic认证

每次客户端请求都需带上Authorization请求头, 值为"Basic xxx"。xxx为对用户名和密码进行Base64编码后的值。若客户端是浏览器,则浏览器会提供一个输入用户名和密码的对话框,用户输入用户名和密码后,浏览器会保存用户名和密码,用于构造Authorization值。当关闭浏览器后,用户名和密码将不再保存。

用户名/密码经过Base64加码后,这个Base64码值可以轻易被解码并获得用户名/密码,所以此认证方式并不安全。为了传输安全,需要配合SSL使用。


1. 客户端(例如Web浏览器):服务器,请把/family/son.jpg 图片传给我。

GET /family/son.jpg HTTP/1.1

2.服务器:客户端你好,这个资源在安全区family里,是受限资源,需要基本认证,请带上你的用户名和密码再来

HTTP/1.1 401 Authorization Requiredwww-Authenticate: Basic realm= "family"

服务器会返回401,告知客户端这个资源需要使用基本认证的方式访问,我们可以看到在 www-Authenticate这个Header里面 有两个值,Basic:说明需要基本认证,realm:说明客户端需要输入这个安全区的用户名和密码,而不是其他区的。因为服务器可以为不同的安全区设置不同的用户名和密码。如果服务器只有一个安全区,那么所有的基本认证用户名和密码都是一样的。


3. 客户端:服务器,我已经按照你的要求,携带了相应的用户名和密码信息了,你看一下。

如果客户端是浏览器,那么此时就会弹出一个弹窗,让用户输入用户名和密码。

Basic 内容为:base64_encode(用户名:密码) 。如下所示

GET /family/son.jpg HTTP/1.1Authorization: Basic U2h1c2hlbmcwMDcldUZGMUFzczAwNw==


4. 服务器:客户端你好,我已经校验了你的用户名和密码,是正确的,这是你要的资源。

HTTP/1.1 200 OKContent-type: image/jpg...






以上是关于HTTP认证:Oauth2, JWT, BASIC的主要内容,如果未能解决你的问题,请参考以下文章

HTTP验证大法(Basic Auth,Session, JWT, Oauth, Openid)

理解JWT(JSON Web Token)认证

Akka-CQRS(15)- Http标准安全解决方案:OAuth2+JWT

理解JWT(JSON Web Token)认证及实践

SpringSpring Security OAuth2 JWT 认证

Spring OAuth2 和 JWT 认证信息