【笔记】vue+springboot前后端分离实现token登录验证和状态保存的简单实现方案
简单实现
token可用于登录验证和权限管理。
大致步骤分为:
- 前端登录,post用户名和密码到后端。
- 后端验证用户名和密码,若通过,生成一个token返回给前端。
- 前端拿到token用vuex和localStorage管理,登录成功进入首页。
- 之后前端每一次权限操作如跳转路由,都需要判断是否存在token,若不存在,跳转至登录页。
- 前端之后的每一个对后端的请求都要在请求头上带上token,后端查看请求头是否有token,拿到token检查是否过期,返回对应状态给前端。
- 若token已过期,清除token信息,跳转至登录页。
具体代码如下:
前端
- 登录页
<template>
<div class="signin-form">
<h3 class="sign-title">ticket-sys 登录</h3>
<div>
<el-form :model="loginForm" :rules="rules" status-icon ref="ruleForm" class="demo-ruleForm">
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
autocomplete="off"
placeholder="用户名"
prefix-icon="el-icon-user-solid"
></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
v-model="loginForm.password"
autocomplete="off"
placeholder="请输入密码"
prefix-icon="el-icon-lock"
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm" id="login-btn">登录</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import api from '../constant/api';
import {mapMutations} from "vuex";
export default {
name: 'login',
data() {
return {
loginForm:{
username:'',
password:''
},
userToken:'',
rules:{
username:[
{ required: true, message: '请输入用户名', trigger: 'blur' },
],
password:[
{ required: true, message: '请输入密码', trigger: 'blur' },
]
}
}
},
methods: {
...mapMutations(['changeLogin']),
submitForm() {
let v=this;
this.$axios({
method: 'post',
url: api.base_url+'/user/login',
data:{
'username':v.loginForm.username,
'password':v.loginForm.password
}
}).then(function(res){
console.log(res.data);
v.userToken = 'Bearer ' + res.data.token;
// 将用户token保存到vuex中
v.changeLogin({ Authorization:v.userToken });
v.$router.push('/home');
v.$message('登录成功');
}).catch(function(err){
console.log("err",err);
v.$message('密码或用户名错误');
})
}
}
}
</script>
<style scoped>...</style>
- vuex状态管理
/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
// 存储token
Authorization: localStorage.getItem('Authorization') ? localStorage.getItem('Authorization') : ''
},
mutations: {
// 修改token,并将token存入localStorage
changeLogin (state, user) {
state.Authorization = user.Authorization;
localStorage.setItem('Authorization', user.Authorization);
}
}
});
export default store;
- 路由守卫
/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/home',
name: 'home',
component: Home,
}
,
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: function () {
return import(/* webpackChunkName: "about" */ '../views/About.vue')
}
},
{
path:'/login',
name:'login',
component:function () {
return import('../views/Login.vue');
}
}
]
const router = new VueRouter({
<template>
<div class="home">
<el-container>
<!-- <Header/>-->
<!-- <el-main>首页</el-main>-->
<el-button @click="exit">退出登录</el-button>
<el-button @click="test">携带token的测试请求</el-button>
</el-container>
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
import Header from "../components/Header";
import api from "../constant/api";
export default {
name: 'home',
components: {
Header,
HelloWorld
},
methods:{
exit(){
localStorage.removeItem('Authorization');
this.$router.push('/login');
},
test(){
this.$axios({
method: 'get',
url: api.base_url+'/user/test',
}).then(function(res){
console.log("res",res);
}).catch(function(err){
console.log("err",err);
})
}
}
}
</script>
<style>...</style>
mode: 'history',
base: process.env.BASE_URL,
routes
});
// 导航守卫
// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
router.beforeEach((to, from, next) => {
if (to.path === '/login') {
next();
} else {
let token = localStorage.getItem('Authorization');
if (token === 'null' || token === '') {
next('/login');
} else {
next();
}
}
});
export default router
- 主文件中注册并添加拦截器
/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import './plugins/element.js'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import axios from 'axios' ;
import Vuex from 'vuex' //引入状态管理
Vue.prototype.$axios= axios ;
Vue.use(Vuex) ;
Vue.config.productionTip = false
Vue.use(ElementUI)
//这里要导入store
import store from "./store";
// 添加请求拦截器,在请求头中加token
axios.interceptors.request.use(
config => {
if (localStorage.getItem('Authorization')) {
config.headers.Authorization = localStorage.getItem('Authorization');
}
return config;
},
error => {
return Promise.reject(error);
});
new Vue({
//这里要配置store
router, store:store,
render: function (h) { return h(App) }
}).$mount('#app')
- home页面
<template>
<div class="home">
<el-container>
<!-- <Header/>-->
<!-- <el-main>首页</el-main>-->
<el-button @click="exit">退出登录</el-button>
<el-button @click="test">携带token的测试请求</el-button>
</el-container>
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
import Header from "../components/Header";
import api from "../constant/api";
export default {
name: 'home',
components: {
Header,
HelloWorld
},
methods:{
exit(){
//退出登录,清空token
localStorage.removeItem('Authorization');
this.$router.push('/login');
},
test(){
this.$axios({
method: 'get',
url: api.base_url+'/user/test',
}).then(function(res){
console.log("res",res);
}).catch(function(err){
console.log("err",err);
})
}
}
}
</script>
<style>...</style>
后端
- 登录controller
package com.zxc.ticketsys.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/user")
public class UserController{
@RequestMapping(value = "/login",method = RequestMethod.POST)
@ResponseBody
public String login(@RequestHeader Map<String,Object> he,@RequestBody Map<String,Object> para) throws JsonProcessingException {
System.out.println(he);
String username=(String)para.get("username");
String password=(String)para.get("password");
HashMap<String,Object> hs=new HashMap<>();
hs.put("token","token"+username+password);
ObjectMapper objectMapper=new ObjectMapper();
return objectMapper.writeValueAsString(hs);
}
@RequestMapping(value = "/test",method = RequestMethod.GET)
@ResponseBody
public String test(@RequestHeader Map<String,Object> he) throws JsonProcessingException {
System.out.println(he);
HashMap<String,Object> hs=new HashMap<>();
ObjectMapper objectMapper=new ObjectMapper();
return objectMapper.writeValueAsString(hs);
}
}
测试
- 登录
此时后台的请求头:
{host=localhost:8088, connection=keep-alive, accept=application/json, text/plain, */*, origin=http://localhost:8080, user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36, sec-fetch-site=same-site, sec-fetch-mode=cors, referer=http://localhost:8080/home, accept-encoding=gzip, deflate, br, accept-language=zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7}
- 登录成功,进入home页面
后端返回的token:
- 前端持有token,访问测试接口
后端收到请求头:
{host=localhost:8088, connection=keep-alive, accept=application/json, text/plain, */*, origin=http://localhost:8080, authorization=Bearer tokenadminadmin, user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36, sec-fetch-site=same-site, sec-fetch-mode=cors, referer=http://localhost:8080/home, accept-encoding=gzip, deflate, br, accept-language=zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7}
带有token,可以进行验证
- 退出登录,清空token
后台实际应用
在实际的应用中,一般需要一个生成token的工具类和一个拦截器对请求进行拦截。
- token生成工具类
/utils/TokenUtil.java
package com.zxc.ticketsys.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.zxc.ticketsys.model.User;
import java.util.Date;
public class TokenUtil {
private static final long EXPIRE_TIME= 10*60*60*1000;
private static final String TOKEN_SECRET="txdy"; //密钥盐
/**
* 签名生成
* @param user
* @return
*/
public static String sign(User user){
String token = null;
try {
Date expiresAt = new Date(System.currentTimeMillis() + EXPIRE_TIME);
token = JWT.create()
.withIssuer("auth0")
.withClaim("username", user.getUsername())
.withExpiresAt(expiresAt)
// 使用了HMAC256加密算法。
.sign(Algorithm.HMAC256(TOKEN_SECRET));
} catch (Exception e){
e.printStackTrace();
}
return token;
}
/**
* 签名验证
* @param token
* @return
*/
public static boolean verify(String token){
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(TOKEN_SECRET)).withIssuer("auth0").build();
DecodedJWT jwt = verifier.verify(token);
System.out.println("认证通过:");
System.out.println("username: " + jwt.getClaim("username").asString());
System.out.println("过期时间: " + jwt.getExpiresAt());
return true;
} catch (Exception e){
return false;
}
}
}
- 拦截器类
/interceptor/TokenInterceptor.java
package com.zxc.ticketsys.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.zxc.ticketsys.utils.TokenUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler)throws Exception{
if(request.getMethod().equals("OPTIONS")){
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
response.setCharacterEncoding("utf-8");
String token = request.getHeader("token");
if(token != null){
boolean result = TokenUtil.verify(token);
if(result){
System.out.println("通过拦截器");
return true;
}
}
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
try{
JSONObject json = new JSONObject();
json.put("msg","token verify fail");
json.put("code","50000");
response.getWriter().append(json.toJSONString());
System.out.println("认证失败,未通过拦截器");
}catch (Exception e){
e.printStackTrace();
response.sendError(500);
return false;
}
return false;
}
}
- 配置拦截器
/config/WebConfiguration.java
注意最好写在一个配置类里,且WebMvcConfigurationSupport和WebMvcConfigurerAdapter不要同时存在
这里包括处理跨域的配置,而且全部改为implements WebMvcConfigurer接口
package com.zxc.ticketsys.config;
import com.zxc.ticketsys.interceptor.TokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
import org.springframework.web.servlet.config.annotation.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 跨域请求支持/token拦截
* tip:只能写在一个配置类里
*/
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
private TokenInterceptor tokenInterceptor;
//构造方法
public WebConfiguration(TokenInterceptor tokenInterceptor){
this.tokenInterceptor = tokenInterceptor;
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedHeaders("*")
.allowedMethods("*")
.allowedOrigins("*");
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer){
configurer.setTaskExecutor(new ConcurrentTaskExecutor(Executors.newFixedThreadPool(3)));
configurer.setDefaultTimeout(30000);
}
@Override
public void addInterceptors(InterceptorRegistry registry){
List<String> excludePath = new ArrayList<>();
//排除拦截,除了注册登录(此时还没token),其他都拦截
excludePath.add("/user/register"); //登录
excludePath.add("/user/login"); //注册
excludePath.add("/static/**"); //静态资源
excludePath.add("/assets/**"); //静态资源
registry.addInterceptor(tokenInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(excludePath);
WebMvcConfigurer.super.addInterceptors(registry);
}
}
- 控制器类
package com.zxc.ticketsys.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zxc.ticketsys.model.User;
import com.zxc.ticketsys.utils.TokenUtil;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/user")
public class UserController{
@RequestMapping(value = "/login",method = RequestMethod.POST)
@ResponseBody
public String login(@RequestBody Map<String,Object> para) throws JsonProcessingException {
String username=(String)para.get("username");
String password=(String)para.get("password");
String token= TokenUtil.sign(new User(username,password));
HashMap<String,Object> hs=new HashMap<>();
hs.put("token",token);
ObjectMapper objectMapper=new ObjectMapper();
return objectMapper.writeValueAsString(hs);
}
@RequestMapping(value = "/test",method = RequestMethod.POST)
@ResponseBody
public String test(@RequestBody Map<String,Object> para) throws JsonProcessingException {
HashMap<String,Object> hs=new HashMap<>();
hs.put("data","data");
ObjectMapper objectMapper=new ObjectMapper();
return objectMapper.writeValueAsString(hs);
}
}
- 测试
- 登录
成功登录,获得token。
- 不带token访问test接口
被拦截,访问失败。
- 带有效token访问test接口
访问成功,获得数据。
完整项目代码
【笔记】vue+springboot前后端分离实现token登录验证和状态保存的简单实现方案的更多相关文章
- vue+springboot前后端分离实现单点登录跨域问题处理
最近在做一个后台管理系统,前端是用时下火热的vue.js,后台是基于springboot的.因为后台系统没有登录功能,但是公司要求统一登录,登录认证统一使用.net项目组的认证系统.那就意味着做单点登 ...
- docker-compose 部署 Vue+SpringBoot 前后端分离项目
一.前言 本文将通过docker-compose来部署前端Vue项目到Nginx中,和运行后端SpringBoot项目 服务器基本环境: CentOS7.3 Dokcer MySQL 二.docker ...
- 前后端分离使用 Token 登录解决方案
前后端分离使用 Token 登录解决方案:https://juejin.im/post/5b7ea1366fb9a01a0b319612
- Vue+SpringBoot前后端分离中的跨域问题
在前后端分离开发中,需要前端调用后端api并进行内容显示,如果前后端开发都在一台主机上,则会由于浏览器的同源策略限制,出现跨域问题(协议.域名.端口号不同等),导致不能正常调用api接口,给开发带来不 ...
- 云计算:Ubuntu下Vue+Springboot前后端分离项目部署(多节点)
一.机器准备 首先准备三台机器: 我是一台WINDOWS系统主机,在WINDOWS里的 VMware 中安装两台Ubuntu系统虚拟机 如果你的虚拟机只有 CentOS,可以参考这篇文章:https: ...
- 解决vue+springboot前后端分离项目,前端跨域访问sessionID不一致导致的session为null问题
问题: 前端跨域访问后端接口, 在浏览器的安全策略下默认是不携带cookie的, 所以每次请求都开启了一次新的会话. 在后台打印sessionID我们会发现, 每次请求的sessionID都是不同的, ...
- Vue + SpringBoot 前后端分离打包部署遇到的坑
1. 在vue项目的目录下,用npm run build 生成dist目录,将目录下的static和index.html复制到SpringBoot项目下的resource目录下 这个时候发现启动Spr ...
- 一套基于SpringBoot+Vue+Shiro 前后端分离 开发的代码生成器
一.前言 最近花了一个月时间完成了一套基于Spring Boot+Vue+Shiro前后端分离的代码生成器,目前项目代码已基本完成 止步传统CRUD,进阶代码优化: 该项目可根据数据库字段动态生成 c ...
- SpringBoot+Jpa+SpringSecurity+Redis+Vue的前后端分离开源系统
项目简介: eladmin基于 Spring Boot 2.1.0 . Jpa. Spring Security.redis.Vue的前后端分离的后台管理系统,项目采用分模块开发方式, 权限控制采用 ...
随机推荐
- zabbix清理监控历史mysql数据
问题描述: 今天同事说有个zabbix监控数据库历史数据越来越多了, 让我帮忙清一下,顺便熟悉练练手,做个笔记 zabbix监控运行一段时间以后,会留下大量的历史监控数据 zabbix数据库一直在增大 ...
- Swagger解决你手写API接口文档的痛
首先,老规矩,我们在接触新事物的时候, 要对之前学习和了解过的东西做一个总结. 01 痛 苦 不做.不行 之前,前后端分离的系统由前端和后端不同的编写,我们苦逼的后端工程师会把自己已经写完的A ...
- Chrome插件开发(三)
在日常工作中,我们可能经常需要在手机端测试我们所做的页面,如果每次在手机端测试都手输网址,网址短的还好,如果长的网址也需要一个字母一个字母去敲,那无疑是一场噩梦,试想我们有一个工具只需要点击一个按钮就 ...
- Spring Boot2 系列教程(二十)Spring Boot 整合JdbcTemplate 多数据源
多数据源配置也算是一个常见的开发需求,Spring 和 SpringBoot 中,对此都有相应的解决方案,不过一般来说,如果有多数据源的需求,我还是建议首选分布式数据库中间件 MyCat 去解决相关问 ...
- __new__与__init__的区别和应用场景
创建实例的时候, 先运行的_new_方法, _new_创建对象 Student object(实例)返回给 _init_ 里面的第一个参数self class Student(object): def ...
- [考试反思]0801NOIP模拟测试11
8月开门红. 放假回来果然像是神志不清一样. 但还是要接受这个事实. 嗯,说好听点,并列rank#7. 说难听点,垃圾rank#18. 都不用粘人名就知道我是哪一个吧... 因为图片不能太长,所以就不 ...
- 高可用架构的实现--dubbo+zookeeper+maven+tomcat
最近在做分布式的服务架构搭建,因为自己确实很喜欢搞这种技术类的研究,所以在公司需要的时候主动承担了这项光荣而艰巨的任务.公司搭建的架构主要目的是需要支持后端接口的多用户的高并发访问,希望能够达到每秒并 ...
- 深入理解C#第三版部分内容
最近,粗略的读了<深入理解C#(第三版)>这本技术书,书中介绍了C#不同版本之间的不同以及新的功能. 现在将部分摘录的内容贴在下面,以备查阅. C#语言特性: 1.C#2.0 C#2的主 ...
- C#同级catch块和finally块中全都抛出异常,上一级捕获哪一个?
C#同级catch块和finally块中全都抛出异常,上一级优先捕获finally块中的异常. 测试代码: using System; namespace test { class Program { ...
- win7/win10系列的office安装与激活
Windows系列电脑安装office傻瓜式教程 一. 下载与安装 下载 (1).所需工具:迅雷 下载链接:http://xl9.xunlei.com/ 显示界面如下,点击“立即下载”即可,然后 ...