前端需要掌握的知识:
1、路由守卫:知识点。使用路由拦截、如果有token则放行、没有token跳转到登录页面
2、Vuex的详细理解:知识点。多个组件共享数据。这里牵涉到一些知识点的简写形式
1、视频演示
toekn的生成使用
这里的注销原理:给注销按钮绑定一个事件、触发函数、在函数中移除存储的token,同时跳转到登录页。这里不在演示(过于简单)
2、token的生成以及验证的思路
1、后端使用jwt生成token返回给前端,前端存储token。在进行路由跳转之前、通过路由守卫判断token是否为空。如果为空、则跳转到指定页面。不为空则放行。同时将token放入到请求头中、后端对token进行验证
3、后端代码
3.1 引入jwt依赖
提示:如果引入失败、请替换版本
<!--集成jwt实现token认证-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
3.2 生成token的工具类
package com.zyz.bookshopmanage.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.zyz.bookshopmanage.pojo.User;
import java.util.Date;
/**
* @author 静小文
* @version 1.0
* @data 2022/9/9 14:28
*/
public class MyTokenUtil {
private static final long EXPIRE_TIME= 10*60*60*1000;
/**
* 密钥盐
*/
private static final String TOKEN_SECRET="test";
/**
* 签名生成
* @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;
}
}
}
提示
3.3 token认证过滤器
提示:登录、注册页一般不需要token。直接放行
package com.zyz.bookshopmanage.config;
import com.zyz.bookshopmanage.config.handler.MyTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
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;
import java.util.List;
import java.util.concurrent.*;
/**
* @author 静小文
* @version 1.0
* @data 2022/9/9 10:36
*/
@Configuration
public class CrosConfig implements WebMvcConfigurer {
private ExecutorService executorService = null;
/**
* 开启跨域
*
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
//设置允许跨域的路由
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
}
private MyTokenInterceptor tokenInterceptor;
//构造方法
public CrosConfig(MyTokenInterceptor tokenInterceptor) {
this.tokenInterceptor = tokenInterceptor;
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
executorService = new ThreadPoolExecutor(2,
2,
100,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(2),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
configurer.setTaskExecutor(new ConcurrentTaskExecutor(executorService));
configurer.setDefaultTimeout(30000);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
List<String> excludePath = new ArrayList<>();
//排除拦截,除了注册登录(此时还没token),其他都拦截
excludePath.add("/user/register");
excludePath.add("/user/login");
registry.addInterceptor(tokenInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(excludePath);
WebMvcConfigurer.super.addInterceptors(registry);
}
}
3.4 token认证
package com.zyz.bookshopmanage.config.handler;
import com.alibaba.fastjson.JSONObject;
import com.zyz.bookshopmanage.Utils.MyTokenUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author 静小文
* @version 1.0
* @data 2022/9/9 14:32
*/
@Component
public class MyTokenInterceptor 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");
//前端vue将token添加在请求头中
String token = request.getHeader("token");
if(token != null){
boolean result = MyTokenUtil.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;
}
}
3.5 登录成功、返回token
(value = "/user/login", method = RequestMethod.POST)
public Result login(@RequestBody User users) {
User user = userMapper.findByName(users.getUserName());
boolean flag = false;
if (user != null) {
try {
flag = MyMD5Util.validPassword(users.getPassword(), user.getPassword());
} catch (Exception e) {
e.printStackTrace();
}
} else {
return Result.error().data("errMessage", "不存在该用户");
}
if (flag) {
String token = MyTokenUtil.sign(user);
return Result.ok().data("token",token);
} else {
return Result.error().data("errMessage", "密码错误");
}
}
4、前端代码
4.1 路由守卫
提示:路由守卫写到main.js中可以、直接写在router/index.js中也可以
import Vue from 'vue'
import Router from 'vue-router'
// import HelloWorld from '@/components/HelloWorld'
import Register from '../views/main/Register'
import Login from '@/views/main/Login'
import Home from '@/views/main/Home'
import HomePage from '@/views/end/HomePage'
import AdminInfo from '@/views/end/AdminInfo'
import UserInfo from '@/views/end/UserInfo'
Vue.use(Router)
const router = new Router({
routes: [
{
path: '/',
name: '注册页',
component: Register
},
{
path: '/login',
name: '登录页',
component: Login
},
//菜单栏设置
{
path: '/Home',
name: "主界面",
component: Home,
redirect: '/HomePage',
children: [
{
path: '/HomePage',
name: '后台首页',
component: HomePage
},
{
path: '/AdminInfo',
name: '管理员信息界面',
component: AdminInfo
}, {
path: '/UserInfo',
name: "用户信息界面",
component: UserInfo
}
]
},
]
})
//全局前置守卫
router.beforeEach((to, from, next) => {
// to and from are both route objects. must call `next`.
if (to.path === '/register' || to.path === '/login' || to.path === '/') {
next();//直接放行
} else {
const token = localStorage.getItem('Authorization');
if (token === null || token === '') {
next('/login')
} else {
next()
}
}
})
export
4.2 Vuex的设计(设置共享状态)
对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
如果前端没有基础、这一部分可能不明所以然。请看这里:vuex的介绍
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const actions = {
}
const mutations = {
//修改token,将token放入localStorage中
changeLogin(state, user) {
state.Authorization = user.Authorization
localStorage.setItem('Authorization', user.Authorization)
},
//注销
logout(state) {
state.Authorization = null
localStorage.removeItem('Authorization')
}
}
const state = {
//存储token
Authorization: localStorage.getItem('Authorization') ? localStorage.getItem('Authorization') : '',
}
//向外暴露
const store = new Vuex.Store({
actions,
mutations,
state
})
export
4.3 将token放入到请求头中
提示:如果请求头中没有token、则后端获取不到token的值进行校验、认证不通过。
_axios.interceptors.request.use(
function (config) {
if (localStorage.getItem('Authorization')) {
config.headers.token = localStorage.getItem('Authorization');
}
return config;
},
function (error) {
return Promise.reject(error);
}
);
4.4 登录成功后、将后端返回的token存储
//简化操作
…mapMutations([‘changeLogin’]),这个就牵涉到组件间的通信、这里简化使用。不懂得请看开头给出的链接、这里不做过多讲解。有好几种方式可以实现组件间的通信
1、引入
import { mapMutations } from 'vuex';
。目的是简化组件间的通信2、使用简化形式:
...mapMutations(['changeLogin']),
{
//简化操作
...mapMutations([]),
submitForm(ruleForm) {
const _this this;
this.[ruleForm].validate((valid) > {
(valid) {
axios
.post( ,this.ruleForm)
.then(function (resp) {
console.log(resp);
(resp.data.code ) {
console.log(resp.data)
const token resp.data.data.token
console.log(token)
_this.changeLogin({Authorization:token})
_this.(, , {
confirmButtonText: ,
callback: (action) > {
_this..push();
},
});
} {
_this..error(resp.data.data.errMessage);
}
});
} {
Message.error()
return ;
}
});
},
restForm(formName) {
this.[formName].resetFields();
},
},
4.5、注销
在这里插入代码片
5、友情提示
如果安装报错、则更改安装的版本。默认是最新的。安装指定版本npm i vuex@3
。@后边跟安装指定版本
1、如果不了解Vuex、请了解后再来看(使用前请安装vuex。
npm i vuex
)2、如果不了解路由守卫、请了解后再来看 (使用前请安装router。
npm i router
)3、一定要将token放入到请求头中、后端要根据token来进行认证(使用前请安装axios。
npm i axios
)4、过滤器的拦截、不需要认证的直接放行。像登录、注册等