【笔记】vue+springboot前后端分离实现token登录验证和状态保存的简单实现方案
2020-01-11 07:27
901 查看
简单实现
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);
}
}
[/code]
测试
- 登录
此时后台的请求头:
{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;
}
}
}
[/code]
- 拦截器类
/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;
}
}
[/code]
- 配置拦截器
/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);
}
}
[/code]
- 控制器类
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);
}
}
[/code]
- 测试
- 登录
成功登录,获得token。
- 不带token访问test接口
被拦截,访问失败。
带有效token访问test接口
访问成功,获得数据。
完整项目代码
- 点赞
- 收藏
- 分享
- 文章举报
相关文章推荐
- vue+springboot前后端分离实现单点登录跨域问题解决方法
- vue+springboot前后端分离实现单点登录跨域问题解决方法
- 搭建spring-boot+vue前后端分离框架并实现登录功能
- springboot +vue实现token登录3之获取登录人员信息
- Springboot + Vue + shiro 实现前后端分离、权限控制
- spring boot+vue 的前后端分离与合并方案实例详解
- Spring Boot + Security + JWT 实现Token验证+多Provider——登录系统
- springboot+vue的前后端分离与合并方案
- 前后端分离:使用spring的Aop实现Token验证
- swagger+springboot实现前(vue)后端分离
- SpringBoot+Vue前后端分离实现高并发秒杀——后端项目知识总结
- 基于CAS的单点登录SSO[5]: 基于Springboot实现CAS客户端的前后端分离
- 【笔记】总结Springboot和Vue前后端分离的跨域问题
- springboot +vue实现token登录2
- spring boot 前后端分离整合shiro(五)整合redis并实现并发登录控制
- 基于springboot+vue+element+ueditor实现前后端分离的富文本框实现
- springboot+vue的前后端分离与合并方案
- SpringBoot+Vue前后端分离实现高并发秒杀——前端知识总结
- Spring Boot + Java爬虫 + 部署到Linux(六、后端Controller实现、下载文件以及登录验证拦截器)