有点复杂有点长,一定要耐心看,看完之后你会对单点登录有个神奇的看法。
你能学到的知识:1. layout布局 2.加载路由视图 3.封装路由以及路由拦截器使用 4.封装store auth验证模块 5.vue-router、vuex、js-cookie应用 6.什么叫sso单点登录
一、先介绍一下什么是单点登录?了解的同学可以直接跳过了~~~
1.为什么要用sso单点登录?
很多企业或者公司一开始的时候项目没有那么多,或许只有一两个,所以各自有各自的登录平台没啥关系,但是一旦到达一定规模后,单点登录显得尤为重要。在这里 举个栗子:淘宝、天猫、阿里云。一开始淘宝也是在本系统内进行登录注册的,但是当有了天猫,有了阿里巴巴之后,你登陆一个平台之后,另外几个平台再打开的时候会自动获取客户端存储的信息,然后验证是否登录,也就实现了单点登录。至于淘宝信息存在哪,有兴趣的可以自己去看看,这里不再赘述。
对了 这里有个小插曲,单点登录并不是说 两个网页同一个网站,这边登录后那边被顶下去了。这一定要注意~~~
2.单点登录的应用场景?
多个平台共用同一个用户系统,实现一端登陆后其余平台均可以拿到用户信息。
二、安装需要用到的插件,开始实际的代码操作
安装三个插件:npm install --save vue-router vuex js-cookie 安装完之后在项目里的package.json文件中有这三个插件就说明安装成功了
vue-router 应用vue的路由器
vuex 封装store,暴露模块
js-cookie 存浏览器cookie,获取cookie然后登录信息
三、详细介绍
跟紧党的 jue 步,要开始一步一步的操作了
1. 首先是 layout布局:我习惯把layout文件夹放在组件components文件夹内,然后封装layout组件,引用路由组件,完成项目整体架构
在这里我新建了个layout文件夹以及下面的AppHeader和AppFooter文件夹,每个文件夹内新建了个index.vue文件,其实不难理解,就是布局的头部,底部以及整体的布局引用文件。
(1)AppHeader/index.vue && AppFooter/index.vue 这两个组件很简单,就是头部和底部代码,都是静态的。这里有个小细节点:<style scoped> 这个style标签内有个scoped属性,这个属性的意思是:里面的css只作用于当前的模板文件,也就是就算其他的组件有重名的class也不会有冲突。
(2)layout/index.vue文件,引入上面封装的两个组件,然后在头和脚中间 引用 rooter-view 组件
(3)在项目唯一暴露文件App.vue中引用route-view路由组件:这时候布局并不能出现,并且还会报错,别着急 继续往下走
2.加载路由视图:其实上面的router-view已经是加载路由视图了,只需要在组件中用router-view标签就可以
3.封装路由,以便让视图可以展示。这个时候前端就能展示出来了,如果还不显示的话,要看下你的代码是否编辑正确了,然后再根据实际报错实际分析,分析不出来的话就留言问我,我帮你分析。router/index.js
import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) const router = new Router({ mode: 'history', // 定义路由地址前不带 # routes: [ { path: '/', component: () => import('@/components/layout'), // 必须写import导入,不然就是个字符串,不识别 children: [ { path: '', component: () => import('@/views/auth/login') // 因为auth下面不是index.vue所以login不能省略 } ] } ] }) // 引入store 执行里面的userLogout方法 import store from '@/store' //路由拦截器 router.beforeEach((to, from, next) => { if (to.path === '/logout') { store.dispatch('userLogout', to.query.redirectURL) // 执行store里面的方法用dispatch(func, params) } else { next() } }) export default router4.页面完事了就要开始写功能了。
编写顺序:(1)编写api----(2)封装store----(3)模板引用方法做处理 ,因为这里涉及的代码较多,所以不再截图,直接暴露我写的源码,源码中都有注释,哪里不懂的再留言问我。(题外话:其实我特别不想暴露源码,这样你们就不自己编写了,我不想让你变成一个ctrl+c & ctrl+v的程序员,所以如果不会写,尽量抄我的,不要直接复制,因为复制你还是不懂什么意思。)
(1)编写api 对了api请求接口也是做了转发,在我个人博客vue-element-admin初识中有介绍,不再多说,感兴趣的去看那篇文章。sso单点登录的项目请求地址: 也是mockjs模拟的数据。
这里的请求头要询问后台开发人员是否需要,不需要的话就无所谓了。下面是登录接口
import request from '@/utils/request' const header = {'Content-type': 'application/x-www-form-urlencode'} //请求头添加 Authorization: Basic client_id:client_secret const auth = { username: 'xzec-sso', password: '123456' } // 登录接口 export function login(data) { return request({ header, auth, url: '/auth/login', method: 'post', params: data }) } // 查询用户名是否已经存在 export function getByUsername(username) { return request({ url: `/system/api/user/username/${username}`, method: 'get' }) } // 注册 export function register(data) { return request({ url: '/system/api/user/register', method: 'post', data }) } // 获取注册协议,本地html export function getXieyi() { return request({ url: `${window.location.href}/xieyi.html`, method: 'get' }) } // 退出 export function logout(accessToken) { return request({ url: '/auth/logout', method: 'get', params: {accessToken} }) }(2)封装store
store/index.js很简单,引用vuex插件,在module中暴露封装的auth.js;两个文件的位置:store/index.js、store/modules/auth.js
index.js
import Vue from 'vue' import Vuex from 'vuex' import auth from './modules/auth' Vue.use(Vuex) const store = new Vuex.Store({ modules: { auth } }) export default storeauth.js
import { login, logout } from '@/api/auth' import { PcCookie, Key } from '@/utils/cookie' // 定义状态,state 必须是 func 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状态赋值 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状态置空 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 }, userInputData) { const { username, password } = userInputData return new Promise((resolve, reject) => { login({ username: username.trim(), password:password.trim() }).then(res => { const { code, data } = res if (code === 20000) { commit('SET_USER_STATE', data) } resolve(res) //成功时返回resolve }).catch(error => { commit('RESET_USER_STATE') reject(error) // 失败时返回reject }) }) }, userLogout({state, commit}, redirectURL) { logout(state.accessToken).then(res => { commit('RESET_USER_STATE') window.location.href = redirectURL || '/' }).catch(error => { commit('RESET_USER_STATE') window.location.href = redirectURL || '/' }) } } export default { state, mutations, actions }(3)模板引用 views/auth/login.vue
<template> <div class="login_page"> <div class="login_box"> <div class="center_box"> <!-- 登录&注册--> <div :class="{login_form: true, rotate: tab == 2}"> <div :class="{tabs: true, r180: reverse == 2}"> <div class="fl tab" @click="changetab(1)"> <span :class="{on: tab == 1}">登录</span> </div> <div class="fl tab" @click="changetab(2)"> <span :class="{on: tab == 2}">注册</span> </div> </div> <!-- 登录 --> <div class="form_body" v-if="reverse == 1"> <!-- submit.prevent 阻止默认表单事件提交,采用loginSubmit --> <form @submit.prevent="loginSubmit"> <input type="text" v-model="loginData.username" placeholder="请输入用户名" autocomplete="off"> <input type="password" v-model="loginData.password" placeholder="请输入密码" autocomplete="off"> <div class="error_msg">{{loginMessage}}</div> <input type="submit" v-if="subState" disabled="disabled" value="登录中···" class="btn" /> <input type="submit" v-else value="登录" @submit="loginSubmit" class="btn" /> </form> </div> <!-- 注册 --> <div class="form_body r180" v-if="reverse == 2"> <form @submit.prevent="regSubmit"> <input type="text" v-model="registerData.username" placeholder="请输入用户名" autocomplete="off"> <input type="password" v-model="registerData.password" placeholder="6-30位密码,可用数字/字母/符号组合" autocomplete="off"> <input type="password" v-model="registerData.repassword" placeholder="确认密码" > <div class="error_msg">{{regMessage}}</div> <div class="agree"> <input type="checkbox" id="tonyi" v-model="registerData.check"> <label for="tonyi">我已经阅读并同意</label><a href="jvascript:;" @click="xieyi = true">《用户协议》</a> </div> <input type="submit" v-if="subState" disabled="disabled" value="提交中···" class="btn"> <input type="submit" v-else value="注册" class="btn"> </form> </div> </div> </div> </div> <!-- 用户协议 --> <div class="xieyi" v-if="xieyi" @click.self="xieyi = false"> <div class="xieyi_content"> <div class="xieyi_title">请认真阅读用户协议</div> <div class="xieyi_body" v-if="xieyiContent" v-html="xieyiContent"> </div> <input type="button" class="xieyi_btn" value="确定" @click="xieyi = false"> </div> </div> </div> </template> <script > import { isvalidUsername } from '@/utils/validate' import { getByUsername, register, getXieyi } from '@/api/auth' export default { data () { return { tab: 1, // 高亮当前标签名 reverse: 1, // 旋转 1 登录,2 注册 loginMessage: '', //登录错误提示信息 regMessage: '', //注册错误提示信息 subState: false, //提交状态 xieyi: false, // 显示隐藏协议内容 xieyiContent: null, // 协议内容 redirectURL: '//www.xzec.com', // 登录成功后重写向地址 loginData: { // 登录表单数据 username: '', password: '' }, registerData: { // 注册表单数据 username: '', password: '', repassword: '', check: false }, } }, async created() { if(this.$route.query.redirectURL) { this.redirectURL = this.$route.query.redirectURL } this.xieyiContent = await getXieyi() }, methods: { // 切换标签 changetab (int) { this.tab = int; let _that = this; setTimeout(() => { this.reverse = int }, 200) }, // 提交登录 loginSubmit() { // 如果在登录中,不允许登录 if(this.subState) { return false } // 校验用户名密码 if(!isvalidUsername(this.loginData.username)) { this.loginMessage = '用户名不正确,不得少4位' return false } if (this.loginData.password.length < 6) { this.loginMessage = '密码不得少于6位' return false } // 提交中状态 this.subState = true // 执行登录 this.$store.dispatch('userLogin', this.loginData).then(res => { const { code, message } = res if (code === 20000) { window.location.href = this.redirectURL } else { this.loginMessage = message } this.subState = false this.loginData.username = null this.loginData.password = null }).catch(error => { this.loginMessage = '系统繁忙,请稍候再试' this.subState = flase }) }, // 提交注册 async regSubmit() { // 判断是否提交中 if (this.subState) { return false } // 验证用户名 if ( !isvalidUsername(this.registerData.username) ) { this.regMessage = '用户名不正确,不得少4位' return false } // 验证用户名是否已经存在 const { code, data, message} = await getByUsername(this.registerData.username) if(code !== 20000) { this.regMessage = message return false } else if (data) { this.regMessage = '用户名已经存在,请更换' return false } // 验证密码 if (this.registerData.password.length < 6 || this.registerData.password.length > 30) { this.regMessage = '密码在6-30位之间不可有空格' return false } if (this.registerData.password !== this.registerData.repassword) { this.regMessage = '两次输入密码不一致' return false } //是否勾选协议 if (!this.registerData.check) { this.regMessage = '请阅读并同意用户协议' return false } this.subState = true const ress = await register(this.registerData) if (ress.code !== 20000) { this.regMessage = ress.message } else { this.subState = false this.changetab(1) this.registerData.username = null this.registerData.password = null this.registerData.repassword = null this.registerData.check = false } } }, } </script> <style scoped> @import '../../assets/style/login.css'; </style> 截至上面,你已经完成了sso单点登录、注册、退出的所有功能了。注意:登录和注册在组件中触发,而退出则直接在路由中触发,也就是你请求地址比如:www.xx.com/logout?redirectURL= 会自动匹配/logout 路由进行退出。
小结:utils里面的三个文件,我给你们暴露一下,有兴趣的可以研究一下,没兴趣的可以直接使用。包含cookie.js、request.js、validate.js
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 = 15 // 15 天 } 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()request.js 封装了一个axios
import axios from 'axios' const service = axios.create({ // .env.development 和 .env.productiont baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url timeout: 10000 // request timeout }) // 请求拦截器 service.interceptors.request.use( config => { return config }, error => { return Promise.reject(error) } ) // 响应拦截器 service.interceptors.response.use( response => { // 正常响应 const res = response.data return res }, error => { // 响应异常 return Promise.reject(error) } ) export default servicevalidate.js
// 校验用户名是否合法 只允许4-30位中文、数字、字母和下划线 export function isvalidUsername(str) { const valid_map = /^[a-zA-Z0-9_\u4e00-\u9fa5]{4,30}$/ return valid_map.test(str) } // 校验手机号是否合法 export function isvalidMobile(str) { const valid_map = 11 && /^1(3|4|5|6|7|8|9)\d{9}$/ return valid_map.test(str) } // 校验邮箱是否合法 export function isvalidEmail(str) { const valid_map = /^[A-Za-z0-9_.-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/ return valid_map.test(str) } /* 合法uri*/ export function validateURL(textval) { const urlregex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/ return urlregex.test(textval) }四、注意事项
1.千万不要直接复制我的代码,就算不会也要手写一遍,里面不理解的要多查多问多看。
2.细节很重要,要学会分析问题,找到问题,解决问题
3.promise 成功用什么失败用什么一定要区分清楚
4.在用户行为里面为什么用commit执行定义的方法
5.路由拦截beforeEach里面的三个参数分别是什么?
6.为什么路由component组件用import引入
7.不会的一定要多查多问,不要怕丢人,因为谁一开始都是不会的。
全部评论