概况
百度百科
单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
关键词
token
、session
、cookie
、统一的认证系统
流程说明
这里将 一期 系统比喻主系统,二期 系统使用(跳转到) 一期 系统的登录认证。
-
打开二期主页,当没有登录权限
token
,跳转到一期登录页。并在跳转的url
中带上二期url
,用于登录成功后跳回二期。
2.在 一期 登录成功后,
url
中带上参数token
并返回到 二期 系统;
- 在新窗口中打开 一期 主页,默认已登录。点击 “注销” (此时向后台接口发请求注销
token
,并 删除相关Cookie
信息),返回登录页; - 当在新窗口中打开二期页或刷新页面,调相关接口
token
验证接口。如果接口返回http 401
,则token
已失效。此时会返回一期登录页,重新登录(即图一)。
代码实现
注意要安装插件 js-cookie
npm i js-cookie
公共方法,一期和二期都有使用。解析
function getParamFromUrl() {
let url = location.href;
let map = {}
let paramsStr = url.split('?')[1]
if (paramsStr) {
let paramStrList = paramsStr.split('&')
for (let i = 0; i < paramStrList.length; i++) {
let param = paramStrList[i].split('=')
map[param[0]] = param[1]
}
}
return map;
}
cookie Id,注意一期和二期同名。
const tokenCookieId = 'user_token';
二期
config.js
// 一期API服务地址
const ApiUrl = 'http://192.168.182.243:8088/';
// 一期登录页面
const LoginUrl = "http://192.168.182.243:8088/login"
main.js
先判断是否有token
import Cookie from 'js-cookie'
// 解析 url 参数
let token = getParamFromUrl().token;
// url 是否带有 token
if (token) {
Cookie.set(tokenCookieId, params.token);
init(token);
} else {// Cookie 中能否找到 token
token = Cookie.get(tokenCookieId);
init(token);
}
初始化方法,判断是否有 token
:
- 没有
token
,则直接跳转登录; - 有
token
,验证token
是否有效,无效则直接跳转登录。**有效*则进入系统;
import axios from 'axios'
function init(token) {
if (token) {
let newAxios = axios.create({
baseURL: ApiUrl,
headers: { 'Authorization': token.replace('%20', ' ') }
})
// 获取用户信息,同时验证 token 是否有效
newAxios.get(`/api/user/myinfo`).then((response) => {
store.commit('user/SET_NAME', {
userName: response.data.username,
isManager: response.data.manager
})
// 注意:此时在初始化 Vue 主页
return new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
})
.catch((error) => {
// 一般会返回 401,token失效。移除 Cookie 中的 token,
Cookie.remove(tokenCookieId);
// 跳转到一期去登录,注意带上本页的 url,用于跳回
location.href = LoginUrl + "?redirect=" + location.origin;
})
} else {// 根本就没有 token,一般为首次打开系统页面
Cookie.remove(tokenCookieId);
location.href = LoginUrl + "?redirect=" + location.origin;
}
}
一期
router/index.js
路由前置守卫(全局) router.beforeEach
router.beforeEach((to, from, next) => {
let redirUrl = getParamFromUrl().redirect;
// Vuex store 也是从 Cookie 中获取,这里是项目截取,也可直接使用 Cookie.get
store.commit('user/REFRESH_NAME');
// 判断是否有 token
if (store.state.user.token) {
// 是否有重定向 url,有则说明是从二期系统发来的请求。带上token返回到一期系统
if(redirUrl) {
location.href = redirUrl+'?token='+store.state.user.token
}
else if (to.path == '/login') {
next('/first')
}
else {
let pages = ''
if(store.state.user.pages){
pages = store.state.user.pages.split(',')
}
if (store.state.user.isManager) {
next()
}
else if (authorPages.indexOf(to.path) == -1) {
next()
}
else if (pages.indexOf(to.path ) > -1) {
next()
}
else {
next('/noAuthor')
}
}
} else {
if(redirUrl) {// 没token 且从二期发来的请求,直接进入登录页即可
next()
}
else if (authorPages.indexOf(to.path) == -1){
next()
}
else if(to.path == '/login') {
next()
}
else {
next('/login')
}
}
})
App.vue
mounted
方法
mounted () {
// 二期url
let redirectUrl = getParamFromUrl().redirect;
let token = Cookie.get(tokenCookieId);
if (redirectUrl && token) {// 有 redirect 和 token 就返回到二期
location.href = decodeURIComponent(redirectUrl) + "?token=" + token;
}
else{// 一期,说明直接从一期打开。如下操作为可选项,如获取用户信息
let url = `/api/user/myinfo`;
this.$axios.get(url).then((response) => {
this.$store.commit('user/SET_NAME', {
userName: response.data.username,
isManager: response.data.manager
})
})
.catch((error) => {
this.$store.dispatch('user/logout');
if (!redirectUrl) {// 不是从二期进入,则直接去登录
this.$router.push('/login')
}
})
}
}
store/modules/user.js
logout
登出方法,删除相关 Cookie
const actions = {
logout ({ commit }) {
Cookies.remove('user_name')
Cookies.remove('user_token')
Cookies.remove('user_isManager')
mutations.REFRESH_NAME(state)
}
}
views/login.vue
登录时,如果有 redirect
,则是从二期跳转过来的。
commit () {
if (this.inputName.trim() == '' || this.inputPw.trim() == '') {
this.$message({ type: 'error', message: '请输入用户名密码!' });
return;
}
let form = {
userName: this.inputName,
password: md5(this.inputPw),
}
// 登录请求
let navUrl = 'api/user/login'
this.$axios.post(navUrl, form).then((resp) => {
if (resp.data.success) {
let token = resp.data.token;
this.$store.commit('user/SET_TOKEN', token);
// 是否为二期请求
let redirectUrl = getParamFromUrl().redirect;
if (redirectUrl) {// 是二期请求,带着 token 跳转回去
location.href = decodeURIComponent(redirectUrl) + "?token=" + token;
} else {// 不是,则进入一期系统
location.reload()
}
} else {
this.$message({ type: 'error', message: resp.data.message })
}
})
.catch((error) => {
this.$message({ type: 'error', message: error })
})
},
TopHeader.vue 和 Adminbox.vue
注销时一定要调后台接口,真正是注销服务端的 token。
// 清除用户相关信息
this.$store.dispatch("user/logout");
// 跳转到登录页
this.$router.push('/login');
// 调服务接口的注销方法,真正注销
this.$axios.get('api/user/logout').then((r)=>{
console.log('注销成功');
}).catch(e=> {
console.error(e);
})