前言
在使用Vue框架时,我们通常使用axios收发服务器的请求。为了避免诸多恶意行为,我们必须使用axios拦截器进行屏蔽。下面我将以axios后置拦截器结合用户登录举例说明
一、逻辑
1. 用户输入邮箱和密码(已经注册);
2. 邮箱错误,弹出提醒,并拒绝执行后续操作;
3. 密码错误,弹出提醒,并拒绝执行后续操作;
4. 邮箱和密码均正确时,放行,跳转到后台界面。
二、后端部分
controller
代码如下:
@PostMapping("/login")
public Result login(@Validated @RequestBody LoginDto loginDto, HttpServletResponse response)
{
User user = userService.getOne(new QueryWrapper<User>().eq("email",loginDto.getEmail()));
Assert.notNull(user,"用户不存在!");//断言user不为空,为空则抛出message
if (!user.getPassword().equals(SecureUtil.md5(loginDto.getPassword()))) {
return Result.fail("密码不正确");
}
String jwt = jwtUtils.generateToken(user.getId());
response.setHeader("Authorization",jwt);
response.setHeader("Access-control-Expose-Headers","Authorization");
user.setLastLogin(LocalDateTime.now());
userService.updateById(user);
return Result.success(MapUtil.builder()
.put("id",user.getId())
.put("username",user.getUsername())
.put("avatar",user.getAvatar())
.put("email",user.getEmail())
.map()
);
上述代码可以简单理解为3个部分:
- 将前端传回email与数据库对应字段对比,若无相同email,说明用户不存在,直接使用断言抛出错误;
- 到了这一步,说明email正确,继续对比密码password,如果不正确,这返回一个Result类型数据(这里Result是我定义的一个统一封装的结果类,分为正确和错误情况,错误情况状态码简单设置为400。给予前端状态和信息反馈),内容如下:
@Data
public class Result implements Serializable {
private int code;//200正常,非200表示异常
private String msg;
private Object data;
public static Result success(Object data)
{
return success(200,"操作成功",data);
}
public static Result success(int code, String msg, Object data)
{
Result result = new Result();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
public static Result fail(String msg)
{
return fail(400,msg,null);
}
public static Result fail(String msg, Object data)
{
return fail(400,msg,data);
}
public static Result fail(int code, String msg, Object data)
{
Result result = new Result();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
}
- email和password均正确,直接返回Result类的success方法。
2.前端部分
在main.js引入ElementUI、axios(两者需要使用npm进行安装)和axios.js代码如下:
import Element from 'element-ui'
import "element-ui/lib/theme-chalk/index.css"
import axios from 'axios'//引入axios库
import "./axios"//引入axios.js
Vue.prototype.$axios = axios
Vue.use(Element)
axios.js为axios拦截器核心代码,示例如下:
import axios from "axios";
import Element from 'element-ui'
import router from './router'
import store from './store'
axios.defaults.baseURL="http://localhost:8081"
//后置拦截
axios.interceptors.response.use(response => {
let res = response.data;
console.log("===========")
console.log(res)
console.log("===========")
if (res.code === 200) {
return response
}else {
Element.Message.error("密码错误,请重新输入!",{duration: 3 * 1000})//持续3秒消失
return Promise.reject(response.data.message)
}
},
error => {
if(error.response.data)
{
error.message = error.response.data.msg
}
if (error.response.status === 401){
store.commit("REMOVE_INFO")
router.push("/login")
}
Element.Message.error(error.message,{duration: 3 * 1000})
return Promise.reject(error)
}
)
这里引入了axios、elementUI、以及Vue路由router和存储store
其后置拦截器分为响应和错误两种情况,即向后端发起请求时,根据后端状态分别执行不同操作。具体为:
1.后端正常执行,则会返回一个Result类型的数据,执行response流程。我们拿到数据后,获取到其中的状态码code,若email和password正确,返回code200(我将正确状态码设置为200)故当其状态码为200时,直接放行,返回响应,继续执行后续操作;反之,使用ElementUi消息组件进行通知。并使用Promise.reject()方法拒绝执行后续操作。
2.后端执行异常,即Assert断言抛出异常,进入error流程。接着判断能否拿到错误响应数据,能的话将其中错误信息赋值给error.message。由于我使用shiro框架进行登录效验,对于未登录访问需要登录才能访问的接口的行为均会抛出401异常。故对这种情况进行处理。即移除浏览器缓存信息,同时跳转到登录页。最后,同样使用Promise.reject()方法拒绝执行后续操作。
前端配置
store下index.js的全局参数配置(保存jwt和userInfo到浏览器以及将他们移除):
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
/**
* 全局配置参数
*/
export default new Vuex.Store({
state: {
token:localStorage.getItem("token"),
userInfo:JSON.parse(sessionStorage.getItem("userInfo"))//反序列化获得userInfo对象
},
mutations: {
//set
SET_TOkEN: (state, token) => {
state.token = token
//浏览器关闭时,可以存到浏览器的localStore
localStorage.setItem("token",token)
},
SET_USER_INFO: (state, userInfo) => {
state.userInfo = userInfo
//浏览器关闭时,可以存到sessionStorage,但其不能存一个对象,只能序列化存储
sessionStorage.setItem("userInfo",JSON.stringify(userInfo))
},
REMOVE_INFO: (state) => {
state.token = ''
state.userInfo = {}
//移除存储在浏览器缓存的数据
localStorage.removeItem("token")
sessionStorage.removeItem("userInfo")
},
},
getters: {
//get
getUsers : state => {
return state.userInfo
}
},
actions: {
},
modules: {
}
})
测试页面
以登录页进行测试(默认以及传入email和password方便测试),代码如下:
<template>
<div id="login" data-title="登录">
<!--<video preload="auto" class="me-video-player" autoplay="autoplay" loop="loop">
<source src="../../static/vedio/sea.mp4" type="video/mp4">
</video>-->
<div class="me-login-box me-login-box-radius">
<h1>用户登录</h1>
<el-form ref="userForm" :model="userForm" :rules="rules">
<el-form-item prop="email">
<el-input placeholder="邮箱" v-model="userForm.email"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input placeholder="密码" type="password" v-model="userForm.password"></el-input>
</el-form-item>
<el-form-item size="small" class="me-login-button">
<el-button type="primary" @click="login('userForm')">登录</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
export default {
name: 'Login',
data() {
return {
userForm: {
email: '1720619112@qq.com',
password: 'zpl809020'
},
rules: {
email: [
{required: true, message: '请输入邮箱', trigger: 'blur'},
{type: 'email', message: '邮箱格式不正确', trigger: 'blur, change'}
],
password: [
{required: true, message: '请输入密码', trigger: 'blur'},
{max: 20, message: '不能大于20个字符', trigger: 'blur'}
]
}
}
},
methods: {
login(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
const _this = this;
this.$axios.post('/user/login',this.userForm).then(res => {
const jwt = res.headers['authorization']
const userInfo = res.data.data;
//提交存储jwt和userInfo,为了后续页面获取到数据
_this.$store.commit("SET_TOkEN",jwt)
_this.$store.commit("SET_USER_INFO",userInfo)
_this.$message.success("登录成功!")
//页面跳转
_this.$router.push("/userIndex")
})
} else {
console.log('error submit');
return false;
}
});
}
}
}
</script>
<style scoped>
#login {
min-width: 100%;
min-height: 100%;
}
.me-video-player {
background-color: transparent;
width: 100%;
height: 100%;
object-fit: fill;
display: block;
position: absolute;
left: 0;
z-index: 0;
top: 0;
}
.me-login-box {
position: absolute;
width: 300px;
height: 260px;
background-color: white;
margin-top: 150px;
margin-left: -180px;
left: 50%;
padding: 30px;
}
.me-login-box-radius {
border-radius: 10px;
box-shadow: 0px 0px 1px 1px rgba(161, 159, 159, 0.1);
}
.me-login-box h1 {
text-align: center;
font-size: 24px;
margin-bottom: 20px;
vertical-align: middle;
}
.me-login-design {
text-align: center;
font-family: 'Open Sans', sans-serif;
font-size: 18px;
}
.me-login-design-color {
color: #5FB878 !important;
}
.me-login-button {
text-align: center;
}
.me-login-button button {
width: 100%;
}
</style>
测试结果
- 用户名错误
- 密码错误
- 均正确