开篇
身份认证系列进行到现在,大部分内容都是偏理论的,今天我们就来点轻松的,直接上实战,纸上得来终觉浅,绝知此事要躬行呀。
正如本篇题目所示,我们来看看如何通过go语言的gin web框架来实现一个简单的认证功能,不过就算你没用过gin也没关系,把代码部分当做伪代码来看就行,毕竟认证功能是语言无关的嘛,先把内功打深厚,然后再学招式就简单得多了。
四种基本的认证方式
认证是用来验证某个用户是否具有访问系统的权限。如果认证通过,该用户就可以访问系统,从而创建、修改、删除、查询平台支持的资源。认证需要你证明你是谁,是进入系统的第一道关卡,认证之后才是授权。
常见的认证方式有四种,分别是 Basic、Digest、OAuth 和 Bearer。
Basic
Basic 认证(基础认证),是最简单的认证方式。它简单地将用户名:密码进行 base64 编码后,放到 HTTP Authorization Header 中。HTTP 请求到达后端服务后,后端服务会解析出 Authorization Header 中的 base64 字符串,解码获取用户名和密码,并将用户名和密码跟数据库中记录的值进行比较,如果匹配则认证通过。例如:
$ basic=`echo -n 'admin:Admin@2021'|base64`
$ curl -XPOST -H"Authorization: Basic ${basic}" http://127.0.0.1:8080/login
通过 base64 编码,可以将密码以非明文的方式传输,增加一定的安全性。但是,base64 不是加密技术,入侵者仍然可以截获 base64 字符串,并反编码获取用户名和密码。另外,即使 Basic 认证中密码被加密,入侵者仍可通过加密后的用户名和密码进行重放攻击。
所以,Basic 认证虽然简单,但极不安全。使用 Basic 认证的唯一方式就是将它和 SSL 配合使用,来确保整个认证过程是安全的。
这里需要注意,在设计系统时,要遵循一个通用的原则:不要在请求参数中使用明文密码,也不要在任何存储中保存明文密码。
Digest
Digest 认证(摘要认证),是另一种 HTTP 认证协议,它与基本认证兼容,但修复了基本认证的严重缺陷。Digest 具有如下特点:
- 绝不会用明文方式在网络上发送密码。
- 可以有效防止恶意用户进行重放攻击。
- 可以有选择地防止对报文内容的篡改。
摘要认证的过程见下图:
在上图中,完成摘要认证需要下面这四步:
- 客户端请求服务端的资源。
- 在客户端能够证明它知道密码从而确认其身份之前,服务端认证失败,返回401 Unauthorized,并返回WWW-Authenticate头,里面包含认证需要的信息。
- 客户端根据WWW-Authenticate头中的信息,选择加密算法,并使用密码随机数 nonce,计算出密码摘要 response,并再次请求服务端。
- 服务器将客户端提供的密码摘要与服务器内部计算出的摘要进行对比。如果匹配,就说明客户端知道密码,认证通过,并返回一些与授权会话相关的附加信息,放在 Authorization-Info 中。
WWW-Authenticate头中包含的信息见下表:
字段名 | 说明 |
---|---|
username | 用户名 |
realm | 服务器返回的realm,一般是域名 |
method | HTTP请求方法 |
nonce | 服务器发给客户端的随机字符串 |
nc(nonce count) | 请求的次数,用于标记、计数、防止重放攻击 |
cnonce(client nonce) | 客户端发送给服务端的随机字符串,用于客户端对服务端的认证 |
qop | 保护质量参数,一般是auth或auth-int,这会影响摘要的算法 |
uri | 请求的uri |
response | 客户端根据根据算法计算出的摘要 |
虽然使用摘要可以避免密码以明文方式发送,一定程度上保护了密码的安全性,但是仅仅隐藏密码并不能保证请求是安全的。因为请求(包括密码摘要)仍然可以被截获,这样就可以重放给服务器,带来安全问题。
为了防止重放攻击,服务器向客户端发送了密码随机数 nonce,nonce 每次请求都会变化。客户端会根据 nonce 生成密码摘要,这种方式,可以使摘要随着随机数的变化而变化。服务端收到的密码摘要只对特定的随机数有效,而没有密码的话,攻击者就无法计算出正确的摘要,这样我们就可以防止重放攻击。
摘要认证可以保护密码,比基本认证安全很多。但摘要认证并不能保护内容,所以仍然要与 HTTPS 配合使用,来确保通信的安全。
Oauth
Oauth认证和授权我们在之前的文章中已经详细描述,这里就不再赘述了。
Bearer
Bearer 认证,也称为令牌认证,是一种 HTTP 身份验证方法。Bearer 认证的核心是 bearer token。bearer token 是一个加密字符串,通常由服务端根据密钥生成。客户端在请求服务端时,必须在请求头中包含Authorization: Bearer 。服务端收到请求后,解析出 ,并校验 的合法性,如果校验通过,则认证通过。跟Basic认证一样,Bearer 认证需要配合 HTTPS 一起使用,来保证认证安全性。
当前最流行的 token 编码方式是 JSON Web Token(JWT,音同 jot,详见 JWT RFC 7519)。接下来,我通过讲解 JWT 认证来帮助你了解 Bearer 认证的原理。
基于 JWT 的 Token 认证机制实现
在典型业务场景中,为了区分用户和保证安全,必须对 API 请求进行鉴权,但是不能要求每一个请求都进行登录操作。
合理做法是,在第一次登录之后产生一个有一定有效期的 token,并将它存储在浏览器的 Cookie 或 LocalStorage 之中。之后的请求都携带这个 token ,请求到达服务器端后,服务器端用这个 token 对请求进行认证。
在第一次登录之后,服务器会将这个 token 用文件、数据库或缓存服务器等方法存下来,用于之后请求中的比对。或者也可以采用更简单的方法:直接用密钥来签发 Token。这样,就可以省下额外的存储,也可以减少每一次请求时对数据库的查询压力。这种方法在业界已经有一种标准的实现方式,就是 JWT。
JWT的具体格式我们之前也有文章写过,想更进一步了解JWT的读者可以翻一下以前的文章。
JWT 认证流程
使用 JWT Token 进行认证的流程如下图:
具体可以分为四步:
- 客户端使用用户名和密码请求登录。
- 服务端收到请求后,会去验证用户名和密码。如果用户名和密码跟数据库记录不一致,则验证失败;如果一致则验证通过,服务端会签发一个 Token 返回给客户端。
- 客户端收到请求后会将 Token 缓存起来,比如放在浏览器 Cookie 中或者 LocalStorage 中,之后每次请求都会携带该 Token。
- 服务端收到请求后,会验证请求中的 Token,验证通过则进行业务逻辑处理,处理完后返回处理后的结果。
最后,关于 JWT 的使用,还有两点建议:
- 不要存放敏感信息在 Token 里;
- Payload 中的 exp 值不要设置得太大,一般开发版本 7 天,线上版本 2 小时。当然,你也可以根据需要自行设置。
总结
Basic 认证通过用户名和密码来进行认证,主要用在用户登录场景;Bearer 认证通过 Token 来进行认证,通常用在 API 调用场景。不管是 Basic 认证还是 Bearer 认证,都需要结合 HTTPS 来使用,来最大程度地保证请求的安全性。
Basic 认证简单易懂,但是 Bearer 认证有一定的复杂度,所以这一讲的后半部分通过 JWT Token,讲解了 Bearer Token 认证的原理。
JWT Token 是 Bearer 认证的一种比较好的实现,主要包含了 3 个部分:
- Header:包含了 Token 的类型、Token 使用的加密算法。在某些场景下,你还可以添加 kid 字段,用来标识一个密钥 ID。
- Payload:Payload 中携带 Token 的具体内容,由 JWT 标准中注册的声明、公共的声明和私有的声明三部分组成。
- Signature:Signature 是 Token 的签名部分,程序通过验证 Signature 是否合法,来决定认证是否通过。
下一篇我们通过实际代码来看一下gin框架怎么通过basic和bearer认证来实现一套完整的认证机制。