搜尋
首頁web前端js教程聊聊基於Node實作單一登入(SSO)的方法

聊聊基於Node實作單一登入(SSO)的方法

Dec 06, 2022 pm 07:49 PM
node單一登入sso

什麼是單一登入?原理是什麼?怎麼實現?以下這篇文章帶大家了解單一登錄,並聊聊使用Node實作單一登入SSO的方法,希望對大家有幫助!

聊聊基於Node實作單一登入(SSO)的方法

【相關教學推薦:nodejs影片教學程式設計教學

什麼是單一登入

隨著公司業務的增多,必然會產生各個不同的系統,如果每個系統都需要單獨登入的話就會很不方便。

因此產生了單一登入這樣的解決方案,單一登入全名為Single Sign On,簡稱SSO,意思是在多個系統應用群組中登入一個系統,便可在其他所有系統中得到授權而無需再次登入。

例如小明今天登入了淘寶,如果沒有登錄,就會被要求輸入認證資訊(使用者名稱密碼等),登入過後再去造訪天貓的頁面時就不需要登入可以直接存取。

單一登入原則

聊聊基於Node實作單一登入(SSO)的方法

#SSO 需要有一個獨立的認證中心,只有獨立的驗證中心能接受使用者的用戶名密碼等安全訊息,其他系統不提供登入入口,只接受認證中心的間接授權。 整個過程可以簡單的用上圖描述:

  • 當使用者登入存取應用程式A時,應用程式A發現使用者未登入,跳轉至SSO認證中心,並將自己的位址作為參數方便回調

  • SSO認證中心發現用戶沒有登入過,將用戶引導至登入頁面;用戶填寫用戶名密碼提交登入申請;SSO認證中心校驗用戶信息,建立用戶雨SSO認證中心的會話(這時會把資訊儲存到cookie中),同時建立授權令牌token

  • sso認證中心帶著令牌跳到最初的請求位址(應用A)

  • 應用A拿到令牌去SSO認證中心認證是否有效,如果返回有效註冊應用A

  • 應用A創建與使用者之間的會話,展示資源並維持使用者登入狀態

  • 當使用者存取應用程式B時,發現使用者未登入(SSO認證伺服器與應用A應用B不是同一個網域,不能提供登錄態),跳到SSO認證中心,並將自己的地址和之前和SSO認證中心會話的cookie資訊帶入

  • SSO認證中心發現用戶已登錄,跳轉回應用B位址,並附上令牌token

  • 同樣的應用程式B拿到令牌去SSO認證中心認證是否有效,如果回傳有效註冊應用B

  • 應用程式B建立與使用者之間的會話,展示資源並維持使用者登入態

NodeJS 示範

三個不同的服務

這裡我們需要啟動三個服務來分別模擬應用A,SSO認證伺服器和應用B

聊聊基於Node實作單一登入(SSO)的方法

#這裡連接埠號碼8383的服務是SSO認證伺服器,其餘的:8686 和:8787 分別代表應用A與應用B。

其實應用A與應用B的程式碼幾乎一樣,如上圖所示我們可以透過穿參的方式來設定不同的連接埠及應用程式名稱。

先來看下效果

聊聊基於Node實作單一登入(SSO)的方法

#第一次造訪跳轉至登入頁

聊聊基於Node實作單一登入(SSO)的方法


##應用A判斷登入態,跳到SSO認證伺服器

應用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}`)

})

認證伺服器判斷登入態,渲染登入頁面

認證伺服器SSO

認證伺服器的目錄結構如下 主要處理兩個功能,一是登入邏輯,二是之後驗證令牌的有效性,分別有路由login.js 和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`)
})

剛才我們從應用程式A跳到http://localhost:8383/login?redirectUrl=localhost:8686來看login中的邏輯

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

登入頁面


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 id="统一登录">统一登录</h1>
    <form method="post">
       <div>用户名: <input type="text" name="name"/></div>
       <div>密码  <input type="text" name="password" /></div>
       <div><input type="submit" value=&#39;登录&#39;></div>
    </form>
</body>
</html>

校驗使用者訊息,建立令牌

###Auth/routes/login.js###
router.post(&#39;/&#39;,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(&#39;token&#39;,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 id="登录成功">登录成功!</h1>"
       }
   }else{
       ctx.response.body={
           error:1,
           msg:&#39;用户名或密码错误&#39;
       }
   }
})
########從認證伺服器攜帶令牌跳轉回應用A###############令牌校驗回傳資源#########應用A###
app.use(views(path.join(__dirname,&#39;./views&#39;)),{
    extension:&#39;ejs&#39;
  })

//...

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(&#39;index.ejs&#39;,{
                        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}`)

})
###與之對應的SSO中處理驗證令牌的邏輯###Auth/routes/check-token###
const router=require("koa-router")()
const service=require("../service")

router.get(&#39;/&#39;,async (ctx)=>{
  const token=ctx.query.token;
  const result={
      error:1
  }
  //当token 是 password时
  if(service.isTokenVailid(token)){
    result.error=0;
    result.userId=&#39;admin&#39;
  }
  ctx.body=result
 

})


module.exports=router
# ##Auth/service/index.js###
module.exports={
    isTokenVailid: function(token){
      if(token && token===&#39;passport&#39;){
          return true
      }
      return false
    }
}
###至此使用者已經能正常存取應用程式A,SSO伺服器和應用程式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(&#39;/&#39;,async (ctx)=>{
  const cookies=ctx.cookies;
  const token=cookies.get(&#39;token&#39;);
  //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 id="登录成功">登录成功!</h1>"
    }
  }else{
    //...渲染登录页面
  }
})
//..

令牌校验 返回资源

这里的逻辑和5,6两步一样,因为token容易伪造,所以要检验真伪。 应用B

app.use(views(path.join(__dirname,&#39;./views&#39;)),{
    extension:&#39;ejs&#39;
  })

//...

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(&#39;index.ejs&#39;,{
                        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(&#39;index.ejs&#39;,{
              user,
              system
        })
    }
    //...
 })

原文地址:https://juejin.cn/post/7088343138905325582

作者:YoYo君

更多node相关知识,请访问:nodejs 教程

以上是聊聊基於Node實作單一登入(SSO)的方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:掘金社区。如有侵權,請聯絡admin@php.cn刪除
使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

如何使用Next.js(前端集成)構建多租戶SaaS應用程序如何使用Next.js(前端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript:探索網絡語言的多功能性JavaScript:探索網絡語言的多功能性Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

JavaScript的演變:當前的趨勢和未來前景JavaScript的演變:當前的趨勢和未來前景Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

神秘的JavaScript:它的作用以及為什麼重要神秘的JavaScript:它的作用以及為什麼重要Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

Python還是JavaScript更好?Python還是JavaScript更好?Apr 06, 2025 am 12:14 AM

Python更适合数据科学和机器学习,JavaScript更适合前端和全栈开发。1.Python以简洁语法和丰富库生态著称,适用于数据分析和Web开发。2.JavaScript是前端开发核心,Node.js支持服务器端编程,适用于全栈开发。

如何安裝JavaScript?如何安裝JavaScript?Apr 05, 2025 am 12:16 AM

JavaScript不需要安裝,因為它已內置於現代瀏覽器中。你只需文本編輯器和瀏覽器即可開始使用。 1)在瀏覽器環境中,通過標籤嵌入HTML文件中運行。 2)在Node.js環境中,下載並安裝Node.js後,通過命令行運行JavaScript文件。

在Quartz中如何在任務開始前發送通知?在Quartz中如何在任務開始前發送通知?Apr 04, 2025 pm 09:24 PM

如何在Quartz中提前發送任務通知在使用Quartz定時器進行任務調度時,任務的執行時間是由cron表達式設定的。現�...

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器