zcs*_*rei 5 javascript authentication jwt vue.js nuxt.js
我花了一个晚上寻找这个问题的解决方案,似乎很多人都有它,最好的建议通常是“切换到 SPA 模式”,这对我来说不是一个选择。
我有 JWT 进行身份验证,使用JWTSessionsRails的gem。
在前端,我有 Nuxt with nuxt-auth,使用自定义方案,以及以下授权中间件:
export default function ({ $auth, route, redirect }) {
const role = $auth.user && $auth.user.role
if (route.meta[0].requiredRole !== role) {
redirect('/login')
}
}
Run Code Online (Sandbox Code Playgroud)
我的症状如下:如果我登录并浏览受限页面,一切都会按预期进行。我什至有fetchOnServer: false限制页面,因为我只需要公共页面的 SSR。
但是,一旦我刷新页面或直接导航到受限制的 URL,我就会立即被中间件重定向到登录页面。显然,在客户端通过身份验证的用户也不会在服务器端进行身份验证。
我有以下相关文件。
nuxt.config.js
...
plugins: [
// ...
{ src: '~/plugins/axios' },
// ...
],
// ...
modules: [
'cookie-universal-nuxt',
'@nuxtjs/axios',
'@nuxtjs/auth'
],
// ...
axios: {
baseURL: process.env.NODE_ENV === 'production' ? 'https://api.example.com/v1' : 'http://localhost:3000/v1',
credentials: true
},
auth: {
strategies: {
jwtSessions: {
_scheme: '~/plugins/auth-jwt-scheme.js',
endpoints: {
login: { url: '/signin', method: 'post', propertyName: 'csrf' },
logout: { url: '/signin', method: 'delete' },
user: { url: '/users/active', method: 'get', propertyName: false }
},
tokenRequired: true,
tokenType: false
}
},
cookie: {
options: {
maxAge: 64800,
secure: process.env.NODE_ENV === 'production'
}
}
},
Run Code Online (Sandbox Code Playgroud)
auth-jwt-scheme.js
const tokenOptions = {
tokenRequired: true,
tokenType: false,
globalToken: true,
tokenName: 'X-CSRF-TOKEN'
}
export default class LocalScheme {
constructor (auth, options) {
this.$auth = auth
this.name = options._name
this.options = Object.assign({}, tokenOptions, options)
}
_setToken (token) {
if (this.options.globalToken) {
this.$auth.ctx.app.$axios.setHeader(this.options.tokenName, token)
}
}
_clearToken () {
if (this.options.globalToken) {
this.$auth.ctx.app.$axios.setHeader(this.options.tokenName, false)
this.$auth.ctx.app.$axios.setHeader('Authorization', false)
}
}
mounted () {
if (this.options.tokenRequired) {
const token = this.$auth.syncToken(this.name)
this._setToken(token)
}
return this.$auth.fetchUserOnce()
}
async login (endpoint) {
if (!this.options.endpoints.login) {
return
}
await this._logoutLocally()
const result = await this.$auth.request(
endpoint,
this.options.endpoints.login
)
if (this.options.tokenRequired) {
const token = this.options.tokenType
? this.options.tokenType + ' ' + result
: result
this.$auth.setToken(this.name, token)
this._setToken(token)
}
return this.fetchUser()
}
async setUserToken (tokenValue) {
await this._logoutLocally()
if (this.options.tokenRequired) {
const token = this.options.tokenType
? this.options.tokenType + ' ' + tokenValue
: tokenValue
this.$auth.setToken(this.name, token)
this._setToken(token)
}
return this.fetchUser()
}
async fetchUser (endpoint) {
if (this.options.tokenRequired && !this.$auth.getToken(this.name)) {
return
}
if (!this.options.endpoints.user) {
this.$auth.setUser({})
return
}
const user = await this.$auth.requestWith(
this.name,
endpoint,
this.options.endpoints.user
)
this.$auth.setUser(user)
}
async logout (endpoint) {
if (this.options.endpoints.logout) {
await this.$auth
.requestWith(this.name, endpoint, this.options.endpoints.logout)
.catch(() => {})
}
return this._logoutLocally()
}
async _logoutLocally () {
if (this.options.tokenRequired) {
this._clearToken()
}
return await this.$auth.reset()
}
}
Run Code Online (Sandbox Code Playgroud)
axios.js
export default function (context) {
const { app, $axios, redirect } = context
$axios.onResponseError(async (error) => {
const response = error.response
const originalRequest = response.config
const access = app.$cookies.get('jwt_access')
const csrf = originalRequest.headers['X-CSRF-TOKEN']
const credentialed = (process.client && csrf) || (process.server && access)
if (credentialed && response.status === 401 && !originalRequest.headers.REFRESH) {
if (process.server) {
$axios.setHeader('X-CSRF-TOKEN', csrf)
$axios.setHeader('Authorization', access)
}
const newToken = await $axios.post('/refresh', {}, { headers: { REFRESH: true } })
if (newToken.data.csrf) {
$axios.setHeader('X-CSRF-TOKEN', newToken.data.csrf)
$axios.setHeader('Authorization', newToken.data.access)
if (app.$auth) {
app.$auth.setToken('jwt_access', newToken.data.csrf)
app.$auth.syncToken('jwt_access')
}
originalRequest.headers['X-CSRF-TOKEN'] = newToken.data.csrf
originalRequest.headers.Authorization = newToken.data.access
if (process.server) {
app.$cookies.set('jwt_access', newToken.data.access, { path: '/', httpOnly: true, maxAge: 64800, secure: false, overwrite: true })
}
return $axios(originalRequest)
} else {
if (app.$auth) {
app.$auth.logout()
}
redirect(301, '/login')
}
} else {
return Promise.reject(error)
}
})
}
Run Code Online (Sandbox Code Playgroud)
这个解决方案已经受到了其他线程下可用材料的极大启发,在这一点上,我对如何在 Nuxt 上普遍验证我的用户一无所知。非常感谢任何帮助和指导。
为了使您不丢失系统中的身份验证会话,您首先需要将 JWT 令牌保存到客户端上的某个存储中:localStorage 或 sessionStorage 或以及令牌数据可以保存在 cookie 中。
为了使应用程序能够最佳地工作,您还需要将令牌保存在 Nuxt 的存储中。(Vuex)
如果您仅将令牌保存在Nuxt的srore中并仅使用状态,则每次刷新页面时,您的令牌将重置为零,因为状态将没有时间初始化。因此,您将被重定向到页面/login。
为了防止这种情况发生,在将令牌保存到某个存储后,您需要读取它并在特殊方法nuxtServerInit()中重新初始化它,在通用模式下,他将首先在服务器端工作。(Nuxt2)
然后,相应地,您在向 api 服务器发送请求时使用您的令牌,向每个需要授权的请求添加授权类型的标头。
由于您的问题特定于 Nuxt2 版本,因此对于此版本,使用 cookie 存储令牌的工作代码示例将是:
/store/auth.js
import jwtDecode from 'jwt-decode'
export const state = () => ({
token: null
})
export const getters = {
isAuthenticated: state => Boolean(state.token),
token: state => state.token
}
export const mutations = {
SET_TOKEN (state, token) {
state.token = token
}
}
export const actions = {
autoLogin ({ dispatch }) {
const token = this.$cookies.get('jwt-token')
if (isJWTValid(token)) {
dispatch('setToken', token)
} else {
dispatch('logout')
}
},
async login ({ commit, dispatch }, formData) {
const { token } = await this.$axios.$post('/api/auth/login', formData, { progress: false })
dispatch('setToken', token)
},
logout ({ commit }) {
this.$axios.setToken(false)
commit('SET_TOKEN', null)
this.$cookies.remove('jwt-token')
},
setToken ({ commit }, token) {
this.$axios.setToken(token, 'Bearer')
commit('SET_TOKEN', token)
this.$cookies.set('jwt-token', token, { path: '/', expires: new Date('2024') })
// <-- above use, for example, moment or add function that will computed date
}
}
/**
* Check valid JWT token.
*
* @param token
* @returns {boolean}
*/
function isJWTValid (token) {
if (!token) {
return false
}
const jwtData = jwtDecode(token) || {}
const expires = jwtData.exp || 0
return new Date().getTime() / 1000 < expires
}
Run Code Online (Sandbox Code Playgroud)
/store/index.js
export const state = () => ({
// ... Your state here
})
export const getters = {
// ... Your getters here
}
export const mutations = {
// ... Your mutations here
}
export const actions = {
nuxtServerInit ({ dispatch }) { // <-- init auth
dispatch('auth/autoLogin')
}
}
Run Code Online (Sandbox Code Playgroud)
/middleware/isGuest.js
export default function ({ store, redirect }) {
if (store.getters['auth/isAuthenticated']) {
redirect('/admin')
}
}
Run Code Online (Sandbox Code Playgroud)
/中间件/auth.js
export default function ({ store, redirect }) {
if (!store.getters['auth/isAuthenticated']) {
redirect('/login')
}
}
Run Code Online (Sandbox Code Playgroud)
/pages/login.vue
<template>
<div>
<!-- Your template here-->
</div>
</template>
<script>
export default {
name: 'Login',
layout: 'empty',
middleware: ['isGuest'], // <-- if the user is authorized, then he should not have access to the page !!!
data () {
return {
controls: {
login: '',
password: ''
},
rules: {
login: [
{ required: true, message: 'login is required', trigger: 'blur' }
],
password: [
{ required: true, message: 'password is required', trigger: 'blur' },
{ min: 6, message: 'minimum 6 length', trigger: 'blur' }
]
}
}
},
head: {
title: 'Login'
},
methods: {
onSubmit () {
this.$refs.form.validate(async (valid) => { // <-- Your validate
if (valid) {
// here for example: on loader
try {
await this.$store.dispatch('auth/login', {
login: this.controls.login,
password: this.controls.password
})
await this.$router.push('/admin')
} catch (e) {
// eslint-disable-next-line no-console
console.error(e)
} finally {
// here for example: off loader
}
}
})
}
}
}
</script>
Run Code Online (Sandbox Code Playgroud)
!- 您必须安装以下软件包:
我想你会发现我的回答很有帮助。如果有什么不清楚,请询问!
| 归档时间: |
|
| 查看次数: |
1270 次 |
| 最近记录: |