我一直在尝试实现单点登录(SSO)。我有不同的前端应用程序模块,它们在不同的域上运行,它们都使用单个 API 服务器。
- SSO 服务器https://sso.app.com
- API 服务器https://api.app.com
- 前端模块 1 https://module-1.app.com
- 前端模块 2 https://module-2.app.com
认证流程
身份验证流程是前端模块检查本地存储中的令牌。如果找不到令牌,它将用户重定向到 API 服务器端点,比如说https://api.app.com/oauth/connect. API 服务器具有 SSO 服务器的 clientId 和 Secrets。API 服务器在 cookie 中设置前端模块的 url(以便我可以将用户重定向回启动器前端模块),然后将请求重定向到 SSO 服务器,在该服务器上向用户显示登录屏幕。用户在那里输入凭据,SSO 服务器验证凭据,创建会话。验证凭证后,SSO 服务器使用用户配置文件和 access_token 调用 API 服务器端点。API 服务器在会话中获取配置文件并查询并签署自己的令牌,然后通过查询参数将其发送到前端模块。在前端(React APP)上有一条专门用于此的路线。在该前端路由中,我从 queryParams 中提取令牌并设置在本地存储中。用户在应用程序中。同样,当用户加载 FrontendModule-2 时,会发生相同的流程,但这次是因为在 FrontendModule-1 流程运行时 SSO 服务器正在创建会话。它从不要求登录凭据并将用户登录到系统。
失败场景:
场景是,假设有用户 JHON 尚未登录并且没有会话。Jhon 在浏览器中点击了“前端模块 1”URL。前端模块检查 localStorage 的令牌,它没有找到它,然后前端模块将用户重定向到 API 服务器路由。API 服务器具有将请求重定向到 SSO 服务器的 clientSecret 和 clientId。那里的用户将看到登录屏幕。
Jhon 看到登录屏幕并保持原样。现在 Jhon 在同一浏览器中打开另一个选项卡并输入“前端模块 2”的 URL。与上面相同的流程发生,Jhon 登陆登录屏幕。Jhon 离开该屏幕并返回到第一个选项卡,在该选项卡中加载了 Frontend Module 1 会话屏幕。他输入信用并点击登录按钮。它给了我会话状态已更改的错误。这个错误实际上是有道理的,因为会话是共享的。
期待
我如何在没有错误的情况下实现这一点。我想将用户重定向到发起请求的同一个前端模块。
我正在使用的工具
示例实现(API 服务器)
require('dotenv').config();
var express = require('express')
, session = require('express-session')
, morgan = require('morgan')
var Grant = require('grant-express')
, port = process.env.PORT || 3001
, oauthConsumer= process.env.OAUTH_CONSUMER || `http://localhost`
, oauthProvider = process.env.OAUTH_PROVIDER_URL || 'http://localhost'
, grant = new Grant({
defaults: {
protocol: 'https',
host: oauthConsumer,
transport: 'session',
state: true
},
myOAuth: {
key: process.env.CLIENT_ID || 'test',
secret: process.env.CLIENT_SECRET || 'secret',
redirect_uri: `${oauthConsumer}/connect/myOAuth/callback`,
authorize_url: `${oauthProvider}/oauth/authorize`,
access_url: `${oauthProvider}/oauth/token`,
oauth: 2,
scope: ['openid', 'profile'],
callback: '/done',
scope_delimiter: ' ',
dynamic: ['uiState'],
custom_params: { deviceId: 'abcd', appId: 'com.pud' }
}
})
var app = express()
app.use(morgan('dev'))
// REQUIRED: (any session store - see ./examples/express-session)
app.use(session({secret: 'grant'}))
// Setting the FrontEndModule URL in the Dynamic key of Grant.
app.use((req, res, next) => {
req.locals.grant = {
dynamic: {
uiState: req.query.uiState
}
}
next();
})
// mount grant
app.use(grant)
app.get('/done', (req, res) => {
if (req.session.grant.response.error) {
res.status(500).json(req.session.grant.response.error);
} else {
res.json(req.session.grant);
}
})
app.listen(port, () => {
console.log(`READY port ${port}`)
})