Maison > Article > interface Web > Parlons de la façon de mettre en œuvre l'authentification unique (SSO) basée sur Node
Qu'est-ce que l'authentification unique ? Quel est le principe ? Comment y parvenir ? L'article suivant vous présentera l'authentification unique et expliquera comment utiliser Node pour mettre en œuvre l'authentification unique SSO. J'espère qu'il vous sera utile !
【Tutoriels associés recommandés : Tutoriel vidéo Nodejs, Enseignement de la programmation】
À mesure que l'activité de l'entreprise augmente, différents systèmes seront inévitablement produits si chacun le ferait. être très gênant si le système nécessite des connexions séparées.
Une solution telle que l'authentification unique est née. Le nom complet de l'authentification unique est Single Sign On, ou SSO en abrégé, cela signifie que si vous vous connectez à un système dans plusieurs groupes d'applications système, vous pouvez l'être. autorisé dans tous les autres systèmes sans vous reconnecter.
Par exemple, Xiao Ming s'est connecté à Taobao aujourd'hui. S'il ne s'est pas connecté, il lui sera demandé de saisir des informations d'authentification (nom d'utilisateur, mot de passe, etc.). Après s'être connecté, lorsqu'il visitera la page de Tmall, il pourra y accéder. directement sans vous connecter.
SSO nécessite un centre de certification indépendant. Seul le centre de vérification indépendant peut accepter le nom d'utilisateur, le mot de passe et d'autres informations de sécurité de l'utilisateur. Les autres systèmes ne fournissent pas d'entrées de connexion et acceptent uniquement. ceux du centre de certification. Autorisation indirecte. L'ensemble du processus peut être simplement décrit avec l'image ci-dessus :
Lorsque l'utilisateur se connecte pour accéder à l'application A, l'application A constate que l'utilisateur n'est pas connecté, accède au centre d'authentification SSO et utilise sa propre adresse comme un paramètre pour faciliter les rappels
SSO Le centre d'authentification constate que l'utilisateur ne s'est pas connecté auparavant et dirige l'utilisateur vers la page de connexion, l'utilisateur remplit le nom d'utilisateur et le mot de passe pour soumettre une demande de connexion ; le centre d'authentification SSO vérifie ; les informations utilisateur et crée une session entre l'utilisateur et le centre d'authentification SSO (les informations sont enregistrées dans un cookie à ce moment-là), et crée un jeton d'autorisation en même temps
le centre de certification sso passe à la demande d'origine adresse (application A) avec le jeton
L'application A obtient le jeton et se rend au centre de certification SSO pour vérifier s'il est valide S'il revient Enregistrez effectivement l'application A
L'application A crée une session avec l'utilisateur. , affiche les ressources et maintient l'état de connexion de l'utilisateur
Lorsque l'utilisateur accède à l'application B, il s'avère que l'utilisateur n'est pas connecté (le serveur d'authentification SSO et l'application A et l'application B ne sont pas le même domaine, ne peuvent pas fournir l'état de connexion) , accédez au centre de certification SSO et apportez votre adresse et les informations de cookie de la session précédente avec le centre de certification SSO
Le centre de certification SSO constate que l'utilisateur est connecté et revient à l'adresse de l'application B, et joignez le jeton token
La même application B obtient le jeton et se rend au centre de certification SSO pour vérifier s'il est valide. Si elle renvoie une application enregistrée valide B
, l'application B crée une session avec l'utilisateur et affiche. ressources et maintenir le statut de connexion de l'utilisateur
Trois services différents
Ici, nous devons démarrer trois services pour simuler respectivement l'application A, le serveur d'authentification SSO et l'application B
Le service avec le numéro de port 8383 voici le serveur d'authentification SSO, et le reste : 8686 et :8787 représentent respectivement l'application A et l'application B.
En fait, les codes de l'application A et de l'application B sont presque les mêmes. Comme le montre la figure ci-dessus, nous pouvons définir différents ports et noms d'application en passant des paramètres.
Jetons d'abord un coup d'œil à l'effet
La première visite accède à la page de connexion
Appliquez A pour déterminer l'état de connexion, accédez au serveur d'authentification SSO
Appliquez A
const Koa=require('koa'); const Router=require('koa-router') const views=require('koa-views') const static=require('koa-static') const path=require('path'); const app=new Koa(); const router=new Router(); const session=require('koa-session') const koa2Req=require('koa2-request'); //模版引擎相关配置 app.use(views(path.join(__dirname,'./views')),{ extension:'ejs' }) app.keys=['key'] const keyMap={ '8686':'koa:sess8686', '8787':'koa:sess8787' } const CONFIG={ key:keyMap[process.env.PORT] || 'koa:sess', maxAge:1000*60*60*24, httpOnly:true } app.use(session(CONFIG,app)) const system=process.env.SERVER_NAME router.get("/",async (ctx)=>{ //通过 session来判断 应用A的登录状态 let user=ctx.session.user if(user){ //... } else //1、当用户登录访问应用A时,应用A发现用户未登录(应为服务器没有保存对应的session) { let token=ctx.query.token //第一次登录url上也不会有令牌 if(!token) { //1、跳转到SSO认证服务器 ctx.redirect(`http://localhost:8383/login?redirectUrl=${ctx.host+ctx.originalUrl}`) } else { //... } } }) app.use(router.routes()) const port=process.env.PORT||8888 app.listen(port,()=>{ console.log(`app ${system} running at ${port}`) })
Serveur d'authentification pour déterminer l'état de connexion, afficher la page de connexion
Serveur d'authentification SSO
La structure de répertoires du serveur d'authentification est la suivante Il gère principalement deux fonctions, l'une est la logique de connexion et l'autre est la vérification ultérieure de la validité du jeton, qui sont gérées respectivement par le routage login.js et check-token.js
Auth/index. js
const Koa=require('koa'); const Router=require('koa-router') const views=require('koa-views') const path=require('path'); const app=new Koa(); const router=new Router(); const login=require("./routes/login") const checkToken=require('./routes/check-token') const bodyparser=require('koa-bodyparser') app.use(views(path.join(__dirname,'./views')),{ extension:'ejs' }) app.use(bodyparser()) //处理登录相关的逻辑 router.use('/login',login.routes()) //处理令牌验证的逻辑 router.use('/check_token',checkToken.routes()) app.use(router.routes()) app.listen(8383,()=>{ console.log(`app listen at 8383`) })
Tout à l'heure, nous avons commencé à partir de l'application A, nous passons à http://localhost:8383/login?redirectUrl=localhost:8686
pour voir la logique de connexion
Auth/routes/login.js
const service = require("../service"); const router=require("koa-router")() router.get('/',async (ctx)=>{ const cookies=ctx.cookies; const token=cookies.get('token'); //从cookie中判断应用A的登录态 if(token && service.isTokenVailid(token)){ //。。。如果有登录过 }else{ //2、SSO认证中心发现用户没有登录过,于是渲染登录页面登录页面; await ctx.render('login.ejs',{ extension:'ejs' }) } }) //。。。 module.exports=router
Page de connexion
Auth/views/login.ejs
<html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>统一登录</title> </head> <body> <h1>统一登录</h1> <form method="post"> <div>用户名: <input type="text" name="name"/></div> <div>密码 <input type="text" name="password" /></div> <div><input type="submit" value='登录'></div> </form> </body> </html>
Vérifiez les informations de l'utilisateur et créer un jeton
Auth /routes/login.js
router.post('/',async (ctx)=>{ //2、用户填写用户名密码提交登录申请; const body=ctx.request.body; const {name,password}=body; //2、SSO认证中心校验用户信息, if(name==="admin" && password==="123456"){ //2、创建用户雨SSO认证中心的会话(这时会把信息保存到cookie中),同时创建授权令牌token const token="passport"; await ctx.cookies.set('token',token,{ maxAge:1000*60*60*24*30, httpOnly:true }) if(ctx.query.redirectUrl){ //3、sso认证中心带着令牌跳转到最初的请求地址(应用A) ctx.redirect(`${ctx.protocol}://${ctx.query.redirectUrl}?token=${token}`) //回跳地址是 http://localhost:8686/?token=passport }else{ ctx.body="<h1>登录成功!</h1>" } }else{ ctx.response.body={ error:1, msg:'用户名或密码错误' } } })
transporte le jeton du serveur d'authentification et revient à l'application A
La vérification du jeton renvoie la ressource
L'application A
app.use(views(path.join(__dirname,'./views')),{ extension:'ejs' }) //... const system=process.env.SERVER_NAME router.get("/",async (ctx)=>{ let user=ctx.session.user if(user){ //... } else //这时应用A依旧没有登录态 但url上有了令牌 http://localhost:8686/?token=passport { let token=ctx.query.token if(!token) { //...跳转去SSO登录页面 } else //跳回应用A时走这里的逻辑 { //ajax请求 4. 应用A拿到令牌去SSO认证中心认证是否有效,如果返回有效注册应用A const url=`://localhost:8383/check_token?token=${token}&t=${new Date().getTime()}` let data = await koa2Req(ctx.protocol + url); if(data && data.body){ try { const body=JSON.parse(data.body) const {error,userId}=body; // console.log(error,userId) 0,admin if(error==0){ if(!userId){ ctx.redirect(`http://localhost:8383/login?redirectUrl=${ctx.host+ctx.originalUrl}`) return } //验证通过后注册session,渲染页面 //5. 应用A创建与用户之间的会话,展示资源并维持用户登录态 ctx.session.user=userId; await ctx.render('index.ejs',{ user:userId, system }) }else{ ctx.redirect(`http://localhost:8383/login?redirectUrl=${ctx.host+ctx.originalUrl}`) } } catch (error) {console.log(error)} } } } }) app.use(router.routes()) const port=process.env.PORT||8888 app.listen(port,()=>{ console.log(`app ${system} running at ${port}`) })
traite le jeton de vérification dans le SSO correspondant La logique de la carte
Auth/routes/check-token
const router=require("koa-router")() const service=require("../service") router.get('/',async (ctx)=>{ const token=ctx.query.token; const result={ error:1 } //当token 是 password时 if(service.isTokenVailid(token)){ result.error=0; result.userId='admin' } ctx.body=result }) module.exports=router
Auth/service/index.js
module.exports={ isTokenVailid: function(token){ if(token && token==='passport'){ return true } return false } }
Maintenant, l'utilisateur peut accéder à l'application A normalement et les informations de connexion de l'utilisateur sont disponibles sur le serveur SSO et le serveur d'application A.
访问应用B
带cookie跳转至SSO认证服务器
应用B
//... router.get("/",async (ctx)=>{ let user=ctx.session.user if(user){ //... }else{ let token=ctx.query.token //... if(!token) { //同样既没有session也没有令牌,跳转到SSO认证服务器 //6、当用户访问应用B时,发现用户未登录(SSO认证服务器与应用A应用B不是同一个域,不能提供登录态),跳转到SSO认证中心,并将自己的地址和之前和SSO认证中心会话的cookie信息带入 ctx.redirect(`http://localhost:8383/login?redirectUrl=${ctx.host+ctx.originalUrl}`) } else { //。。。验证令牌的部分 } } }) app.use(router.routes()) const port=process.env.PORT||8888 app.listen(port,()=>{ console.log(`app ${system} running at ${port}`) })
从认证服务器携带令牌跳转回应用B
SSO认证服务器 ,再次登录时携带了cookie,因此不会再请求登录页面 Auth/routes/login
//... router.get('/',async (ctx)=>{ const cookies=ctx.cookies; const token=cookies.get('token'); //7. SSO认证中心发现用户已登录,跳转回应用B地址,并附上令牌token if(token && service.isTokenVailid(token)){ const redirectUrl=ctx.query.redirectUrl; if(redirectUrl){ //带着令牌跳转回应用B ctx.redirect(`${ctx.protocol}://${redirectUrl}?token=${token}`) }else{ ctx.body="<h1>登录成功!</h1>" } }else{ //...渲染登录页面 } }) //..
令牌校验 返回资源
这里的逻辑和5,6两步一样,因为token容易伪造,所以要检验真伪。 应用B
app.use(views(path.join(__dirname,'./views')),{ extension:'ejs' }) //... const system=process.env.SERVER_NAME router.get("/",async (ctx)=>{ let user=ctx.session.user if(user){ //... } else //这时应用B依旧没有登录态 但url上有了令牌 http://localhost:8787/?token=passport { let token=ctx.query.token if(!token) { //...跳转去SSO登录页面 } else //跳回应用B时走这里的逻辑 { //ajax请求 8. 同样的应用B拿到令牌去SSO认证中心认证是否有效,如果返回有效注册应用B const url=`://localhost:8383/check_token?token=${token}&t=${new Date().getTime()}` let data = await koa2Req(ctx.protocol + url); if(data && data.body){ try { const body=JSON.parse(data.body) const {error,userId}=body; // console.log(error,userId) 0,admin if(error==0){ if(!userId){ ctx.redirect(`http://localhost:8383/login?redirectUrl=${ctx.host+ctx.originalUrl}`) return } //验证通过后注册session,渲染页面 //9. 应用B创建与用户之间的会话,展示资源并维持用户登录态 ctx.session.user=userId; await ctx.render('index.ejs',{ user:userId, system }) }else{ ctx.redirect(`http://localhost:8383/login?redirectUrl=${ctx.host+ctx.originalUrl}`) } } catch (error) {console.log(error)} } } } }) app.use(router.routes()) const port=process.env.PORT||8888 app.listen(port,()=>{ console.log(`app ${system} running at ${port}`) })
至此单点登录的大部分逻辑都已经完成,之后再session有效期内再访问页面,就不需要再登录,直接返回资源
router.get("/",async (ctx)=>{ //如果session中有用户信息,说明已经登录过,直接返回请求资源 let user=ctx.session.user if(user){ await ctx.render('index.ejs',{ user, system }) } //... })
原文地址:https://juejin.cn/post/7088343138905325582
作者:YoYo君
更多node相关知识,请访问:nodejs 教程!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!