前后端分离项目token怎么验证_前后端分离项目——登录Token校验思路

本文介绍了在前后端分离项目中实现登录Token验证的思路。前端在登录成功后将token存入sessionStorage,并在请求时放入header。后端通过拦截器检查token的有效性,若无效则拒绝请求。此外,前端对特定路由进行登录状态判断,以实现页面级别的权限控制。
摘要由CSDN通过智能技术生成

前言

根据token校验当前用户登录状态是Web项目的常见手段,我给自己的项目做token校验功能时,发现网上很多文章代码高度相似,实现的思路也差不多(基本都是前端校验后从router入手去做页面拦截)。所以想自己写一篇文章记录一下自己实现的思路,实现功能的前提在于需求,希望能够给相关开发人员一个参考。

需求思考

对token的校验分为前端和后端

对后台来说,并不是所有的请求都需要用户登录后才可以执行,所以需要后台去鉴别需要拦截的需求,用户是否满足已登录的状态。

对前端来说,也并不是用户没有登录,就一定不允许访问某个页面,可能只是说不允许访问某个页面的某些功能而已。所以使用router进行登录校验的时候,不要一棒子打死,最好是先确定好想要实现什么样的功能,再去设计代码。

代码实现思路

用户前端登录成功,后台将用户的唯一token存入redis(有效期30min)中,并返回给前端

前端接收到token后,将其存放到session缓存,每次发起请求时将token封装到请求头head中

后台根据token是否有效,无效则拒绝请求

前端返回结果

一、环境介绍

前端: Vue-Cli 2.x + axios

后端:SpringBoot 2.3.4

二、前端代码

1、成功登录后回调函数封装用户token和userId(为什么要传userId后面会说)

image.png

2、token和userId放在全局参数store中

const user = {

state: {

userId: '',

userToken: '', // 用户token,用户确认当前用户是否登录

},

getters: {

userId: state => {

let userId = state.userId;

if(!userId){

userId = JSON.parse(window.sessionStorage.getItem('userId'));

}

return userId;

},

userToken: state => {

let userToken = state.userToken;

if(!userToken){

userToken = JSON.parse(window.sessionStorage.getItem('userToken'));

}

return userToken;

},

},

mutations: {

setUserId: (state,userId) => {

state.userId = userId;

window.sessionStorage.setItem('userId',JSON.stringify(userId));

},

setUserToken: (state,userToken) => {

state.userToken = userToken;

window.sessionStorage.setItem('userToken',JSON.stringify(userToken));

},

}

}

export default user;

这里的话,userToken和userId放到sessionStorage是关键步骤

3、使用 axios.interceptors.request.use对axios的请求进行统一拦截,封装token和userId

import axios from 'axios';

import router from '../router';

// 设置请求拦截器

axios.interceptors.request.use(function (config) {

// Do something before request is sent

//window.localStorage.getItem("accessToken") 获取token的value

let token = JSON.parse(window.sessionStorage.getItem('userToken'));

let userId = JSON.parse(window.sessionStorage.getItem('userId'));

if (token && userId) {

//将token放到请求头发送给服务器,将tokenkey放在请求头中

console.log(token);

console.log(userId);

config.headers.userId = userId;

config.headers.userToken = token;

//也可以这种写法

// config.headers['accessToken'] = token;

}

return config;

}, function (error) {

// Do something with request error

return Promise.reject(error);

});

三、后端

后端主要是使用拦截器来进行请求的拦截和校验

1、定义拦截器

package com.qiqv.music.controller.interceptor;

import com.qiqv.music.utils.JSONUtils;

import com.qiqv.music.utils.QiqvJSONResult;

import com.qiqv.music.utils.RedisOperator;

import org.apache.commons.lang3.StringUtils;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

import java.io.OutputStream;

/**

* 自定义拦截器类

*/

public class MiniInterceptor implements HandlerInterceptor {

@Autowired

private RedisOperator redisOperator;

// token规则为 user-reids-token:userId : UUID

private static String USER_REDIS_TOKEN = "user-redis-token";

/**

* 判断用户是否登录

* 若用户userId不存在,则为未登录

* 若用户userId存在,则判断token是否存在

* 若存在,则用户状态为已登录

* 若不存在,则用户状态为登录超时

* @param request

* @param response

* @param handler

* @return

* @throws Exception

*

*/

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

// 如果是 嗅探请求,则直接放行

if("OPTIONS".equals(request.getMethod())){

return true;

}

String userId = request.getHeader("userId");

String userOldToken = request.getHeader("userToken");

if(StringUtils.isNotBlank(userId) && StringUtils.isNotBlank(userOldToken)){

String userTokenKey = USER_REDIS_TOKEN + ":" + userId;

String userToken = redisOperator.getValue(userTokenKey);

// 用户有token,但最新token为空,说明登录状态过期

if(StringUtils.isBlank(userToken)){

returnErrorResponse(response,QiqvJSONResult.noAuth("登录过期,请重新登录"));

return false;

}

// 两个token不一致,可能是恶意用户乱填token

if(!userOldToken.equals(userToken)){

returnErrorResponse(response,QiqvJSONResult.noAuth("无效token,请重新登录"));

return false;

}

}else{

System.out.println("该用户没有登录");

returnErrorResponse(response,QiqvJSONResult.noAuth("请登录后再操作"));

return false;

}

return true;

}

public void returnErrorResponse(HttpServletResponse response, QiqvJSONResult qiqvJSONResult) throws IOException {

OutputStream outputStream = null ;

try {

response.setContentType("application/json");

response.setCharacterEncoding("utf-8");

outputStream = response.getOutputStream();

outputStream.write(JSONUtils.objectToJson(qiqvJSONResult).getBytes("UTF-8"));

outputStream.flush();

} catch (IOException e) {

e.printStackTrace();

}finally {

if(outputStream != null){

outputStream.close();

}

}

}

}

解释一下思路:

使用userId作为用户登录的唯一key值,UUID作为value。存放在redis中,30min后过期

由于请求还未到controller,所以转换结果的时候需要手动转一下json

2、注册拦截器

package com.qiqv.music.config;

import com.qiqv.music.controller.interceptor.MiniInterceptor;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.servlet.config.annotation.CorsRegistry;

import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.Arrays;

import java.util.List;

@Configuration

public class WebMvcConfig extends WebMvcConfigurerAdapter {

@Bean

public MiniInterceptor miniInterceptor(){

return new MiniInterceptor();

}

/**

* 设置拦截的url路径

* 暂时只针对前端用户评论、收藏、评分功能进行拦截

* @param registry

*/

@Override

public void addInterceptors(InterceptorRegistry registry){

List listOfVerify = Arrays.asList("/consumer/**","/rank/rateSongList","/collect/**","/comment/**");

List listOfExc = Arrays.asList("/consumer/login","/consumer/queryUserById","/consumer/getAllConsumer","/collect/getUserCollect","/comment/query**","/comment/allComment");

registry.addInterceptor(miniInterceptor()).addPathPatterns(listOfVerify)

.excludePathPatterns(listOfExc);

super.addInterceptors(registry);

}

}

这里的话,针对需要拦截的路径和需要放行的路径进行配置就行

关于redisTemple的引入这里就不再赘述。

到这里为止,前后端的token就都做完了,后面就再讲讲前端的一些其他思路吧

对于登录状态的判断,前端可以在router.foreach上对路由进行状态判定,从而实现页面程度的拦截(具体可以参考最后的参考文章2)

隐藏的小坑:

跨域问题

在使用拦截器后,会发现前端部分请求会无法正常到达后端,百度后发现是因为axios发送正式请求前会先发送一个嗅探请求,而嗅探请求是不携带我们封装的header的,所以会导致部分请求会无法成功,解决的方式有很多种,这里的话是选择了在后端去直接处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值