认证机制是一种用户确定用户身份或者权限的安全措施,比如用来验证某个用户是否有权限访问一个资源,如果认证通过,用户就可以成功访问,反之则会访问失败
常见的认证方式有四种,分别是 Basic、Digest、OAuth 和 Bearer
Basic
Basic Auth,也称为 HTTP 基本认证,是最简单的认证方式
Basic 认证步骤:
客户端在发送请求时,简单的把 用户名:密码
进行 base64 编码,包含在 HTTP Authorization Header 字段中发送给服务器
服务器收到请求后,解析出 Authorization 字段中的 base64 字符串,通过解码获取用户名和密码。拿着用户名和密码去数据库里比较,如果匹配到就认证通过
Authorization 是请求标头中专门存放用户身份依据的字段,后面的 Digest、OAuth 和 Bearer 认证方式都会用到这个字段
详细了解:Authorization - HTTP | MDN (mozilla.org)
通过 base64 编码,可以将密码以非明文的方式传输,增加一些安全性。但是 base64 只是一种编码方式,而不是加密方式。任何人在拦截 HTTP 请求的情况下可以轻松地解码并获取用户名和密码
即使把密码加密之后再通过 base64 编码,入侵者仍然可以通过加密后的密码进行重放攻击
因此,Basic 认证方式不适合用于传输敏感信息或者在不安全的网络中使用,除非配合 SSL/TLS 来确保传输层的安全
Digest
Digest Auth(摘要认证)是另一种 HTTP 认证协议,它兼容了 Basic 认证方式,也修复了 Basic 的严重缺陷,提供了更安全的方式进行验证
摘要认证采用质询/响应的方式。整体过程简单来说就是,一开始客户端先向服务端请求认证要求,接着使用服务端响应回的质询码计算生成响应码。最后携带响应码再次请求服务端进行认证
Digest 认证步骤:
整体步骤分为三步:
1、客户端请求服务端,服务端不知道客户端是否真的知道密码,请求失败,返回 401 Unauthorized
,并返回 WWW-Authenticate
字段,该字段中包含认证所需要的信息:
WWW-Authenticate
必须包含 realm
和 nonce
这两个字段。其中 nonce 字段是每一次返回 401 响应时生成的任意随机字符串,该字符串通常由base64 编码的十六进制数组成(实际内容依赖服务器的具体实现)
2、客户端根据WWW-Authenticate
中的信息,选择加密算法,结合随机数 nonce,计算出响应码 response。最后带着响应码再次请求服务端
3、服务器将客户端提供的响应码与服务器内部计算出的结果进行对比。如果匹配,就说明客户端知道密码,认证通过,并返回一些与授权会话相关的附加信息,放在 Authorization-Info
中
摘要认证可以避免密码以明文方式发送,并且可以防止重放攻击
因为服务端向客户端发送的密码随机数 nonce 每次请求都会变化,客户端计算出的响应码会随着 nonce 的变化而变化,入侵者拿着上一次请求携带的响应码去请求服务端肯定会失败,而且想要根据服务端响应回的 nonce 计算出正确的响应码,就必须知道密码
摘要认证可以保护密码,比基本认证安全很多。但摘要认证并不能保护内容
如果入侵者截获了客户端和服务端的所有通讯,就有可能可能推测出密码;摘要认证没有对通信双方进行验证,不能防止用户伪装
OAuth
OAuth(开放授权)是一种开放标准,用于授权用户在客户端应用程序或网站上访问受保护资源的操作流程
OAuth 允许用户在无需提供用户名和密码的情况下授权第三方应用程序访问其受保护的资源。这种授权机制使得用户可以控制自己的数据,并选择允许哪些应用程序访问其数据
OAuth 目前的版本是 2.0,一共有四种授权方式:
-
授权码模式(authorization code)
-
简化模式(implicit)
-
密码模式(resource owner password credentials)
-
客户端模式(client credentials)
再详细介绍各个模式之前,需要先了解几个专有名词
来看一个例子,现在要根据微信账号登录百度
点击下方的微信图标后,会跳转到网页要求扫码,用手机扫码后,会询问是否同意百度申请微信账号的昵称、头像信息
点击允许后,页面重定向到登录完成页面,完成使用微信账号登录百度
在这整个流程中,有几个关键角色
-
第三方应用程序,又称客户端(client),即例子中的“百度”
-
HTTP 服务提供者,简称“服务提供商”,即例子中的“微信”
-
资源所有者,又称“用户”
-
用户代理,即例子中的浏览器(通过浏览器打开百度登录的页面)
-
授权服务器,服务提供商专门用来处理认证授权的服务器
-
资源服务器,服务提供商存放用户资源的服务器,它与认证服务器可以是同一台服务器,也可以不是同一台
授权码模式
授权码模式是功能最完整、流程最严密的授权模式,也是目前最常用的模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动
还是根据上面的例子来详细说明每一个步骤
步骤:
1、用户通过代理(浏览器)访问客户端
用户通过浏览器打开百度登录页面,点击微信图标,使用微信账号登录百度
2、客户端将用户导向授权服务器
这一步是客户端调用服务提供商提供的授权接口
调用接口时有几个重要参数:
-
client_id(图中为appid,这可能是微信平台自定义的参数,如果选用QQ或者微博登录,携带的参数是client_id,效果是一样的),客户端ID,这是百度在微信的授权服务器中注册得到的,必选项
-
response_type,授权类型,使用授权码模式时固定填 code,必选项
-
scope,申请的权限范围,可选项
-
redirect_uri,重定向URI,用于授权成功后跳转,可选项
-
state,表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值,通过这个参数可以防止 csrf 攻击
3、授权服务器询问用户是否给予客户端授权
用微信扫码后,可以选择是否同意百度申请微信账号的昵称、头像信息
4、用户同意授权后,授权服务器将用户导向客户端指定的“重定向URI”,并附上授权码
5、客户端收到授权码,使用授权码向授权服务器申请令牌
6、授权服务器核对授权码,向客户端发送令牌
7、客户端通过令牌向资源服务器请求资源
后面几步是在客户端的后端实现的,对用户不可见,最后能看到的结果就是成功用微信账号登录了百度
授权码模式通过前端传送授权码,后端存储令牌,与资源的通信都在后端,可以避免令牌的泄露导致的安全问题
简化模式
简化模式和授权码模式很类似,只不过不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了“授权码”这个步骤
简化模式中所有步骤都在浏览器中完成,令牌对用户是可见的,且客户端不需要认证
认证步骤:
1、用户通过代理(浏览器)访问客户端
2、客户端将用户导向授权服务器
3、授权服务器询问用户是否给予客户端授权
4、用户同意授权后,授权服务器向客户端发送令牌
5、客户端通过令牌向资源服务器请求资源
简化模式适用于没有后台程序的单页面应用,存在着“中间人攻击”的风险,安全性不高
密码模式
密码模式中,用户直接把用户名和密码告诉第三方应用,第三方应用使用用户名和密码去向服务提供商索要授权,获取令牌
认证步骤:
1、客户端向用户发出获取用户名和密码的请求
2、用户同意后,客户端凭借用户名和密码向服务提供商换取令牌
3、服务提供商验证用户身份后,给出客户端令牌
4、客户端凭借令牌访问资源服务器请求资源
密码式的授权方式中用户必须提供密码,通常用于用户高度信任某应用的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品
客户端模式
客户端模式中,客户端应用程序直接与授权服务器进行通信,不涉及用户的参与
严格来说,客户端模式并不属于 OAuth 要解决的问题,在这种模式中,用户直接向客户端注册,客户端使用自己的客户端表示(client ID)和客户端密钥(client secret)向服务提供商申请令牌,其实不存在用户的授权问题
认证步骤:
1、客户端向认证服务器进行身份验证,并要求一个访问令牌
2、认证服务器确认无误后,向客户端提供令牌
3、客户端通过令牌访问资源
客户端模式不涉及用户的操作和授权交互,适用于一些没有前端的命令行应用
Bearer
Bearer 认证,也称为令牌认证,是一种 HTTP 身份验证方法。Bearer 认证的核心是 bearer token
bearer 是一个加密字符串,通常由服务端根据密钥生成,客户端在请求服务端时,必须在请求头中包含 Authorization: Bearer <token>
服务端收到请求后,解析出 token
并验证合理性,如果校验通过,则认证通过
和基本认证 Basic 一样,Bearer 也需要配合 HTTPS 使用,用来保证安全性
当前最常用的 token 编码方式是 JWT(JSON Web Token)
JWT 由 JSON 数据格式组成,通过 HASH 散列算法生成一个字符串,该字符串可以用来进行授权和信息交换
使用 JWT Token 进行认证具体可以分为四步:
1、客户端使用用户名和密码请求登录
2、服务端收到登录请求后验证用户名和密码,如果通过验证,就签发一个 token 返回给客户端
3、客户端把收到的 token 存放在 cookie 或 LocalStorage 等地方存储,之后客户端的每次请求都会携带该 token
4、服务端再次收到请求后(不一定是登录请求),会解析请求中的 token,解析成功则进行对应的业务逻辑
JWT 由三部分组成,分别是 Header、Payload 和 Signature,它们之间用圆点.
连接,例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJpYW0uYXBpLm1hcm1vdGVkdS5jb20iLCJleHAiOjE2NDI4NTY2MzcsImlkZW50aXR5IjoiYWRtaW4iLCJpc3MiOiJpYW0tYXBpc2VydmVyIiwib3JpZ19pYXQiOjE2MzUwODA2MzcsInN1YiI6ImFkbWluIn0.Shw27RKENE_2MVBq7-c8OmgYdF92UmdwS8xE-Fts2FM
JWT 中,每部分包含的信息为:
分别来看:
Header
Header 中包含两部分信息,分别是 token 的类型和所用的加密算法
例如:
{ "typ": "JWT", "alg": "HS256" }
typ 说明 token 的类型是 JWT,alg 说明加密算法是 HS256,alg算法可以有多种
把这段 JSON 数据进行 base64 编码,就得到了 Header
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
在某些场景下,可能还会有 kid 选项,用来标识一个密钥ID,例如:
{ "alg": "HS256", "kid": "XhbY3aCrfjdYcP1OFJRu9xcno8JzSbUIvGE2", "typ": "JWT" }
Payload
Payload 中携带 token 的具体内容由三部分组成:JWT标准中注册的声明(可选)、公共的声明、私有的声明
JWT 标准中注册的声明部分,有以下标准字段:
例如:
{ "aud": "iam.authz.marmotedu.com", "exp": 1604158987, "iat": 1604151787, "iss": "iamctl", "nbf": 1604151787 }
进行 base64 编码后,得到
eyJhdWQiOiJpYW0uYXV0aHoubWFybW90ZWR1LmNvbSIsImV4cCI6MTYwNDE1ODk4NywiaWF0IjoxNjA0MTUxNzg3LCJpc3MiOiJpYW1jdGwiLCJuYmYiOjE2MDQxNTE3ODd9
除此之外,还有公共的声明和私有的声明。公共的声明可以添加任何的需要的信息,一般添加用户的相关信息或其他业务需要的信息,注意不要添加敏感信息
私有声明是客户端和服务端所共同定义的声明,因为base64是对称解密的,所以一般不建议存放敏感信息
Signature
Signature 是 token 的签名部分,把 Header 和 Payload 分别 base64 编码后,用.
连接,然后再使用 Header 声明的加密方式,利用 secretKey(密钥)对连接后的字符串进行加密,就得到了 Signature
secretKey 保存在服务器中,一般通过配置文件保存,例如
jwt: key: dfVp0K8LZeJLZHYmHdb1VdyRrACKpqoo #服务端密钥 timeout: 24h # token过期时间(小时) max-refresh: 24h # token 更新时间(小时)
密钥一定不能泄露,密钥泄露后,入侵者可以使用该密钥来签发 JWT Token,从而入侵系统
最后生成的 Token 如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJpYW0uYXV0aHoubWFybW90ZWR1LmNvbSIsImV4cCI6MTYwNDE1ODk4NywiaWF0IjoxNjA0MTUxNzg3LCJpc3MiOiJpYW1jdGwiLCJuYmYiOjE2MDQxNTE3ODd9.LjxrK9DuAwAzUD8-9v43NzWBN7HXsSLfebw92DKd1JQ
签发的 token 会在客户端以后的请求中携带,服务端收到 token 后,先解析出 Header 和 Payload,然后使用 Header 中声明的加密算法对 header.payload 进行加密,得到 Signature,拿着这个 Signature 与收到的 token 中的 Signature 对比,如果相同则验证通过,不相同则解析 token 失败,返回对应的错误信息
最后,不要存敏感信息(密码等)在 token 中
总结
在开发应用程序时,需要认证机制来保证应用的安全。认证就是用来验证某个用户是否有执行某种功能的权限,如查询、创建、修改等操作
目前常用的有四种常用的认证方式:Basic、Digest、OAuth、Bearer。其中 Basic 和 Bearer 用得最多
而不管是 Basic 认证还是 Bearer 认证,都需要结合HTTPS来使用,来最大程度地保证请求的安全性
参考资料:
理解OAuth 2.0 - 阮一峰的网络日志 (ruanyifeng.com)
《图解HTTP (上野宣)》