OAuth2 + JWT 授权认证服务
OAuth2 + JWT 授权认证服务
参考阅读
- Spring Security整合JSON Web Token(JWT)提升REST安全性
- Spring Cloud下微服务权限方案
- 使用JWT和Spring Security保护REST API
- 理解OAuth 2.0
JWT 简单介绍
JWT长什么样
JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDg0OTA1NjMsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJqdGkiOiI3YjNiNTBlMC1hMjhjLTQ4NTQtYWNhMi0zMmQwZTIyZTY1ZmUiLCJjbGllbnRfaWQiOiJhYmMiLCJzY29wZSI6WyJhbGwiXX0.hPhvl1XbBMjoPER7BggWD1UO83o8SNtgg15rlUBndDo |
JWT的构成
第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).
header
jwt的头部承载两部分信息
- 声明类型,这里是jwt
- 声明加密的算法 通常直接使用 HMAC SHA256
完整的头部就像下面这样的JSON:
1 | { |
然后将头部进行base64编码,构成了第一部分
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 |
playload
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
- 标准中注册的声明
- 公共的声明
- 私有的声明
标准中注册的声明 (建议但不强制使用)
- iss: jwt签发者
- sub: jwt所面向的用户
- aud: 接收jwt的一方
- exp: jwt的过期时间,这个过期时间必须要大于签发时间
- nbf: 定义在什么时间之前,该jwt都是不可用的.
- iat: jwt的签发时间
- jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
公共的声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
私有的声明
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64编码是可逆的,意味着该部分信息可以归类为明文信息。
定义一个payload:
1 | { |
然后将其进行base64编码,得到Jwt的第二部分
1 | eyJleHAiOjE1MDg0OTA1NjMsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJqdGkiOiI3YjNiNTBlMC1hMjhjLTQ4NTQtYWNhMi0zMmQwZTIyZTY1ZmUiLCJjbGllbnRfaWQiOiJhYmMiLCJzY29wZSI6WyJhbGwiXX0 |
signature
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
- header (base64后的)
- payload (base64后的)
- secret
这个部分需要base64编码后的header和base64编码后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
1 | // javascript |
将这三部分用.连接成一个完整的字符串,构成了最终的jwt
注意
secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
OAuth2 介绍及使用
名词定义
- Third-party application:第三方应用程序,本文中又称”客户端”(client),即上一节例子中的”云冲印”。
- HTTP service:HTTP服务提供商,本文中简称”服务提供商”,即上一节例子中的Google。
- Resource Owner:资源所有者,本文中又称”用户”(user)。
- User Agent:用户代理,本文中就是指浏览器。
- Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。
- Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。
OAuth的思路
OAuth在”客户端”与”服务提供商”之间,设置了一个授权层(authorization layer)。”客户端”不能直接登录”服务提供商”,只能登录授权层,以此将用户与客户端区分开来。”客户端”登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。
“客户端”登录授权层以后,”服务提供商”根据令牌的权限范围和有效期,向”客户端”开放用户储存的资料。
运行流程
- A 用户打开客户端以后,客户端要求用户给予授权。
- B 用户同意给予客户端授权。
- C 客户端使用上一步获得的授权,向认证服务器申请令牌。
- D 认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
- E 客户端使用令牌,向资源服务器申请获取资源。
- F 资源服务器确认令牌无误,同意向客户端开放资源。
客户端的授权模式
客户端必须得到用户的授权(authorization grant), 才能获得令牌(access token)
OAuth 2.0定义了四种授权方式:
- 授权码模式 (authorization code)
- 简化模式 (implicit)
- 密码模式 (resource owner password credentials)
- 客户端模式 (client credentials)
验证token接口 /oauth/check_token
参数 token: 访问令牌
1 | http://localhost:8080/oauth/check_token?token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDg0NzcyNjgsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJqdGkiOiJmZTk1MTNiZS1kZjEwLTQ2MDEtYWM0Yy0zOTc4Njc2NzQ1M2IiLCJjbGllbnRfaWQiOiJhYmMiLCJzY29wZSI6WyJhcHAiXX0.AOWIDfMfqzvMhV1T6eYDVpnvU03vST8XpNY35gNrNnyy_DQcLtFB0n_KIYieG4CYaVGlKnMGGXL7L592Njx6xy3bCdmSHw5_4MZZxxWN9_hj96ZTvi4H5Yc3vVzlXNHIvTTJxUsst15EbmN5s5ZBlZptv_3T70bG6x1iq6sbmMQ8zjHRgSKsZM1hvZ5pAO6BwYmPgIKFDPFtxB36I0LegLypUmzjgUFU5dAfgY-w00yyOf9aNooisM22CmbR0QXg3NAlzeufe_Jjf5W0jBTRzdhnVFreRUeOMMwlyKUb_rXUf53Yx0AXhcEhZYtYaYQpDyGxazcoO_VWSgM89S3nQuf23r20gp-jxgElNvRUnPhbO_jIuOcn2FQlvm-L37FfzlO6cK7BGqegE4ItVERfchznWFPq4Jm98IEKV5mQgAcutrE393uIL2137cNzfdg-DZ5HjFoPeEpiJ_ZL7IydxXgOsVTgYsLZ8Ccl0mO51kOCRC4tRBQxXVS9vtRQtp0TVjGTpdXK7SfLyohK1Ga0TXOA76HZv_nAoKy1BRg-i2bejV900g7-nkQGgthPdZbFk4rylXZe6t8grxCIDgtdYFGNLXiMjmnUYU9MJ35AFc1yGhqjwMX8lzdGsTNPLwNeKq9rt82rZxZuuKpiK3ph4LZgnQX5th6XiNBZDfnRz1M |
响应内容 JSON
1 | { |
刷新token接口 /oauth/token
1 | curl -d 'grant_type=refresh_token&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI4MzRmY2FmOS1hOGVhLTQwYWItYjkxMi1jNjU3NDFmOWJkYmMiLCJleHAiOjE1MDg0ODY2MDMsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iLCJST0xFX1VTRVIiXSwianRpIjoiNWJkYmEzNTctZTRjOS00MzdjLWE2Y2EtODdhM2FiNGVlZTUzIiwiY2xpZW50X2lkIjoiYWJjIn0.JEttthKb3Laj6iNeMZFvnj_2CHLW5WjzQF0RA5RMyGs&client_id=abc&client_secret=aaaabbbb' "http://localhost:8080/oauth/token" |
响应内容 JSON
1 | { |
授权码模式 (authorization code)
授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与”服务提供商”的认证服务器进行互动。
- A 用户访问客户端,后者将前者导向认证服务器。
- B 用户选择是否给予客户端授权。
- C 假设用户给予授权,认证服务器将用户导向客户端事先指定的”重定向URI”(redirection URI),同时附上一个授权码。
- D 客户端收到授权码,附上早先的”重定向URI”,向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
- E 认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
获取code接口 /oauth/authorize
客户端申请认证的URI,包含以下参数:
- response_type:表示授权类型,必选项,此处的值固定为”code”
- client_id:表示客户端的ID,必选项
- redirect_uri:表示重定向URI,可选项
- scope:表示申请的权限范围,可选项
- state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
服务器回应客户端的URI,包含以下参数:
- code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。
- state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
1 | GET http://localhost:8080/oauth/authorize?client_id=abc&response_type=code&redirect_uri=http://localhost:8080/resources/roles |
获取token接口 /oauth/token
D 步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:
_ grant_type:表示使用的授权模式,必选项,此处的值固定为”authorization_code”。
_ code:表示上一步获得的授权码,必选项。
_ redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。
_ client_id:表示客户端ID,必选项。
1 | curl -d 'grant_type=authorization_code&redirect_uri=http://localhost:8080/resources/roles&client_id=abc&client_secret=aaaabbbb&code=zmpbg2' "http://localhost:8080/oauth/token" |
Response Header content-type: application/json;charset=UTF-8
1 | { |
简化模式 (implicit)
简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了”授权码”这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。
- A 客户端将用户导向认证服务器。
- B 用户决定是否给于客户端授权。
- C 假设用户给予授权,认证服务器将用户导向客户端指定的”重定向URI”,并在URI的Hash部分包含了访问令牌。
- D 浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。
- E 资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。
- F 浏览器执行上一步获得的脚本,提取出令牌。
- G 浏览器将令牌发给客户端。
获取token接口 /oauth/token
A步骤中,客户端发出的HTTP请求,包含以下参数:
- response_type:表示授权类型,此处的值固定为”token”,必选项。
- client_id:表示客户端的ID,必选项。
- redirect_uri:表示重定向的URI,可选项。
- scope:表示权限范围,可选项。
- state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
C步骤中,认证服务器回应客户端的URI,包含以下参数:
- access_token:表示访问令牌,必选项。
- token_type:表示令牌类型,该值大小写不敏感,必选项。
- expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
- scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。
- state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
1 | http://localhost:8080/oauth/authorize?client_id=abc&response_type=token&redirect_uri=http://localhost:8080/resources/roles |
密码模式 (resource owner password credentials)
密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向”服务商提供商”索要授权。
在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。
- A 用户向客户端提供用户名和密码。
- B 客户端将用户名和密码发给认证服务器,向后者请求令牌。
- C 认证服务器确认无误后,向客户端提供访问令牌。
获取token接口 /oauth/token
B步骤中,客户端发出的HTTP请求,包含以下参数:
- grant_type:表示授权类型,此处的值固定为”password”,必选项。
- username:表示用户名,必选项。
- password:表示用户的密码,必选项。
- scope:表示权限范围,可选项。
1 | curl -d 'grant_type=password&username=admin&password=admin&client_id=abc&client_secret=aaaabbbb' "http://localhost:8080/oauth/token" |
响应内容 JSON
1 | { |
客户端模式 (client credentials)
客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向”服务提供商”进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求”服务提供商”提供服务,其实不存在授权问题。
- A 客户端向认证服务器进行身份认证,并要求一个访问令牌。
- B 认证服务器确认无误后,向客户端提供访问令牌。
获取token接口 /oauth/token
A步骤中,客户端发出的HTTP请求,包含以下参数:
- granttype:表示授权类型,此处的值固定为”clientcredentials”,必选项。
- scope:表示权限范围,可选项。
1 | curl -d 'grant_type=client_credentials&client_id=abc&client_secret=aaaabbbb' "http://localhost:8080/oauth/token" |
响应内容 JSON
1 | { |