微服务架构中的身份验证问题 :JSON Web Tokens( JWT)

本文翻译自:http://www.svlada.com/jwt-token-authentication-with-spring-boot/

场景介绍

软件安全是一件很负责的问题,由于微服务系统中每个服务都要处理安全问题,所以在微服务场景下会更加复杂,一般我们会四种面向微服务系统的身份验证方案。
在传统的单体架构中,单个服务保存所有的用户数据,可以校验用户,并在认证成功后创建HTTP会话。在微服务架构中,用户是在和服务集合交互,每一个用户都有可能需要知道请求的用户是谁。一种简单的方案是在微服务中,采用与单体系统中相同的模式,但问题是如何让所有的服务访问用户的数据
解决这个问题大致2个思路:(1)使用共享数据库时,更新数据库表会成为一个难题,因为所有的服务必须同时升级以便能够对接修改后的表解构;(2)将相同的数据分发给所有的服务时,当某个用户已经被认证,如何让每个服务都知晓这个状态是个问题。

方案1:单点登录(SSO)方案, 采用单点登录方案,意味着每个面向用户的服务都必须与认证服务交互,这会产生大量非常琐碎的网络流量,同时这个防范实现起来也相当的复杂。在其他方面,选择SSO方案安全性会很好,用户登录状态是不透明的,可防止攻击者从状态中推断任何有用的信息。

方案2:分布式会话方案,原理主要是将关于用户信息存储在共享内存中,并通常由用户会话作为key来实现简单的分布式哈希映射。当用户访问微服务时,用户数据可以从共享存储中获取。该方案的另外一个优点就是用户登录状态不是透明的。当使用分布式数据库时,它也是一个高度可用且可可扩展的解决方案。这种方案的优点是在于共享存储需要一定保护机制,因此需要通过安全链接来访问,这时解决方案的实现就通常具有相当高的负责性了。

方案3:token客户端令牌方案,此令牌在客户端生成,由身份验证服务进行签名,并且必须包含足够的信息,以便可以在所有微服务中建立用户身份。令牌会附加到每一个请求上,为微服务提供身份验证。这种解决方案安全性相对较好,但身份验证注销是一个大大的问题,缓解这种情况的方法可以使用短期令牌access_token 和频繁检查认证服务器等。对于客户端令牌的编码方案,可以使用 JSON Web Tokens( JWT), 它足够简单且支持程度也比较好。

方案4:客户端令牌与API网关结合,这个方案意味着所有的请求都通过网关,从而有效地隐藏了微服务。在请求时,网关将原始用户令牌转换为内部会话(session)ID令牌。在这种情况下,注销就不在是个大大的问题, 因为网关在注销时可以撤销用户的令牌。这种方案虽然支持程度比较好,但是实现起来还是可能相当的复杂。

个人推荐方案:客户端令牌(JWT) + API网关结合的方案,因为这个方案通常使用起来比较容易,且性能也不错。SSO方案虽然能满足需求,但尽量避免使用,若分布式会话方案所需要的相关技术已经应用在你的场景中,那么这个方案也是比较有趣的。在考虑方案的时候,应该考虑注销的重要性。

api网关,参考这篇,
http://geek.csdn.net/news/detail/104715
http://www.dockerinfo.net/773.html

JWT介绍

这篇文章将会知指导你如何用spring boot实现JWT的授权。
文章将会涉及到下面2个方面:
1. Ajax 授权
2. JWT token 授权

前提

请在你细读本篇文章的时候,先看看Github 上的简单项目:https://github.com/svlada/springboot-security-jwt
这个项目是使用H2 内存数据库来存储简单的用户信息。为了让事情变的更加简单,我已经配置了spring boot在自动加载Application自动启动的时候,已经创建了数据设备和配置了spring boot的相关配置(/ jwt-demo / src / main /resources/ data.sql)。

先预览一下下面的项目结构:
+—main
| +—java
| | —com
| | —svlada
| | +—common
| | +—entity
| | +—profile
| | | —endpoint
| | +—security
| | | +—auth
| | | | +—ajax
| | | | —jwt
| | | | +—extractor
| | | | —verifier
| | | +—config
| | | +—endpoint
| | | +—exceptions
| | | —model
| | | —token
| | —user
| | +—repository
| | —service
| —resources
| +—static
| —templates

Ajax 授权

当我们在谈论Ajax授权的时候,我们通常会联想到的是用户是通过JSON 的方式提供凭证,并以XMLHttpRequest 的方式发送的场景。
在本教程的第一部分,Ajax实现身份验证是遵循Spring Security 框架的标准模式。
下面列表的东西,将是要我们去实现的:
1. AjaxLoginProcessingFilter
2. AjaxAuthenticationProvider
3. AjaxAwareAuthenticationSuccessHandler
4. AjaxAwareAuthenticationFailureHandler
5. RestAuthenticationEntryPoint
6. WebSecurityConfig

在我们实现这些细节的时候,让我们细看一下,下面的 request/response 的授权流程。

Ajax 授权请求的例子

身份验证API允许用户传入凭据,来获得身份验证令牌token。
在我们的例子中,客户端启动验证过程通过调用身份验证API(/API/auth/login)。
我们可以写这样的Http 请求:

POST /api/auth/login HTTP/1.1  
Host: localhost:9966  
X-Requested-With: XMLHttpRequest  
Content-Type: application/json  
Cache-Control: no-cache

{
    "username": "[email protected]",
    "password": "test1234"
}

终端可以用CURL:

curl -X POST -H "X-Requested-With: XMLHttpRequest" -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{  
    "username": "[email protected]",
    "password": "test1234"
}' "http://localhost:9966/api/auth/login"

Ajax 授权相应的例子

如果客户端请求的凭证被通过,授权API将会返回Http响应包括下面的一些细节:

1. Http 状态 "200 OK"
2. 带有 JWT的access_toke 和 refresh_token 都会在 response body中被包含了。

JWT Refresh token

用来获取新的 access_token, 刷新token 可以用这样的API来处理:/api/auth/token.(刷新可以用来防止access_token 的过期)

获取的HTTP 响应格式:

{
  "token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJzdmxhZGFAZ21haWwuY29tIiwic2NvcGVzIjpbIlJPTEVfQURNSU4iLCJST0xFX1BSRU1JVU1fTUVNQkVSIl0sImlzcyI6Imh0dHA6Ly9zdmxhZGEuY29tIiwiaWF0IjoxNDcyMDMzMzA4LCJleHAiOjE0NzIwMzQyMDh9.41rxtplFRw55ffqcw1Fhy2pnxggssdWUU8CDOherC0Kw4sgt3-rw_mPSWSgQgsR0NLndFcMPh7LSQt5mkYqROQ",

  "refreshToken": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJzdmxhZGFAZ21haWwuY29tIiwic2NvcGVzIjpbIlJPTEVfUkVGUkVTSF9UT0tFTiJdLCJpc3MiOiJodHRwOi8vc3ZsYWRhLmNvbSIsImp0aSI6IjkwYWZlNzhjLTFkMmUtNDg2OS1hNzdlLTFkNzU0YjYwZTBjZSIsImlhdCI6MTQ3MjAzMzMwOCwiZXhwIjoxNDcyMDM2OTA4fQ.SEEG60YRznBB2O7Gn_5X6YbRmyB3ml4hnpSOxqkwQUFtqA6MZo7_n2Am2QhTJBJA1Ygv74F2IxiLv0urxGLQjg"
}

JWT Access Token

JWT访问令牌可用于身份验证和授权:
1. 身份验证是由验证JWT访问令牌签名。如果签名是有效的,访问API请求的资源是理所当然。
2. 授权是通过查找特权JWT scope属性的值来判断。(译者:scope,一般会是版本号或平台,安卓,ios,wap等或不同的系统的id, 具体看自家的场景和需求).

解码JWT token有三个部分:Header(请求头), Claims(要求) and Signature(签名)

Header

{
    "alg": "HS512"
}

Claims(要求):

{
  "sub": "[email protected]",
  "scopes": [
    "ROLE_ADMIN",
    "ROLE_PREMIUM_MEMBER"
  ],
  "iss": "http://svlada.com",
  "iat": 1472033308,
  "exp": 1472034208
}

签名base64 encoded)

41rxtplFRw55ffqcw1Fhy2pnxggssdWUU8CDOherC0Kw4sgt3-rw_mPSWSgQgsR0NLndFcMPh7LSQt5mkYqROQ  

JWT Refresh Token

Refresh token 是长寿令牌用于请求新的访问令牌。Refresh token过期时间是超过access_token的过期时间。
在本次教程中,我们将使用 jti 声称来维持黑名单,或撤销令牌列表。JWT ID(jti) 声称被 RFC7519 定义了,目的是为了唯一地标识单个刷新令牌。

解码刷新令牌有三个部分: Header(请求头), Claims(要求), Signature(签名) 如下所示:
Header:

{
  "alg": "HS512"
}

Claims:

{
  "sub": "[email protected]",
  "scopes": [
    "ROLE_REFRESH_TOKEN"
  ],
  "iss": "http://svlada.com",
  "jti": "90afe78c-1d2e-4869-a77e-1d754b60e0ce",
  "iat": 1472033308,
  "exp": 1472036908
}

Signature (base64 encoded)

SEEG60YRznBB2O7Gn_5X6YbRmyB3ml4hnpSOxqkwQUFtqA6MZo7_n2Am2QhTJBJA1Ygv74F2IxiLv0urxGLQjg  

AjaxLoginProcessingFilter( Ajax 登录处理过滤器)

首先,需要继承AbstractAuthenticationProcessingFilter, 目的是为了提供一般常用的Ajax 身份验证请求。反序列化JSON和基本验证的主要任务都是在的。AjaxLoginProcessingFilter#attemptAuthentication这个方法里完成的。在成功验证JSON的主要检验逻辑是委托给AjaxAuthenticationProvider类实现。

在一个成功校验中, AjaxLoginProcessingFilter#successfulAuthentication 方法会被调用。
在一个失败的检验中,AjaxLoginProcessingFilter#unsuccessfulAuthentication 方法被调用。

public class AjaxLoginProcessingFilter
  • 7
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值