概述
OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 1.0即完全废止了OAuth1.0。
OAuth在"客户端"与"服务提供商"之间,设置了一个授权层(authorization layer)。"客户端"不能直接登录"服务提供商",只能登录授权层,以此将用户与客户端区分开来。"客户端"登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。"客户端"登录授权层以后,"服务提供商"根据令牌的权限范围和有效期,向"客户端"开放用户储存的资料。
客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0定义了四种授权方式。
- 授权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
具体关于OAuth2的介绍,可以参考阮一峰的博客 《理解OAuth 2.0》。
现在我已经基于Spring OAuth2 已经搭建好了这样一个授权层,本文称之为认证中心。那么写本文档的目的,就是指导大家如何将第三方的系统接入这个的认证中心,融入统一的单点登录环境。我这里简单画了一个图。
如图所示,新的第三方平台(如:A平台和B平台),登录和注销的操作都跳转到“OAuth2认证中心”的域名下进行,由于认证通过后会生成Cookie,所以A平台登录后,同一单点登录的B平台、C平台都无需再次登录。接入单点登录的第三方平台都会被授予客户端的clientId和clientSecret,通过“授权码模式”登录通过后获取授权码,然后第三方平台就可以拿授权码通过API来获取令牌(access token)。
客户端接入
我们通过一个案例,讲解如何将第三方平台接入OAuth2的单点登录,这里以纸蜂平台为例。下图是纸蜂平台接入OAuth2单点登录的时序图。
1、注册客户端
1.1、clientId 和 clientSecret
由于我们选用的是授权码模式,当新的客户端接入单点登录时,都需要在认证中心注册对应的 clientId和clientSecret。而客户端只需要保存 clientId和对二者加密后的 Authorization值,如果纸蜂的clientId为client1
1.2、token 的过期时间
我们OAuth2签发的令牌是通过Jwt来生成token的,每个客户端可以自定义jwt token的过期时间,然后注册在认证中心。
2、前端路由守卫控制
纸蜂的前端是由路由守卫控制,路由守卫的过滤规则是判断LocalStorage中是否有access_token,如果有access_token,并且未过期则页面路由放行。
2.1、获取token
当前端路由守卫在LocalStorage中获取不到access_token时,则跳转至认证中心的授权登录地址:{认证中心域名}/uaa/oauth/authorize?response_type=code&client_id=client1&redirect_uri=http://第三方/login/loading
- {认证中心域名}/uaa/oauth/authorize:授权地址
- response_type=code:授权码模式
- client_id=client1:客户端的clientId
- redirect_uri=http://第三方/login/loading ,登录成功后的重定向地址。http://第三方/login/loading 是纸蜂平台自定义开发的页面,用于拿到授权码后获取 access_token
该授权登录页面会引导url调整到登录页 - {认证中心域名}/uaa/login ,在登录成功后会跳转会重定向的地址,并在url param中带上授权码,如:http://第三方/login/loading?c... 。该页面用于通过授权码获取 access_token和refresh_token,并存入LocalStorage。
通过授权码获取token的接口(POST)调用方式为:
Header:
Authorization:对clientId和clientSecret 通过Basic Auth 加密后的值
Content-Type:application/x-www-form-urlencoded
Body:
grant_type:authorization_code
code:授权码
redirect_uri:http://第三方/login/loading
返回结果示例:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJrZXJyeSIsInNjb3BlIjpbInVzZXJfaW5mbyJdLCJuYW1lIjoi5ZC05pmo55GeIiwiZXhwIjoxNTYzOTg1MjU2LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiYzg4YjM5ZjEtYTE2Yy00OGI3LTg4N2MtMjM3MTc5NDI3MjRhIiwiY2xpZW50X2lkIjoicGFwZXJiZWVDbGllbnQifQ.Gmt8xFMqbRcx96rmlhg8AZhrMxDRGorVjK8wV_AEox4",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJrZXJyeSIsInNjb3BlIjpbInVzZXJfaW5mbyJdLCJhdGkiOiJjODhiMzlmMS1hMTZjLTQ4YjctODg3Yy0yMzcxNzk0MjcyNGEiLCJuYW1lIjoi5ZC05pmo55GeIiwiZXhwIjoxNTY2NTc2NTYxLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiMjNjYzIyY2EtNmI2OC00ZmJkLTg1OGItZjcxNzVkNzAxOTgyIiwiY2xpZW50X2lkIjoicGFwZXJiZWVDbGllbnQifQ.DY_GvNRS7mJP8FrPorffoqvTokU3udnlYnnFcWv0x4g",
"expires_in": 599,
"jti": "c88b39f1-a16c-48b7-887c-23717942724a"
}
2.2、刷新过期的token
路由守卫虽然在LocalStorage中检测到access_token,但当检测到该jwt 的token已过期,则需要拿 refresh_token通过调用接口 - {认证中心域名}/uaa/oauth/token ,来获取最新的access_token和refresh_token,并在LocalStorage中替换。
刷新token的接口(POST)调用方式为:
Header:
Authorization:clientId和clientSecret加密后的值
Content-Type:application/x-www-form-urlencoded
Body:
grant_type:refresh_token
refresh_token:refresh_token的值
返回结果示例:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJrZXJyeSIsInNjb3BlIjpbInVzZXJfaW5mbyJdLCJuYW1lIjoi5ZC05pmo55GeIiwiZXhwIjoxNTYzOTg1MjU2LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiYzg4YjM5ZjEtYTE2Yy00OGI3LTg4N2MtMjM3MTc5NDI3MjRhIiwiY2xpZW50X2lkIjoicGFwZXJiZWVDbGllbnQifQ.Gmt8xFMqbRcx96rmlhg8AZhrMxDRGorVjK8wV_AEox4",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJrZXJyeSIsInNjb3BlIjpbInVzZXJfaW5mbyJdLCJhdGkiOiJjODhiMzlmMS1hMTZjLTQ4YjctODg3Yy0yMzcxNzk0MjcyNGEiLCJuYW1lIjoi5ZC05pmo55GeIiwiZXhwIjoxNTY2NTc2NTYxLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiMjNjYzIyY2EtNmI2OC00ZmJkLTg1OGItZjcxNzVkNzAxOTgyIiwiY2xpZW50X2lkIjoicGFwZXJiZWVDbGllbnQifQ.DY_GvNRS7mJP8FrPorffoqvTokU3udnlYnnFcWv0x4g",
"expires_in": 599,
"jti": "c88b39f1-a16c-48b7-887c-23717942724a"
}
2.3、校验jwt token
access_token是第三方系统的前端来获取的,所以对于前端来说,本身就已经保证了token的合法性,用户的账号信息可以直接通过对token的base64解码来获取。
但是前端在调用后端接口时也是要携带token,为了防止伪造,第三方的后端服务就需要拿这个token去认证中心的服务器上去校验token的合法性,并获取对应token的用户信息。
临时提供的接口地址 - {认证中心域名}/uaa/user/parseJwt (GET):
Header:
Authorization:bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJrZXJyeSIsInNjb3BlIjpbInVzZXJfaW5mbyJdLCJuYW1lIjoi5ZC05pmo55GeIiwiZXhwIjoxNTY0MTk3Mjk1LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiYjg5ZDRlYjgtOTE2ZC00NTM1LTgyMDQtOTEyMjdlNjJhNTgyIiwiY2xpZW50X2lkIjoiYXBpRGVzaWduQ2xpZW50In0.Lgw9LOzK0b2i8w2VrU0NumOKvNBFXoDKIwRNi2UB6vs
# bearer access_token的值
返回结果示例:
{
"user_name": "kerry",
"scope": [
"user_info"
],
"name": "吴晨瑞",
"exp": 1564197295,
"authorities": [
"ROLE_USER"
],
"jti": "b89d4eb8-916d-4535-8204-91227e62a582",
"client_id": "client1"
}
3、注销
授权登录都是跳转到认证中心域名下的登录页面,在登录成功后,会在该域名下生成认证的Cookie。
优点:在同一个浏览器上,只要有一个接入单点登录的第三方系统登录后,其他的系统无需登录就能获取到授权码。
缺点:如果想重新登录,就需要清除当前浏览器中,认证中心域下的Cookie。
认证中心已经提供了一个注销的地址 - {认证中心域名}/uaa/logout ,当的第三方系统页面上点击注销,只需要跳转到该地址,会自动清除认证中心域名下的Cookie,并重定向回第三方系统的当前页面。
4、客户端后台接口
第三方系统的前端在调用后端接口时,会携带 jwt 的 access_token,由于jwt的密钥只存在认证中心的服务器上,第三方系统的后台是没有办法去验证并解析获取到的access_token的。
所以当客户端后台需要获取当前access_token用户的信息,只能通过携带access_token调用认证中心开放的指定接口,并由认证中心来验证当前access_token是否合法授权。