Grails Oauth2 插件适配非标准 SSO 接口
source link: https://blog.dteam.top/posts/2020-11/grails-oauth.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
最近遇到一个项目,需要跟某大厂的单点登录接口对接。然而该厂使用的并非行业通用的 OAuth2 接口,经过一番魔改之后,成功搞定了单点登录的对接。
该厂单点登录的流程
该厂单点登录的流程大致如下:
-
请求登录页面:
{ssoUrl}?returnUrl=http://localhost:8080/
-
登录成功会给
returnUrl
带回一个token=xxYYzz
的参数跳转回来 -
使用这个
token
参数配合username:password
(经过 base64 编码)认证请求用户信息接口获取用户信息:POST /{ssoUrl}/getUser?token=xxYYzz Authorization Basic {secret}
虽然不是标准的单点登录流程(如 Oauth2 协议跳转回来的参数是code
而不是token
,需要用code
换取access token
,然后用access token
登录 SSO 系统获取用户信息),但大体流程上还是大同小异的。因此可以通过稍加改造 Grails Oauth2 插件来对接到我们已经支持的 OAuth2 登录。
实现非标准 Oauth2 适配
Grails 的grails-shiro-oauth插件使用上还是比较简单的,插件已经实现了一个OauthController
,并且实现了callback
和authenticate
两个 action,默认注册了/oauth/callback/$provider
这个 URL 帮助自动处理 OAuth2 的回调,我们只需要实现目标 SSO 服务端的 API 以及成功的响应onSuccess
action 就可以了。
大致的主要部分逻辑如下:
Api 实现部分:
class MyApi extends DefaultApi20 {
private static final ConfigObject configObject = Holders.grailsApplication.config
private static final String ssoUrl = configObject.oauth.providers.myApi.ssoUrl
private static final String resourceUrl = configObject.oauth.providers.myApi.resourceUrl
@Override
Verb getAccessTokenVerb() {
Verb.POST
}
@Override
AccessTokenExtractor getAccessTokenExtractor() {
new JsonTokenExtractor()
}
@Override
String getAccessTokenEndpoint() {
resourceUrl
}
@Override
String getAuthorizationUrl(OAuthConfig config) {
Preconditions.checkValidUrl(config.callback, "Must provide a valid url as callback. RootCloud does not support OOB")
String.format(ssoUrl, OAuthEncoder.encode(config.callback))
}
@Override
OAuthService createService(OAuthConfig config) {
new MyApiOAuthService(this, config);
}
}
OAuthService 实现部分:
class MyApiOAuthService extends OAuth20ServiceImpl {
private final DefaultApi20 api
private final OAuthConfig config
MyApiOAuthService(DefaultApi20 api, OAuthConfig config) {
super(api, config)
this.api = api
this.config = config
}
@Override
Token getAccessToken(Token requestToken, Verifier verifier) {
String url = api.accessTokenEndpoint + "?token=${requestToken.token}"
OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), url)
request.addHeader('Authorization', "Basic ${config.apiSecret}")
Response response = request.send()
new Token(response.body, config.apiSecret)
}
}
这里有一些 hack 的部分,getAccessToken
按照 OAuth2 的流程应该是使用code
换取access token
的过程,但是被我们调整成了直接用token
请求用户信息,将响应封装进入了Token
类,交由onSuccess
action 进行解析。
由于 grails shiro oauth 插件只认code
参数,不认非标准的token
参数,因此我们需要在 filter 中做一下参数转换,以便对接插件。
class OauthCallbackFilters {
def filters = {
fillInCodeParam(controller: 'oauth', action: 'callback') {
before = {
if (params.token && !params.code) {
params.put('code', params.token)
}
}
}
}
}
最后在 onSuccess action 的逻辑中变通一下,直接按照 JSON 格式解析Token
就完事了:
class ShiroOAuthController {
GrailsApplication grailsApplication
OauthService oauthService
def onSuccess() {
if (!params.provider) {
renderError 400, "The Shiro OAuth callback URL must include the 'provider' URL parameter."
return
}
Map providerConfig = grailsApplication.config.oauth.providers[params.provider]
String sessionKey = oauthService.findSessionKeyForAccessToken(params.provider)
Token accessToken = session[sessionKey]
if (!accessToken) {
renderError 500, "No OAuth token in the session for provider '${params.provider}'!"
return
}
// Create the relevant authentication token and attempt to log in.
def oauthUserInfo
if (params.provider == 'myapi') {
oauthUserInfo = new JSONObject(accessToken.token)
} else {
Response response = oauthService."get${params.provider}Resource"(accessToken, providerConfig.resourceUrl)
log.debug("OAuth resource response for ${params.provider}: ${response.body}")
if (response.body =~ /\{.*}/) {
oauthUserInfo = JSON.parse(response.body)
} else if (response.body =~ /<.*>/) {
oauthUserInfo = XML.parse(response.body)
} else {
oauthUserInfo = request.getParameterMap()
}
}
//...
}
}
这样通过小幅度变通就可以适配非标准的 OAuth2 流程了。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK