基于cookie的认证机制
Cookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器端创建了一个Cookie对象;通过客户端带上来Cookie对象来与服务器端的session对象匹配来实现状态管理的。默认的,当我们关闭浏览器的时候,cookie会被删除。但可以通过修改cookie 的expire time使cookie在一定时间内有效;
基于HTTP Basic Auth的认证机制
应该来谈一谈基于token的认证和传统的session认证的区别.
我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
带来的问题?
- Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
- 扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
- CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
基于 HTTP Basic Auth的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。尤其是移动端HTTP Basic Auth简单点说, 就是每次请求API时,将用户的username和password放到header头中,然后在服务端进行验证。但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的越来越少。
一个简单的例子如下:
客户端的请求(用户名“”Aladdin”,口令, password “open sesame”):
GET /private/index.html HTTP/1.0
Host: localhost
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Authorization消息头的用户名和口令的值可以容易地编码和解码:
echo base64_encode("Aladdin:open sesame")."\n";
echo base64_decode("QWxhZGRpbjpvcGVuIHNlc2FtZQ==")."\n";
服务端的应答:
HTTP/1.0 200 OK
Server: HTTPd/1.0
Date: Sat, 27 Nov 2004 10:19:07 GMT
Content-Type: text/html
Content-Length: 10476
那怎样解决不安全的问题呢?
基于JWT的Http Token认证机制
JWT介绍
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
JWT的组成
JWT就是一个字符串,经过加密处理与校验处理的字符串,由三个部分组成。基于token的身份验证可以替代传统的cookie+session身份验证方法。三个部分分别如下:
header.payload.signature
header部分组成:
{ "
typ":"JWT",
"alg":"HS256"
}
这就是一个json串,两个字段都是必须的,alg字段指定了生成signature的算法,默认值为 HS256,可以自己指定其他的加密算法,如RSA.经过base64encode就可以得到 header.
payload 部分组成
简单的写法:
- $payload = [
- 'iss' => $issuer, //签发者
- 'iat' => $_SERVER['REQUEST_TIME'], //什么时候签发的
- 'exp' => $_SERVER['REQUEST_TIME'] + 7200, //过期时间
- 'uid' => 1111
- ];
复杂点:官方说法,三个部分组成(Reserved claims,Publicclaims,Private claims)
- $payload = [
- #非必须。issuer 请求实体,可以是发起请求的用户的信息,也可是jwt的签发者。
- "iss" => "http://example.org",
- #非必须。issued at。 token创建时间,unix时间戳格式
- "iat" => $_SERVER['REQUEST_TIME'],
- #非必须。expire 指定token的生命周期。unix时间戳格式
- "exp" => $_SERVER['REQUEST_TIME'] + 7200,
- #非必须。接收该JWT的一方。
- "aud" => "http://example.com",
- #非必须。该JWT所面向的用户
- "sub" => "jrocket@example.com",
- # 非必须。not before。如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟。
- "nbf" => 1357000000,
- # 非必须。JWT ID。针对当前token的唯一标识
- "jti" => '222we',
- # 自定义字段
- "GivenName" => "Jonny",
- # 自定义字段
- "name" => "Rocket",
- # 自定义字段
- "Email" => "jrocket@example.com",
- ];
payload 也是一个json数据,是表明用户身份的数据,可以自己自定义字段,很灵活。你也可以简单的使用,比如简单的方式。经过 json_encode 和base64_encode 就可得到 payload
signature组成部分
将 header和 payload使用header中指定的加密算法加密,当然加密过程还需要自定秘钥,自己生成一个字符串就可以了。
官网的例子:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
我们可以自己写一个 :
- <?php
- function base64url_encode($data)
- {
- }
-
- function base64url_decode($data)
- {
- }
-
- {
- $jwt = self::urlsafeB64Encode(json_encode(['type' => 'JWT', 'alg' => $alg])) . '.' . self::urlsafeB64Encode(json_encode($payload));
- return $jwt . '.' . self::signature($jwt, $key, $alg);
- }
-
- public static function signature(string $input, string $key, string $alg)
- {
- }
这三个部分使用 . 连接起来就是高大上的JWT,然后就可以使用了
JWT的使用流程
解释一下:
- 第一次认证:第一次登录,用户从浏览器输入用户名/密码,提交后到服务器的登录处理的 Action 层(Login Action)
- Login Action 调用认证服务进行用户名密码认证,如果认证通过, Login Action层调用用户信息服务获取用户信息(包括完整的用户信息及对应权限信息)
- 返回用户信息后, Login Action 从配置文件中获取 Token 签名生成的秘钥信息,进行 Token 的生成
- 生成 Token 的过程中可以调用第三方的 JWT Lib 生成签名后的 JWT 数据
- 完成 JWT 数据签名后,将其设置到 COOKIE 对象中,并重定向到首页,完成登录过程
JWT服务端验证过程
基于 Token 的认证机制会在每一次请求中都带上完成签名的 Token 信息,这个 Token 信息可能在 COOKIE
中,也可能在 HTTP 的 Authorization 头中
- 客户端(APP客户端或浏览器)通过 GET 或 POST 请求访问资源(页面或调用API)认证服务作为一个Middleware HOOK 对请求进行拦截,首先在 cookie 中查找 Token 信息,如果没有找到,则在 HTTP Authorization Head 中查找;
- 如果找到 Token 信息,则根据配置文件中的签名加密秘钥,调用 JWT Lib 对 Token
信息进行解密和解码; - 完成解码并验证签名通过后,对 Token 中的 exp 、 nbf 、 aud 等信息进行验证;
- 全部通过后,根据获取的用户的角色权限信息,进行对请求的资源的权限逻辑判断;
- 如果权限逻辑判断通过则通过 Response 对象返回;否则则返回 HTTP 401 ;
验证代码:
- <?php
- public static function decode(string $jwt, string $key)
- {
- return false;
- return false;
- if (self::signature($header64 . '.' . $payload64, $key, $header['alg']) !== $sign)
- return false;
- $time = $_SERVER['REQUEST_TIME'];
- return false;
- return false;
- return $payload;
- }
优势之处
- 支持跨域访问: Cookie 是不允许垮域访问的,这一点对 Token 机制是不存在的,前提是传输的用户认证信息通过HTTP头传输.
- 去耦: 不需要绑定到一个特定的身份验证方案。 Token 可以在任何地方生成,只要在你的 API 被调用的时候,你可以进行 Token 生成调用即可. 也可以用一套token认证代码来面对浏览器类客户端和非浏览器类客户端;
- 更适用于移动应用: 当你的客户端是一个原生平台 (iOS, Android,Windows 8等 )时, Cookie 是不被支持的,这时采用 Token 认证机制就会简单得多
- CSRF:因为不再依赖于 Cookie ,所以你就不需要考虑对 CSRF (跨站请求伪造)的防范。
- 性能: Token 中包含足够多的信息,在后续请求中减少查询数据库的几率
- 基于标准化:你的API可以采用标准化的 JSON Web Token (JWT) , 这个标准已经存在多个后端库 (.NET, Ruby, Java,Python, PHP) 和多家公司的支持 (如:Firebase,Google, Microsoft)
如何确保安全
- 如何保证用户名/密码验证过程的安全性:因为在验证过程中,需要用户输入用户名和密码,在这一过程中,用户名、密码等敏感信息需要在网络中传输。因此,在这个过程中建议采用HTTPS,通过SSL加密传输,以确保通道的安全性。
- 如果你是将用户提交的字符串存储到数据库的话(也针对SQL注入攻击),你需要在前端和服务端分别做过滤;