認證與受權:2、session認證

一、session認證原理


session.drawio.png

流程大體以下:html

  • 進入某個平臺的頁面後會發送一些請求,第一次請求時沒有攜帶cookie,自動條狀到SSO進行登陸
  • 登錄後,SSO會生成sessionID並保存到cookie中,而後重定向到應用平臺
  • 接着發送的請求會攜帶cookie,若是session過時,那麼跳轉到SSO再一次登陸

二、window下redis安裝與使用


session會話保存到redis集羣中,實現服務器共享session。前端

2.一、安裝redis

  • 使用wget或者直接到github下載redis壓縮包,將壓縮包解壓到任何文件夾中,好比D:\redis
  • 設置密碼,打開redis.windows.conf文件,添加requirepass ****,以下圖:
    image2020-7-14_18-6-55.png
  • redis-server.exe使用本地配置啓動redisnode

    cd D:\redis
    
    redis-server.exe redis.windows.conf
  • 運行redis-cli.exe以鏈接到您的redis實例react

    cd D:\redis
    
    redis-cli.exe
  • 開始使用redis

PS:若是報了(error) NOAUTH Authentication required.是由於設置了認證密碼,須要輸入密碼(就是上面設置的密碼)進行認證登入jquery

2.二、使用圖形化工具鏈接redis

Redis React是一個簡單易用的用戶界面,用於瀏覽Redis服務器中的數據,該數據由React桌面模板構建,可在Windows,OSX,Linux等多種平臺上使用,也能夠部署爲自託管控制檯或ASP.NET Web應用程序。git

Redis React充分利用了基於Web的UI的導航和深層連接優點,React框架的生產力和響應能力 以及本機桌面應用程序提供的豐富的本機體驗和OS集成。github

三、模擬redis集羣

在本地啓動redis就能夠輕鬆的模擬redis集羣,全部其餘服務器直接鏈接redis就能夠了redis

本文後臺使用nodeJs實現,生成session和操做redis依賴express-sessionconnect-redisredis三個庫。sql

express-session:中間件,用於在後端生成sessionID和cookie,會在request流對象中生成一個Session對象,用於操做session。
connect-redis:這是一個關於session的持久化插件, 配合express-session使用。此模塊基於redis,將session相關信息持久化(意思就是將session存入redis)。
redis:用於鏈接和操做redis數據庫。

使用方式大體以下:

const express = require("express")
const session = require('express-session')
const RedisStrore = require('connect-redis')(session)
const redis = require('redis')


const config={
  "cookie" : {
    "path": "/",
    "maxAge" : 1800000,
    "httpOnly": true
  },
  "sessionStore" : {
    "host": "127.0.0.1", // redis主機
    "port": "6379", // redis默認端口號
    "pass": "****",
    "auth_pass": "****", // 設置了密碼時須要該字段
  }
}

const app = express()
const client = redis.createClient(6379, '127.0.0.1', config.sessionStore) // 鏈接redis,建立實例

app.use(session({
  name : "sessionId", // sessionID的屬性名
  secret : 'Asecret123-', // 生成sessionID的密鑰
  resave : false,
  rolling: false, //在每次請求時強行設置 cookie,這將重置 cookie 過時時間(默認:false)
  saveUninitialized : false, // 強制將未初始化的 session 存儲。當新建了一個 session 且未設定屬性或值時,它就處於未初始化狀態。在設定一個 cookie 前,這對於登錄驗證,減輕服務端存儲壓力,權限控制是有幫助的。(默 認:true)。建議手動添加。
  cookie: config.cookie,
  store: new RedisStrore({ client })
}));

使用後request流對象會新增sessionID字段和session對象

image.png

三、應用平臺實現


前端部分:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
</head>
<body>
  <div id="app">home</div>
  <button onclick="logout()">退出</button>
</body>
</html>
<script>
  $.ajax({
    url: "http://127.0.0.1:1001/getData",
    method: 'GET',
    dataType: 'json',
    xhrFields: {
      withCredentials: true //容許攜帶Cookie
    },
    success: function(res) {
      console.log(res)
      if (res.code === 401) {
        window.location.href = "http://127.0.0.1:2000/?redirect_url=" + encodeURIComponent(window.location.href)
      } else if (res.code === 200) {
        $("#app").text(res.data.msg)
      }
    }
  })

  function logout() {
    $.ajax({
      url: "http://127.0.0.1:1001/logout",
      method: 'GET',
      dataType: 'json',
      xhrFields: {
        withCredentials: true //容許攜帶Cookie
      },
      success: function(res) {
        if (res.code === 200) {
          // 不能再這裏操做document.cookie來刪除sessionId,由於設置了httpOnly
          window.location.reload()
        }
      }
    })
  }
</script>

後端部分:

// 攔截器
app.all('*', function(req, res, next) {
  if (req.cookies.sessionId) {
    var sessionId = req.cookies.sessionId.split('.')[0].replace("s:", "sess:")
    console.log('sessionId', sessionId)
    client.get(sessionId, function(err, reply) {
      console.log('reply', reply)
      if (reply) {
        next();
      }
    })
  } else {
    console.log("=============重定向到SSO=============")
    res.json({
      code: 401
    })
  }
})

app.get('/getData', (req, res) => {
  res.json({
    code: 200,
    data: {
      msg: 'hello, welcome you!'
    }
  })
})

app.get('/logout', (req, res) => {
  req.session.destroy(err => {
    if (err) throw new ErrorEvent("註銷失敗")
    res.clearCookie("sessionId").json({
      code: 200
    })
  })
})

初始進入頁面發送http://127.0.0.1:1001/getData請求,若是沒有攜帶cookie,後臺攔截器就會攔截,返回401,前臺判斷是401就重定向到SSO

四、SSO實現


前端部分:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>login</title>
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
</head>
<body>
    <div>
      <label>帳戶:</label>
      <input type="text" class="account" name="account" value="">
    </div>
    <div>
      <label>密碼:</label>
      <input type="text" class="password" name="password" value="">
    </div>
    <div>
      <input type="button" onclick="submit()" value="登陸">
      <input type="reset" value="重置">
    </div>
</body>
</html>
<script>
  // 獲取url參數
  function getQueryVariable(variable) {
    var query = window.location.search.substring(1);
    var vars = query.split("&");
    for (var i=0;i<vars.length;i++) {
      var pair = vars[i].split("=");
      if(pair[0] == variable){return pair[1];}
    }
    return(false);
  }

  function submit() {
    var account = $(".account").val().trim()
    var password = $(".password").val().trim()
    if (!account || !password) {
      alert("帳戶密碼不能爲空")
    }
    $.ajax({
      url: 'http://127.0.0.1:2001/login',
      method: 'POST',
      data: { account, password },
      dataType: 'json',
      contentType: 'application/x-www-form-urlencoded',
      xhrFields: {
        withCredentials: true //容許攜帶Cookie
      },
      success: function(res) {
        if (res.code === 200) {
          setTimeout(() => {
            window.location.href = decodeURIComponent(getQueryVariable("redirect_url"))
          }, 1000);
        }
      } 
    })
  }
</script>

後臺部分:

app.post('/login', (req, res) => {
  var account = req.body.account
  var pwd = req.body.password
  if (!account || !pwd) {
    console.log("帳戶密碼不能爲空")
    return
  }
  nosql.one().make(builder => {
    builder.where('account', '=', account);
    builder.where('password', '=', pwd);

    builder.callback((err, userInfo) => {
      if (userInfo) {
        req.session.regenerate(err => {
          if (err) {
            console.log('生成sessionID失敗: ' + err)
          } else {
            console.log(req)
            req.session.userInfo = userInfo
            req.session.save()
            // res.cookie("sessionId", req.sessionID, req.session.cookie)
            res.json({
              code: 200
            })
          }
        })
      } else {
        console.log("該帳戶不存在")
      }
    })
  })
})

這裏數據庫使用的是nosql文檔數據庫,依賴nosql模塊。登陸成功後regenerate方法會生成session和cookie,而後將session保存到redis中

PS:存在cookie中的sessionID和存在redis中的sessionID不同,這是由於express-session底層實現的緣由,有興趣的同窗能夠深刻一下

項目地址:https://github.com/Revelation...

五、參考

https://segmentfault.com/a/11...
https://github.com/ServiceStackApps/RedisReact#download

https://github.com/ServiceStack/redis-windows

相關文章
相關標籤/搜索