cookie实现单点登录
·
前言
网上搜单点登录的文章有很多,实现方案有cookie、session、jwt等,但大多讲的都是原理,核心问题都没提到,因此我根据这俩条件整理个可行方案:
- 只考虑同域,不跨域,如a.my.com、b.my.com等,都在*.my.com主域内,因此使用cookie比较方便
- 前后端分离,目前大多前端项目都基于react、vue,因此必须要考虑
有个问题一直没搞清,就是使用cookie方案时,前端如何判断是否登录。按自己的理解,我想了以下几个方案:
- 方案一:HttpOnly设为false ,这样前端js就能读取cookie,判断是否存在登录信息,但是安全性会降低
- 方案二:使用nginx判断,nginx代理判断header里是否有指定cookie,没有则跳转sso登录页
- 方案三:前端不判断,等到网页中有接口请求时,让后端判断有无cookie
- 方案四:设置两个cookie(推荐),一个存登录令牌(HttpOnly=true),一个存用户id(HttpOnly=false),前端读取cookie中有无用户id来判断是否登录
实现逻辑(方案四)
场景如下:
- A网站:
a.my.com - B网站:
b.my.com - SSO网站:
sso.my.com - cookie值:
tk=xxxxxsso服务端加密后的令牌uid=123用户id
A网站步骤:
- 浏览器打开A首页
a.my.com/index.html - A网站js判断cookie不存在uid,跳转
sso.my.com/login.html?return=a.my.com/index.html - sso登录页输入账号密码点击登录,post请求给
sso.my.com/api/login - sso服务端验证账号密码,成功则设置cookie:tk=xxx(HttpOnly=true),uid=123(HttpOnly=false),域名都设为
.my.com,返回200 - sso登录页js判断返回200,则获取url中return值
a.my.com/index.html,并跳转到A网站 - A网站每次请求api时,A后台验证cookie中的tk和uid(解密tk的方法应与sso加密方法对应),失败或过期则返回401
- A网站js判断返回401,则跳转sso登录页
sso.my.com/login.html?return=当前url
B网站步骤:
- 浏览器打开
b.my.com/index.html - B网站js判断cookie中存在uid,则进入网页,实现单点登录
- 其他同上
部分代码
sso前端
<body>
<div><label>Username</label><inputtype="text"></div>
<div><label>Password</label><input type="password"></div>
<div><button onclick="loginRequest()">Login</button></div>
</body>
<script>
function loginRequest() {
fetch("/api/login", {
method: "post",
data: null,
}).then((data) => {
if (data.status == 200){
const url = new URL(window.location.href);
location = url.searchParams.get("return")
}else{
console.log(data)
}
})
}
</script>
sso后端(golang)
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.POST("/api/login", func(c *gin.Context) {
c.SetCookie("tk", "xxxxx", 3600, "/", ".my.com", true, false)
c.SetCookie("uid", "123", 3600, "/", ".my.com", false, false)
c.JSON(200, "ok")
})
r.Run(":8000")
}
A网站前端
<body>
<h2>A网站</h2>
<button onclick="testRequest()">Test</button>
</body>
<script>
function getCookie(key) {
return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
}
const ssoUrl = "http://localhost:8000/login"
if (!getCookie("uid")){
window.location = ssoUrl + "?return=" + encodeURIComponent(location.href)
}
function testRequest() {
fetch("/api/test").then((data) => {
if (data.status == 401) {
window.location = ssoUrl + "?return=" + encodeURIComponent(location.href)
} else {
console.log(data)
}
})
}
</script>
A网站后端(golang)
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func checkCookies(c *gin.Context) bool{
tk, _ := c.Cookie("tk")
uid, _ := c.Cookie("uid")
// TODO 验证tk、uid
return true
}
func main() {
r := gin.Default()
r.GET("/api/test", func(c *gin.Context) {
if checkCookies() == false {
c.JSON(http.StatusUnauthorized, "invalid cookie")
} else {
c.JSON(http.StatusOK, "ok")
}
})
r.Run(":8001")
}
更多推荐
所有评论(0)