由于文章的篇幅有限,无法将全部的代码贴上来,如想要看完整案例,请在公众号文章中留言(其他平台很少看…毕竟最近印度同事的UI组件库搞得我好烦)
1.关于SSO
单点登录又称之为SSO,全称为 Single Sign On ,一般在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
比如,当我们使用一个腾讯旗下的产品时,我们一般会接入QQ登陆,此时就可以认为QQ登陆为我们的SSO。只要我们登录了QQ,就可以根据凭证获取到你在QQ中的信息,并登录该平台。
2.SSO流程
无论使用第三方或者是自己的sso,下面将以一个开发人员的角度,以QQ作为例子讲解一个SSO的过程,
1.当用户第一次登录平台A的时候,由于该平台使用了QQ的服务去获取用户信息,该平台会自动调用QQ的服务(比如跳转QQ登录),此时我们会设置一个RedirectURL的参数到qq的login服务上。
2.当用户从qq登录后,qq的后台服务会根据我们的 RedirectURL 参数,将一个token传到我们的Redirect的地址。
3.当我们的服务接收到了来自QQ服务器的一个重定向请求,且该请求还会带一个token,我们可以根据QQ文档去调用Api获取我们想要的信息,比如获取用户信息等。
4.当我们获取到用户的信息之后,我们用 JsonWebToken 的形式重新去设置我们的token,并且使用其作为前后端通讯的token。
在上述的过程中,我们使用第三方的SSO,是基于以下几个原因
- qq本质上提供了用户的信息给我们,并且提供了一个便捷的,获取用户信息的api。
- QQ作为一个维护多年的平台,对于权限以及用户管理等模块已经很完善了
- 我们自己开发的话,我们需要花费大量的时间和精力,还不能保证一定没问题
3.关于Azure
上面的过程中,已经知道SSO的流程,想必大家都已经对SSO有了初步的认知,而选择一个SSO是要根据市场以及客户所用的SSO有关,比如对于外企,我们会选择Azure作为SSO,而不是选择很少外国人用的QQ。
Microsoft Azure 作为微软云计算,大公司背书.
最主要是我们的客户选择使用它,所以下面的例子会以其作为例子
4.使用SSO
不同平台的sso参数思路‘大差不差’,都是用 AppID + AppScrect 这一套,所以下面的例子也按照这个套路来介绍。
1.准备各类参数,其中最主要的参数是CLIENT_ID,TENANT_ID,CLIENT_SECRET。
//当前域名
LOGIN_REDIRECT=https://xxx.com
//重定向地址
OAUTH2_REDIRECT_URL=https://xxx.com/user/login_callback
//client_id,在app的详情中查看,由管理员给的
OAUTH2_CLIENT_ID=6aaaaaae-7aaa-4aaa-baaa-aaaaaaaaad89
//tent_id,可以理解为密钥。由管理员给的
OAUTH2_TENANT_ID=4266ec6c-fe9f-4893-82e9-996189e0b81b
//在Azure上生成的,验证机器是否允许登录
OAUTH2_CLIENT_SECRET=mvaaa~.qLgH8aaaaaaaaaTpnWaLD9Em-H3Z6gb_T
2.准备我们的登陆接口重定向到auzre的登陆接口
当用户调用我们的登陆接口时,我们会马上调用到Azure的服务去登陆。
@Get('user/login')
login(@Response() res) {
res.redirect(this.userServie.processLogin());
}
此时,浏览器的弹窗如下:
3.登陆成功后获取到用户的凭证
@Get('user/login_callback')
async loginCallback(@Request() req, @Response() res) {
let code = '';
if (req.query.code) {
code = req.query.code;
const tokenInfo = await this.userServie.getAccessTokenByCode(code, req.log);
// if redirect error, check cookie has refresh_token
if (tokenInfo.error) {
req.log.error(`user login callback error will redirect to login`);
res.redirect('/login');
} else {
const { claimsInfo, user, groups } = this.userServie.processAccessToken(tokenInfo.access_token);
if (!groups.includes(environmentConfig.azure.adGroupName)) {
req.log.error('User not in AD group');
res.status(400).json({ message: 'User not in AD group' });
}
req.log.info(`login user name is ${user.id}`);
const redirectUrl = `${environmentConfig.cx.frontend_url}?t=${claimsInfo}`;
res.redirect(redirectUrl);
}
} else {
req.log.error('ADFS grant code not found');
res.status(400).json({ message: 'ADFS grant code not found' });
}
}
上述代码中,流程在于获取到了azure的token之后,调用api获取用户信息,并生成新的token并给到前端。
4.根据凭证获取到用户的信息。
在上述代码中,我们完成了整个流程,但是最主要的核心代码如下
processAccessToken(azureToken) {
const auzraUserInfo = JWT.decode(azureToken);
const {
onPremisesSamAccountName = '',
cn = '',
name = '',
family_name = '',
given_name = '',
username = '',
groups = []
} = auzraUserInfo;
let adKeyWord = '';
let userName = '';
adKeyWord = name;
userName = `${given_name} ${family_name}`;
const jwtToken = JWT.sign(
{
cn: adKeyWord,
sAMAccountName: adKeyWord,
username: userName,
auth: 'saml',
thumbnail: ''
},
environmentConfig.cx.jwt_token_secret,
// { expiresIn: 7 * 24 * 60 * 60 }
{ expiresIn: 1 * 24 * 60 * 60 }
);
return { claimsInfo: jwtToken, user: { id: userName }, groups: groups };
}
至此,一个流程就结束了,我们将生成的 token 放到前端就可以了。
需要注意的是,我们的凭证是有expiry date的。
多谢关注~ 公众号求关注~
公众号文章