Authorization

This commit is contained in:
Maxim Filippov 2019-02-22 22:38:56 +03:00
parent 83fc9e6aa8
commit 96e52a14cc
11 changed files with 67 additions and 153 deletions

View file

@ -57,7 +57,7 @@ const devWebpackConfig = merge(baseWebpackConfig, {
template: 'index.html', template: 'index.html',
inject: true, inject: true,
favicon: resolve('favicon.ico'), favicon: resolve('favicon.ico'),
title: 'vue-element-admin', title: 'Admin FE',
templateParameters: { templateParameters: {
BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory, BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory,
}, },

View file

@ -55,7 +55,7 @@ const webpackConfig = merge(baseWebpackConfig, {
template: 'index.html', template: 'index.html',
inject: true, inject: true,
favicon: resolve('favicon.ico'), favicon: resolve('favicon.ico'),
title: 'vue-element-admin', title: 'Admin FE',
templateParameters: { templateParameters: {
BASE_URL: config.build.assetsPublicPath + config.build.assetsSubDirectory, BASE_URL: config.build.assetsPublicPath + config.build.assetsSubDirectory,
}, },

View file

@ -1,5 +1,5 @@
module.exports = { module.exports = {
NODE_ENV: '"development"', NODE_ENV: '"development"',
ENV_CONFIG: '"dev"', ENV_CONFIG: '"dev"',
BASE_API: '"https://api-dev"' BASE_API: '"http://localhost:4000"'
} }

View file

@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit"> <meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>vue-element-admin</title> <title>Admin FE</title>
</head> </head>
<body> <body>
<script src=<%= BASE_URL %>/tinymce4.7.5/tinymce.min.js></script> <script src=<%= BASE_URL %>/tinymce4.7.5/tinymce.min.js></script>

View file

@ -1,29 +1,42 @@
import request from '@/utils/request' import request from '@/utils/request'
export function loginByUsername(username, password) { export async function loginByUsername(username, password) {
const data = { const appsRequest = await request({
username, url: '/api/v1/apps',
password
}
return request({
url: '/login/login',
method: 'post', method: 'post',
data data: {
client_name: `AdminFE_${Math.random()}`,
redirect_uris: `${window.location.origin}/oauth-callback`,
scopes: 'read write follow'
}
})
const app = appsRequest.data
return request({
url: '/oauth/token',
method: 'post',
data: {
client_id: app.client_id,
client_secret: app.client_secret,
grant_type: 'password',
username: username,
password: password
}
}) })
} }
export function logout() { export function getUserInfo() {
return request({ return request({
url: '/login/logout', url: '/api/account/verify_credentials',
method: 'post' method: 'post'
}) })
} }
export function getUserInfo(token) { export function logout() {
return request({
url: '/user/info',
method: 'get',
params: { token }
})
} }
const oauth = { loginByUsername, getUserInfo, logout }
export default oauth

View file

@ -63,7 +63,8 @@ export default {
theme: 'Theme', theme: 'Theme',
clipboardDemo: 'Clipboard', clipboardDemo: 'Clipboard',
i18n: 'I18n', i18n: 'I18n',
externalLink: 'External Link' externalLink: 'External Link',
users: 'Users'
}, },
navbar: { navbar: {
logOut: 'Log Out', logOut: 'Log Out',

View file

@ -26,7 +26,7 @@ router.beforeEach((to, from, next) => {
} else { } else {
if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息 if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
store.dispatch('GetUserInfo').then(res => { // 拉取user_info store.dispatch('GetUserInfo').then(res => { // 拉取user_info
const roles = res.data.roles // note: roles must be a array! such as: ['editor','develop'] const roles = res.data.rights.admin ? ['admin'] : []
store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表 store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表
router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表 router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record

View file

@ -76,32 +76,6 @@ export const constantRouterMap = [
meta: { title: 'dashboard', icon: 'dashboard', noCache: true, affix: true } meta: { title: 'dashboard', icon: 'dashboard', noCache: true, affix: true }
} }
] ]
},
{
path: '/documentation',
component: Layout,
redirect: '/documentation/index',
children: [
{
path: 'index',
component: () => import('@/views/documentation/index'),
name: 'Documentation',
meta: { title: 'documentation', icon: 'documentation', affix: true }
}
]
},
{
path: '/guide',
component: Layout,
redirect: '/guide/index',
children: [
{
path: 'index',
component: () => import('@/views/guide/index'),
name: 'Guide',
meta: { title: 'guide', icon: 'guide', noCache: true }
}
]
} }
] ]
@ -112,6 +86,18 @@ export default new Router({
}) })
export const asyncRouterMap = [ export const asyncRouterMap = [
{
path: '/users',
component: Layout,
children: [
{
path: 'index',
component: () => import('@/views/users/index'),
name: 'Users',
meta: { title: 'users', icon: 'peoples', noCache: true }
}
]
},
{ {
path: '/permission', path: '/permission',
component: Layout, component: Layout,

View file

@ -44,14 +44,13 @@ const user = {
}, },
actions: { actions: {
// 用户名登录
LoginByUsername({ commit }, userInfo) { LoginByUsername({ commit }, userInfo) {
const username = userInfo.username.trim() const username = userInfo.username.trim()
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
loginByUsername(username, userInfo.password).then(response => { loginByUsername(username, userInfo.password).then(response => {
const data = response.data const data = response.data
commit('SET_TOKEN', data.token) commit('SET_TOKEN', data.access_token)
setToken(response.data.token) setToken(response.data.access_token)
resolve() resolve()
}).catch(error => { }).catch(error => {
reject(error) reject(error)
@ -59,25 +58,25 @@ const user = {
}) })
}, },
// 获取用户信息
GetUserInfo({ commit, state }) { GetUserInfo({ commit, state }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getUserInfo(state.token).then(response => { getUserInfo(state.token).then(response => {
// 由于mockjs 不支持自定义状态码只能这样hack
if (!response.data) { if (!response.data) {
reject('Verification failed, please login again.') reject('Verification failed, please login again.')
} }
const data = response.data const data = response.data
if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组 if (data.rights) {
commit('SET_ROLES', data.roles) if (data.rights.admin) {
commit('SET_ROLES', ['admin'])
}
} else { } else {
reject('getInfo: roles must be a non-null array!') reject('getInfo: roles must be a non-null array!')
} }
commit('SET_NAME', data.name) commit('SET_NAME', data.name)
commit('SET_AVATAR', data.avatar) commit('SET_AVATAR', data.profile_image_url)
commit('SET_INTRODUCTION', data.introduction) commit('SET_INTRODUCTION', '')
resolve(response) resolve(response)
}).catch(error => { }).catch(error => {
reject(error) reject(error)

View file

@ -12,10 +12,8 @@ const service = axios.create({
// request interceptor // request interceptor
service.interceptors.request.use( service.interceptors.request.use(
config => { config => {
// Do something before request is sent
if (store.getters.token) { if (store.getters.token) {
// 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改 config.headers['Authorization'] = `Bearer ${getToken()}`
config.headers['X-Token'] = getToken()
} }
return config return config
}, },

View file

@ -1,11 +1,10 @@
<template> <template>
<div class="login-container"> <div class="login-container">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left"> <el-form ref="loginForm" :model="loginForm" class="login-form" auto-complete="on" label-position="left">
<div class="title-container"> <div class="title-container">
<h3 class="title"> <h3 class="title">
{{ $t('login.title') }} {{ $t('login.title') }}
</h3> </h3>
<lang-select class="set-language" />
</div> </div>
<el-form-item prop="username"> <el-form-item prop="username">
@ -41,66 +40,18 @@
<el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin"> <el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">
{{ $t('login.logIn') }} {{ $t('login.logIn') }}
</el-button> </el-button>
<div style="position:relative">
<div class="tips">
<span>{{ $t('login.username') }} : admin</span>
<span>{{ $t('login.password') }} : {{ $t('login.any') }}</span>
</div>
<div class="tips">
<span style="margin-right:18px;">
{{ $t('login.username') }} : editor
</span>
<span>{{ $t('login.password') }} : {{ $t('login.any') }}</span>
</div>
<el-button class="thirdparty-button" type="primary" @click="showDialog=true">
{{ $t('login.thirdparty') }}
</el-button>
</div>
</el-form> </el-form>
<el-dialog :title="$t('login.thirdparty')" :visible.sync="showDialog">
{{ $t('login.thirdpartyTips') }}
<br>
<br>
<br>
<social-sign />
</el-dialog>
</div> </div>
</template> </template>
<script> <script>
import { isvalidUsername } from '@/utils/validate'
import LangSelect from '@/components/LangSelect'
import SocialSign from './socialsignin'
export default { export default {
name: 'Login', name: 'Login',
components: { LangSelect, SocialSign }, data: function() {
data() {
const validateUsername = (rule, value, callback) => {
if (!isvalidUsername(value)) {
callback(new Error('Please enter the correct user name'))
} else {
callback()
}
}
const validatePassword = (rule, value, callback) => {
if (value.length < 6) {
callback(new Error('The password can not be less than 6 digits'))
} else {
callback()
}
}
return { return {
loginForm: { loginForm: {
username: 'admin', username: '',
password: '1111111' password: ''
},
loginRules: {
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
}, },
passwordType: 'password', passwordType: 'password',
loading: false, loading: false,
@ -116,12 +67,6 @@ export default {
immediate: true immediate: true
} }
}, },
created() {
// window.addEventListener('hashchange', this.afterQRScan)
},
destroyed() {
// window.removeEventListener('hashchange', this.afterQRScan)
},
methods: { methods: {
showPwd() { showPwd() {
if (this.passwordType === 'password') { if (this.passwordType === 'password') {
@ -131,8 +76,6 @@ export default {
} }
}, },
handleLogin() { handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true this.loading = true
this.$store.dispatch('LoginByUsername', this.loginForm).then(() => { this.$store.dispatch('LoginByUsername', this.loginForm).then(() => {
this.loading = false this.loading = false
@ -140,38 +83,12 @@ export default {
}).catch(() => { }).catch(() => {
this.loading = false this.loading = false
}) })
} else {
console.log('error submit!!')
return false
}
})
},
afterQRScan() {
// const hash = window.location.hash.slice(1)
// const hashObj = getQueryObject(hash)
// const originUrl = window.location.origin
// history.replaceState({}, '', originUrl)
// const codeMap = {
// wechat: 'code',
// tencent: 'code'
// }
// const codeName = hashObj[codeMap[this.auth_type]]
// if (!codeName) {
// alert('')
// } else {
// this.$store.dispatch('LoginByThirdparty', codeName).then(() => {
// this.$router.push({ path: '/' })
// })
// }
} }
} }
} }
</script> </script>
<style rel="stylesheet/scss" lang="scss"> <style rel="stylesheet/scss" lang="scss">
/* 修复input 背景不协调 和光标变色 */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
$bg:#283443; $bg:#283443;
$light_gray:#eee; $light_gray:#eee;
$cursor: #fff; $cursor: #fff;