springboot+vue结合redis实现登录拦截
1.后端代码
1.1pom.xml依赖
redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
mysql依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
jwt依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.3</version>
</dependency>
完整pom.xml依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>vuesb</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>vuesb</name>
<description>vuesb</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.2后端jwt拦截器
1.2.1 Interceptor拦截器配置(入口拦截)
因为我前端的url为本机8080端口,所以在allowedOrigins这里我把允许的跨域请求设置为了我的前端端口,这里也可以写为
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT", "PATCH", "OPTIONS", "HEAD")
.maxAge(3600 * 24);
package com.example.vuesb.jwt;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList;
public class InterceptorConfig implements WebMvcConfigurer {
TokenInterceptor tokenInterceptor;
public InterceptorConfig(TokenInterceptor tokenInterceptor){//构造函数
this.tokenInterceptor=tokenInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry){//配置拦截器
ArrayList<String>excludePath=new ArrayList<>();
excludePath.add("/login");//登录
excludePath.add("/logout");//登出
excludePath.add("/register");//注册
registry.addInterceptor(tokenInterceptor)//注册拦截器
.addPathPatterns("/**")//拦截所有请求
.excludePathPatterns(excludePath);//添加拦截白名单
WebMvcConfigurer.super.addInterceptors(registry);//调用父接口
}
@Override
public void addCorsMappings(CorsRegistry registry){
registry.addMapping("/**")
.allowCredentials(true)//允许携带cookie
.allowedMethods("GET", "POST", "DELETE", "PUT", "PATCH", "OPTIONS", "HEAD")//允许访问的方法
.allowedOrigins("http://localhost:8080")//允许的跨域访问地址
.maxAge(3600*24);//options缓存时间
}
}
1.2.2 JWTUtil
这里的verify方法没什么用,是我在用redis之前写的,可以不加
package com.example.vuesb.jwt;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
public class JWTUtil {
private static final long EXPIRE_TIME = 60 * 60 * 1000;
private static final String tokenPassword = "uziCjb";
public static String sign(String username) {//用用户名作为被加密的对象
String token;
Date expireTime = new Date(System.currentTimeMillis() + EXPIRE_TIME);//过期时间
token = JWT.create()//生成jwt令牌,加密过程
.withIssuer("llh")
.withClaim("username", username)
.withExpiresAt(expireTime)
.sign(Algorithm.HMAC256(tokenPassword));
return token;//返回加密后的token
}
public static boolean verify(String token){
JWTVerifier jwtVerifier=JWT.require(Algorithm.HMAC256(tokenPassword)).withIssuer("llh").build();//构建一个jwt解码器
DecodedJWT jwtToken=jwtVerifier.verify(token);//解码
if(token.isEmpty()){//若token为空则返回false拦截
return false;
}
else {
System.out.println("认证通过:");
System.out.println("issuer: " + jwtToken.getIssuer());
System.out.println("username: " + jwtToken.getClaim("username").asString());
System.out.println("过期时间: " + jwtToken.getExpiresAt());
return true;
}
}
}
1.2.3TokenInterceptor(token拦截器)
注册服务那里一定要写!!!
package com.example.vuesb.jwt;
import com.example.vuesb.redis.RedisUtil;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TokenInterceptor implements HandlerInterceptor {
@Resource
RedisUtil redisUtil;//注册服务
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("token");
try {
if (request.getMethod().equals("OPTIONS")) {//检查是否为跨域请求
if (token != null) {
if (redisUtil.getTokens(token) != null) {
response.setStatus(200);//设置状态码为正常
return true;
} else {
response.setStatus(401);//设置状态码为未通过登录验证
return false;
}
} else {
response.setStatus(401);//设置状态码为未通过登录验证
return false;
}
}
} catch (Exception e) {
response.setStatus(500);
throw new RuntimeException();
}
response.setCharacterEncoding("utf-8");
try {
if (token != null) {
if (redisUtil.getTokens(token) != null) {
response.setStatus(200);//设置状态码为正常,即通过登录验证
System.out.println("通过拦截器");
return true;
} else {
response.setStatus(401);
return false;
}
} else {
response.setStatus(401);
return false;
}
} catch (Exception exception) {
response.setStatus(500);//发生了不可预测的错误
throw new RuntimeException();
}
}
}
1.3redis配置
RedisUtil
package com.example.vuesb.redis;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Service
public class RedisUtil {
@Resource
private RedisTemplate<String, String> stringRedisTemplate;//这是一个使用redis的API,可以直接用StringRedisTemplate
public void addTokens(String username, String token) {//存入token
if (username != null && token != null) {
System.out.println("参数不为空");
System.out.println(username);
System.out.println(token);
ValueOperations valueOperations = stringRedisTemplate.opsForValue();
valueOperations.set(username, token, 60, TimeUnit.MINUTES);//设置token过期时间为一小时
} else {
System.out.println("参数为空");
}
}
public String getTokens(String username) {//获取token
return stringRedisTemplate.opsForValue().get(username);
}
public void delTokens(String username) {//删除token
stringRedisTemplate.delete(username);
}
}
1.4登录接口
数据库这里我是用的原生jdbc
package com.example.vuesb;
import JDBC.VueLogin;
import com.example.vuesb.jwt.JWTUtil;
import com.example.vuesb.redis.RedisUtil;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.sql.ResultSet;
import java.sql.SQLException;
@CrossOrigin
@RestController
public class demo {
@Resource
RedisUtil redisUtil;
@RequestMapping("/login")
public String loginCheck(@RequestParam("username") String username, @RequestParam("password") String password) throws SQLException {
String returnStr="No";
ResultSet resultSet;
resultSet=new VueLogin().seekAccount(username);
String sqlPass = null;
if(resultSet.next()){
sqlPass=resultSet.getString(1);
if (sqlPass.equals(password)){
String token = new JWTUtil().sign(username);
redisUtil.addTokens(username,token);
returnStr=token;
}
}
return returnStr;
}
@RequestMapping("/register")
public String register(@RequestParam("username") String username,@RequestParam("password") String password){
String returnStr="123";
ResultSet resultSet=new VueLogin().seekAccount(username);
try {
if (!resultSet.next()){
new VueLogin().addRegister(username, password);
returnStr="OK";
}
else {
returnStr="No";
}
} catch (SQLException e) {
System.out.println("注册失败");
throw new RuntimeException(e);
}
return returnStr;
}
}
1.5mysql
package JDBC;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class VueLogin {
public void addRegister(String username ,String password){
String sql="insert into vuelogin(username,password)values(?,?)";
PreparedStatement preparedStatement;
try {
preparedStatement=Conn.getConnection().prepareStatement(sql);
preparedStatement.setString(1,username);
preparedStatement.setString(2,password);
preparedStatement.executeUpdate();
preparedStatement.close();
} catch (SQLException e) {
System.out.println("注册失败");
throw new RuntimeException(e);
}
}
public ResultSet seekAccount(String account){
String sql="select password from vuelogin where username=?";
PreparedStatement preparedStatement;
ResultSet resultSet;
try {
preparedStatement= Conn.getConnection().prepareStatement(sql);
preparedStatement.setString(1,account);
resultSet=preparedStatement.executeQuery();
return resultSet;
} catch (SQLException e) {
System.out.println("查询失败");
throw new RuntimeException(e);
}
}
}
2.前端代码
2.1 main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './plugins/element.js'
import './router/permission'
import axiosInstance from './store/request.js'
import axios from 'axios'
Vue.prototype.$http = axios
Vue.config.productionTip = false
// axiosInstance.defaults.baseURL = 'http://localhost:8081'; // 后端端口为8081
new Vue({
router,
store,
axiosInstance,
render: h => h(App)
}).$mount('#app')
2.2store/index.js(记得安装vuex)
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
token:localStorage.getItem('token')?localStorage.getItem('token'):''
},
getters: {
},
mutations: {
setToken(state,token){
state.token=token
localStorage.setItem("token",token.token)//存储token
},
delToken(state){
state.token=''
localStorage.removeItem("token")//删除token
}
},
actions: {
},
modules: {
}
})
2.3路由配置router下index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
import LayoutView from "../views/LayoutView.vue"
Vue.use(VueRouter)
const routes = [
{
path:"/",
name:"LayoutView",
component:LayoutView,
children:[
{
path: '',
name: 'home',
component: HomeView,
meta:{
isLogin:true
}
},
{
path:"params",
name:"paramsview",
component:()=>import("../views/main/ParamsView.vue"),
meta:{
isLogin:true
}
},
{
path:"product",
name:"ProductView",
component:()=>import("../views/main/ProductView.vue"),
meta:{
isLogin:true
}
},
{
path:"ad",
name:"ADCategoryview",
component:()=>import("../views/main/ADCategoryView.vue"),
meta:{
isLogin:true
}
}
]
},
{
path:"/login",
name:"LoginView",
component:()=>import("../views/LoginView.vue")
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
2.4permission路由守护
import router from "./index";
//路由守护
router.beforeEach((to,from,next)=>{
if(to.meta.isLogin){
if(to.path==='/login'){
next()
}
else{
const tokenStr=window.localStorage.getItem('token')
if(!tokenStr){
return next('/login')
}
else{
next()
}
}
}
else{
next();
}
})
2.5设置新的全局axios
在这里需要设置一个新的全局axios,这样可以在每一个ajax请求发出之前将token加入到请求头中
import axios from "axios";
//创建axios实例
const axiosInstance = axios.create({
baseURL: 'http://localhost:8081', // 设置默认发送地址为后端端口
});
//添加拦截器,在每次ajax之前都会执行这个操作
axiosInstance.interceptors.request.use(function (config){
console.log("操作了")
//从本地缓存获得token
const token=window.localStorage.getItem('token')
//如果存在将其加入请求头中
if(token){
console.log(token)
config.headers.token = token;
}
return config;
},function (error){
return Promise.reject(error);//发生错误返回一个拒绝的promise对象
})
export default axiosInstance;
2.6loginView(登录业务)
上半部分是elementUI,可以省略
<template>
<div class="login">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>人工智能大数据实验室</span>
</div>
<el-tabs v-model="currentIndex" stretch @tab-click="handleClick">
<el-tab-pane label="登录" name="login">
<el-form :model="loginForm" :rules="rules" status-icon ref="loginForm">
<el-form-item label="用户名" label-width="80px" prop="username">
<el-input type="text" v-model="loginForm.username"></el-input>
</el-form-item>
<el-form-item label="密码" label-width="80px" prop="password">
<el-input type="password" v-model="loginForm.password"></el-input>
</el-form-item>
<el-form-item >
<el-button type="primary" @click="submitForm('loginForm')">登录</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="注册" name="register">
<el-form :model="registerForm" :rules="rules" status-icon ref="registerForm">
<el-form-item label="用户名" label-width="80px" prop="username">
<el-input type="text" v-model="registerForm.username"></el-input>
</el-form-item>
<el-form-item label="密码" label-width="80px" prop="password">
<el-input type="password" v-model="registerForm.password"></el-input>
</el-form-item>
<el-form-item label="确认密码" label-width="80px" prop="checkPassword">
<el-input type="password" v-model="registerForm.checkPassword"></el-input>
</el-form-item>
<el-form-item >
<el-button type="primary" @click="submitForm('registerForm')">注册</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</template>
<script>
import axios from 'axios';
import axiosInstance from '@/store/request';
export default {
data(){
var validateUsername= (rule,value,callback) =>{
if(!value){
return callback(new Error('请输入账号'))
}
else if(value.length<3){
return callback(new Error('请输入3位以上的账号'))
}
else {
callback();
}
}
var validatePassword=(rule,value,callback) =>{
if(this.activeTab==="login"){
if(!value){
return callback(new Error("请输入密码"))
}
else{
callback();
}
}
if(this.activeTab==="register"){
if(!value){
return callback(new Error("请输入密码"))
}
else if(value.length<6){
callback(new Error("请输入六位以上的密码"))
}
else if(value.length>16){
callback(new Error("请输入16位以下的密码"))
}
else{
callback();
}
}
}
var validateRegisterCheckPassword=(rule,value,callback) =>{
if(!value){
return callback(new Error("请再次输入密码"))
}
else if(this.registerForm.password!==value){
return callback(new Error("两次密码输入不一致"))
}
else {
return callback()
}
}
return {
currentIndex:"login",
loginForm:{
username:"",
password:""
},
registerForm:{
username:"",
password:"",
checkPassword:"",
},
activeTab:"login",
rules:{
username:[{
validator:validateUsername,trigger:'blur'
}],
password:[{
validator:validatePassword,trigger:'blur'
}],
checkPassword:[{
validator:validateRegisterCheckPassword,trigger:'blur'
}],
}
}
},
methods:{
submitForm(formname){
const that=this
this.$refs[formname].validate((valid)=>{
if(valid){
if(this.activeTab==="login"){
console.log(this.loginForm.username)
console.log(this.loginForm.password)
axiosInstance.get('/login',{
params:{
username:this.loginForm.username,
password:this.loginForm.password,
}
}).then(function (response){
if(response.data!=='No'){
that.$store.commit('setToken',response.data)
that.$router.push({path:'/product'})
}
else{
alert("账号密码错误")
}
})
// console.log(this.username+"pass"+this.password);
}
if(this.activeTab==="register"){
axios.get('/register',{
params:{
username:this.registerForm.username,
password:this.registerForm.password,
}
}).then(function (response){
if(response.data==='OK'){
alert("注册成功")
}
else if(response.data==='No'){
alert("账号已存在")
}
else{
alert("注册失败")
}
})
console.log(this.registerForm)
}
}
else{
console.log("abc")
return;
}
})
},
handleClick(tab){
this.activeTab=tab.name
// console.log("active"+this.activeTab)
// console.log("tab"+tab.name)
}
}
}
</script>
<style>
.login{
width: 1200px;
margin: 0 auto;
text-align: center;
}
.box-card{
width: 500px;
margin: 100px auto;
}
</style>
3.成果展示
可以在请求头里看到加上了一个token