Cookie、Vuex状态管理器实现单点登录SSO

什么是单点登录?

单点登录即SSO(Single Sign-On),是一种统一认证和授权机制,指访问同一服务器不同应用中的受保护资源的同一用户,只需要登录一次,即通过一个应用中的安全验证后,再访问其他应用中的受保护资源时,不需要重新登录验证。
解决用户只需要登录一次就可以访问所有相互信任的应用系统,而不用重复登录。

单点登录的三种实现方式

  • 方法1:登录成功之后将Cookie回写到多个域名下
    这种办法可能十分简单,可以通过后端也可以用前端js实现,但维护工作十分复杂,同时对于增加站点也会特别痛苦。对于Cookie的销毁也是十分复杂的,因为还是要对所有域名下的Cookie进行删除。对于小型站点这种办法是十分可取的。如果Cookie的加密算法泄露,攻击者通过伪造Cookie则可以伪造特定用户身份。

  • 方法2:jsonp
    用户在父应用中登录后,跟Session匹配的Cookie会存到客户端中,当用户需要登录子应用的时候,授权应用访问父应用提供的JSONP接口,并在请求中带上父应用域名下的Cookie,父应用接收到请求,验证用户的登录状态,返回加密的信息,子应用通过解析返回来的加密信息来验证用户,如果通过验证则登录用户。同时这种办法需要很大的维护成本,每一次请求都要去固定的域下取相应的Cookie之后再做请求。维护十分头疼。

  • 方法3 :引入一个中间态的Server(页面重定向)
    通过父应用和子应用来回重定向中进行通信,实现信息的安全传递。
    父应用提供一个GET方式的登录接口,用户通过子应用重定向连接的方式访问这个接口,如果用户还没有登录,则返回一个的登录页面,用户输入账号密码进行登录。如果用户已经登录了,则生成加密的Token,并且重定向到子应用提供的验证Token的接口,通过解密和校验之后,子应用登录当前用户。

单点登录SSO实现

登录认证流程

相比于单系统登录,sso需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。

1.门户客户端要求登录时,输入用户名密码,认证客户端提交数据给认证服务器。
2.认证服务器校验用户名密码是否合法,合法响应用户基本令牌 userInfo 、访问令牌 access_token 、刷新令牌 refresh_token。

Vuex 登录信息状态管理

当登录成功后,后台响应的 userInfo、access_token、refresh_token 信息使用 Vuex 进行管理,并且将这些信息保存到浏览器 Cookie 中。

1.安装 js-cookie 和 vuex 模块

npm install --save js-cookie vuex

2.创建 Vuex.Store 实例
//store中index.js创建 session 状态模块

import Vue from 'vue' import Vuex from 'vuex'
import auth from './modules/session' 

Vue.use(Vuex)

const store = new Vuex.Store({ 
	modules: {
		session
	}
})

export default store

3.将 store 已添加到Vue 实例中

import Vue from 'vue'
import App from './App.vue'
import router from "./router" 
import store from './store'

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App),
}).$mount('#app')

4.Vuex中modules创建认证状态模块文件
//modules目录下创建添加对 userInfo、access_token、refresh_token 状态管理

import { PcCookie, Key } from '@/utils/cookie'

// 定义状态
const state = {
    userInfo: PcCookie.get(Key.userInfoKey)  ? JSON.parse(PcCookie.get(Key.userInfoKey)) : null, // 用户信息对象
    accessToken: PcCookie.get(Key.accessTokenKey), // 访问令牌字符串
    refreshToken: PcCookie.get(Key.refreshTokenKey), // 刷新令牌字符串
}

// 改变状态值 
const mutations = { 

  // 赋值用户状态
  SET_USER_STATE (state, data) {
      // 状态赋值
      const { userInfo, access_token, refresh_token } = data
      state.userInfo = userInfo
      state.accessToken = access_token
      state.refreshToken = refresh_token

      // 保存到cookie中
      PcCookie.set(Key.userInfoKey, userInfo)
      PcCookie.set(Key.accessTokenKey, access_token)
      PcCookie.set(Key.refreshTokenKey, refresh_token)
  },

  // 重置用户状态,退出和登录失败时用
  RESET_USER_STATE (state) {
      // 状态置空
      state.userInfo = null
      state.accessToken = null
      state.refreshToken = null
      // 移除cookie中的数据
      PcCookie.remove(Key.userInfoKey)
      PcCookie.remove(Key.accessTokenKey)
      PcCookie.remove(Key.refreshTokenKey)
  }
}

// 定义行为 
const actions = {
  // 登录操作  
  UserLogin ({ commit }, userData) {
    console.log('UserLogin', userData)
    const { username, password } = userData

    return new Promise((resolve, reject) => {
      login({ username: username.trim(), password: password }).then(response => {
        // 获取响应值 
        const { code, data } = response
        if(code === 20000) {
            // 状态赋值 
            commit('SET_USER_STATE', data)
        }
        resolve(response) // 正常响应钩子
      }).catch(error => {
        // 重置状态
        commit('RESET_USER_STATE')
        reject(error) // 异常
      })
    })
  },
  // 退出
  UserLogout({ state, commit }, redirectURL) {
    // 调用退出接口, 上面不要忘记导入 logout 方法
    logout(state.accessToken).then(response => {
        // 重置状态
        commit('RESET_USER_STATE')
        // 重写向回来源地址,如果没有传重定向地址则回到到登录页
        window.location.href = redirectURL || '/'
    }).catch(error => {
        // 重置状态
        commit('RESET_USER_STATE')
        window.location.href = redirectURL || '/'
    })
  },
  // 发送刷新令牌
  SendRefreshToken({ state, commit }) {
    return new Promise((resolve, reject) => {
      // 判断是否有刷新令牌
      if( !state.refreshToken ) {
        commit('RESET_USER_STATE')
        reject('没有刷新令牌')
        return 
      }
      // 发送请求
      refreshToken(state.refreshToken).then(response => {
        console.log('获取到的新认证令牌', response.data)
        // 更新用户状态新数据
        commit('SET_USER_STATE', response.data)
        resolve() // 正常钩子
      }).catch(error => {
        // 重置状态
        commit('RESET_USER_STATE')
        reject(error)
      })
    })
  },
}

export default {
  state,
  mutations,
  actions
}

5.设置cookie保存的域名(在 .env.development 和 .env.production文件)

//cookie保存的域名,utils/cookie.js 要用
VUE_APP_COOKIE_DOMAIN = 'location'

6.设置cookie.js

import Cookies from 'js-cookie'

// Cookie的key值
export const Key = {
  accessTokenKey: 'accessToken', // 访问令牌在cookie的key值 
  refreshTokenKey: 'refreshToken', // 刷新令牌在cookie的key值 
  userInfoKey: 'userInfo'
}
class CookieClass {
  constructor() {
    this.domain = process.env.VUE_APP_COOKIE_DOMAIN // 域名
    this.expireTime = 30 // 30 天
  }

  set(key, value, expires, path = '/') {
    CookieClass.checkKey(key);
    Cookies.set(key, value, {expires: expires || this.expireTime, path: path, domain: this.domain})
  }

  get(key) {
    CookieClass.checkKey(key)
    return Cookies.get(key)
  }

  remove(key, path = '/') {
    CookieClass.checkKey(key)
    Cookies.remove(key, {path: path, domain: this.domain})
  }

  geteAll() {
    Cookies.get();
  }

  static checkKey(key) {
    if (!key) {
      throw new Error('没有找到key。');
    }
    if (typeof key === 'object') {
      throw new Error('key不能是一个对象。');
    }
  }
}

// 导出
export const PcCookie =  new CookieClass()

提交登录触发 action

1.在登录页 login.vue 的 created 生命钩子里获取redirectURL,是引发跳转到登录页的引发跳转 URL ,登录成功后需要重定向回 redirectURL

  data () {
      return {
        redirectURL: 'http//www.baidu.com', // 登录成功后重写向地址
  },
  created() {
      if(this.$route.query.redirectURL) {
        this.redirectURL = this.$route.query.redirectURL
      }
  },

2.登录成功后,重定向回到redirectURL参数值对应的页面,如果不带 redirectURL 重写向到 http//www.baidu.com

单点退出系统

定义 Vuex 退出行为

单点登录自然也要单点注销,在一个子系统中注销,所有子系统的会话都将被销毁.

actions 对象中添加调用 logout 退出api方法。退出成功后回到登录页。

const actions = {

	UserLogout({ state, commit }, redirectURL) {
		// 调用退出接口, 上面不要忘记导入 logout 方法
		logout(state.accessToken).then(() => {
			// 重置状态
			commit('RESET_USER_STATE')
			// 退出后,重写向地址,如果没有传重写向到登录页 / window.location.href = redirectURL || '/'
		}).catch(() => {
			// 重置状态
			commit('RESET_USER_STATE') window.location.href = redirectURL || '/'
		})
	}

}

路由拦截器退出操作

router.beforeEach((to, from , next) => {
	if(to.path === '/logout') {
		// 退 出
		store.dispatch('UserLogout', to.query.redirectURL)
	}else {
		next()
	}
})

Vuex 发送请求与重置状态

actions 中 添加发送刷新令牌请求 行为

const actions = {

// 2. 发 送 刷 新 令 牌 ++++++++++++ SendRefreshToken({ state, commit }) {
return new Promise((resolve, reject) => {
	// 判断是否有刷新令牌
	if(!state.refreshToken) { commit('RESET_USER_STATE')
		reject('没有刷新令牌')
		return
	}
		// 发送刷新请求
	refreshToken(state.refreshToken).then(response => {
		// console.log('刷新令牌新数据', response)
		// 更新用户状态新数据
		commit('SET_USER_STATE', response.data) resolve() // 正常响应钩子
		}).catch(error => {
		// 重置状态commit('RESET_USER_STATE') reject(error)
		})
	})
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值