OAuth(开放受权)是一个开放标准,容许用户受权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不须要将用户名和密码提供给第三方移动应用或分享他们数据的全部内容,OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 1.0即彻底废止了OAuth1.0。html
OAuth在"客户端"与"服务提供商"之间,设置了一个受权层(authorization layer)。"客户端"不能直接登陆"服务提供商",只能登陆受权层,以此将用户与客户端区分开来。"客户端"登陆受权层所用的令牌(token),与用户的密码不一样。用户能够在登陆的时候,指定受权层令牌的权限范围和有效期。"客户端"登陆受权层之后,"服务提供商"根据令牌的权限范围和有效期,向"客户端"开放用户储存的资料。前端
客户端必须获得用户的受权(authorization grant),才能得到令牌(access token)。OAuth 2.0定义了四种受权方式。json
具体关于OAuth2的介绍,能够参考阮一峰的博客 《理解OAuth 2.0》。后端
如今我已经基于Spring OAuth2 已经搭建好了这样一个受权层,本文称之为认证中心。那么写本文档的目的,就是指导你们如何将第三方的系统接入这个的认证中心,融入统一的单点登陆环境。我这里简单画了一个图。浏览器
如图所示,新的第三方平台(如:A平台和B平台),登陆和注销的操做都跳转到“OAuth2认证中心”的域名下进行,因为认证经过后会生成Cookie,因此A平台登陆后,同一单点登陆的B平台、C平台都无需再次登陆。接入单点登陆的第三方平台都会被授予客户端的clientId和clientSecret,经过“受权码模式”登陆经过后获取受权码,而后第三方平台就能够拿受权码经过API来获取令牌(access token)。服务器
咱们经过一个案例,讲解如何将第三方平台接入OAuth2的单点登陆,这里以纸蜂平台为例。下图是纸蜂平台接入OAuth2单点登陆的时序图。app
因为咱们选用的是受权码模式,当新的客户端接入单点登陆时,都须要在认证中心注册对应的 clientId和clientSecret。而客户端只须要保存 clientId和对两者加密后的 Authorization值,若是纸蜂的clientId为client1加密
咱们OAuth2签发的令牌是经过Jwt来生成token的,每一个客户端能够自定义jwt token的过时时间,而后注册在认证中心。url
纸蜂的前端是由路由守卫控制,路由守卫的过滤规则是判断LocalStorage中是否有access_token,若是有access_token,而且未过时则页面路由放行。spa
当前端路由守卫在LocalStorage中获取不到access_token时,则跳转至认证中心的受权登陆地址:{认证中心域名}/uaa/oauth/authorize?response_type=code&client_id=client1&redirect_uri=http://第三方/login/loading
该受权登陆页面会引导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" }
路由守卫虽然在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" }
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" }
受权登陆都是跳转到认证中心域名下的登陆页面,在登陆成功后,会在该域名下生成认证的Cookie。
优势:在同一个浏览器上,只要有一个接入单点登陆的第三方系统登陆后,其余的系统无需登陆就能获取到受权码。
缺点:若是想从新登陆,就须要清除当前浏览器中,认证中心域下的Cookie。
认证中心已经提供了一个注销的地址 - {认证中心域名}/uaa/logout ,当的第三方系统页面上点击注销,只须要跳转到该地址,会自动清除认证中心域名下的Cookie,并重定向回第三方系统的当前页面。
第三方系统的前端在调用后端接口时,会携带 jwt 的 access_token,因为jwt的密钥只存在认证中心的服务器上,第三方系统的后台是没有办法去验证并解析获取到的access_token的。
因此当客户端后台须要获取当前access_token用户的信息,只能经过携带access_token调用认证中心开放的指定接口,并由认证中心来验证当前access_token是否合法受权。