前端需要掌握的知识:
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、过滤器的拦截、不需要认证的直接放行。像登录、注册等