springboot+vue前后端分离项目-项目搭建16-集成JWT token权限验证 + 注册功能新增
1. 对之前的代码改造,之前将user存储到sessionStorage,改成存储到localStorage,全局搜索修改

之前Result.code等于0代表success,改成200代表success, vue文件全局搜索修改

一、前端部分
1. 改造request.js,登录时将user已经存储到localStorage里,这里将user获取到,将user里的token放到请求头,后台根据请求头里的token进行校验

2. 后台token校验不通过,返回401,这时重定向到登录页

3. 修改退出系统按钮,退出时清空token和用户信息


二、后端部分
1. pom.xml 里引入java-jwt包

2. 创建 com/example/demo/common/JwtInterceptor.java 文件,内容是拦截规则
package com.example.demo.common; import cn.hutool.core.util.StrUtil; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.exceptions.JWTVerificationException; import com.example.demo.entity.User; import com.example.demo.exception.ServiceException; import com.example.demo.mapper.UserMapper; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; public class JwtInterceptor implements HandlerInterceptor { @Resource private UserMapper userMapper; public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler){ String token = request.getHeader("token"); // header里面传的参数 if (StrUtil.isBlank(token)){ token = request.getParameter("token"); // url参数 } // 方法上有AuthAccess注解时,直接返回true,即取消拦截,直接通过 if (handler instanceof HandlerMethod) { AuthAccess annotation = ((HandlerMethod) handler).getMethodAnnotation(AuthAccess.class); if (annotation != null) { return true; } } // 执行认证 if (StrUtil.isBlank(token)) { throw new ServiceException("401", "请登录"); //401 权限错误 } // 获取token中的userId String userID; try { userID = JWT.decode(token).getAudience().get(0); // JWT.decode(token) 解码 } catch (JWTDecodeException e) { throw new ServiceException("401", "请登录"); } // 根据token中的userId查询数据库 User user = userMapper.selectById(Integer.valueOf(userID)); if (user == null) { throw new ServiceException("401","请登录"); //权限错误 } // 通过用户密码加密后生成一个验证器 JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build(); try { jwtVerifier.verify(token); // 验证token } catch (JWTVerificationException e) { throw new ServiceException("401", "请登录"); } return true; } }
3. 创建 com/example/demo/common/config/InterceptorConfig.java 文件,将上一步自定义的 JwtInterceptor 拦截规则传入,使规则生效,定义拦截路径和不拦截路径
package com.example.demo.common.config; import com.example.demo.common.JwtInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; @Configuration public class InterceptorConfig extends WebMvcConfigurationSupport { protected void addInterceptors(InterceptorRegistry registry){ registry.addInterceptor(jwtInterceptor()) //配置jwt的拦截器规则,传入JwtInterceptor自定义的规则 .addPathPatterns("/**") //拦截所有的请求路径 .excludePathPatterns("/user/login","/files/*"); //将特定请求取消拦截,也可以通过AuthAccess注解实现 super.addInterceptors(registry); } @Bean public JwtInterceptor jwtInterceptor() { return new JwtInterceptor(); } }
3. 创建 com/example/demo/exception/ServiceException.java,拦截成功时会抛出 ServiceException 异常,这是个自定义的异常
package com.example.demo.exception; import lombok.Getter; @Getter public class ServiceException extends RuntimeException { private final String code; public ServiceException(String msg){ super(msg); this.code = "500"; } public ServiceException(String code,String msg){ super(msg); this.code = code; } }
4. 创建com/example/demo/exception/GlobalException.java,定义 抛出 ServiceException 异常时进行的处理,返回Result
package com.example.demo.exception; import com.example.demo.common.Result; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; @ControllerAdvice public class GlobalException { @ExceptionHandler(ServiceException.class) @ResponseBody public Result<?> serviceException(ServiceException e){ return Result.error(e.getCode(), e.getMessage()); } }
5. 第二种 取消拦截的方式,创建 com/example/demo/common/AuthAccess.java 注解
package com.example.demo.common; import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AuthAccess { }
在方法上使用这个注解,拦截规则里获取 方法上的这个注解,如果获取到就取消拦截


6.user实体类增加token属性

7.改造登录方法,账号密码校验不通过时抛出 ServiceException 异常,账号密码正确时,生成token,存储到user里返回

测试:
登录时不进入拦截器,登录成功后跳转到用户管理,查询全部用户,这时跳转到拦截器,可以看到token已经生成并由前端传到后端

页面本地存储里也有token

注册功能
1.改造vue/src/views/LoginView.vue,新增注册按钮,点击后跳转到注册页面

2.新增注册页面vue/src/views/Register.vue
<template> <div style="width: 100%; height: 100vh; background-color: #0a5b22; overflow: hidden"> <div style="width: 400px; margin: 150px auto"> <div style="color: #cccccc; font-size: 30px; text-align: center; padding: 30px 0">欢迎注册后台管理系统</div> <el-form label-position="right" :model="form" size="normal" :rules="rules" ref="form"> <el-form-item prop="username"> <el-input :prefix-icon="Avatar" v-model="form.username" placeholder="请输入账号"/> </el-form-item> <el-form-item prop="password"> <el-input :prefix-icon="Lock" v-model="form.password" show-password placeholder="请输入密码"/> </el-form-item> <el-form-item prop="confirmPass"> <el-input :prefix-icon="Lock" v-model="form.confirmPass" show-password placeholder="请确认密码"/> </el-form-item> <el-form-item> <el-radio v-model="form.role" :label="1">管理员</el-radio> <el-radio v-model="form.role" :label="2">普通用户</el-radio> </el-form-item> <el-form-item> <el-button style="width: 100%;" type="primary" @click="register">注 册</el-button> </el-form-item> </el-form> <div style="display: flex"> <div style="color: #cccccc">已经有帐号了?请 <span style="color: #0f9876; cursor: pointer" @click="$router.push('/login')">登录</span></div> </div> </div> </div> </template> <script> import request from "@/utils/request"; export default { name: "LoginView", data(){ const validatePass = (rule, confirmPass, callback) => { //自定义校验规则 if (confirmPass === ''){ callback(new Error('请确认密码')) } else if (confirmPass !== this.form.password){ callback(new Error('两次输入的密码不一致')) } else { callback() } }; return { form: { username: "", password: "", confirmPass: "", role: "" }, rules: { username: [ { required: true, message: '请输入用户名', trigger: 'blur' } ], password: [ { required: true, message: '请输入密码', trigger: 'blur' } ], confirmPass: [ { validator: validatePass, trigger: 'blur' } ] } } }, created() { }, methods: { register() { this.$refs['form'].validate((valid) => { if(valid){ request.post("/user/register", this.form).then(res => { console.log(res) if (res.code === '200') { this.$message({ type: "success", message: "注册成功" }) this.$router.push("/login") //登陆成功后跳转到登录页 } else { this.$message({ type: "error", message: res.msg }) } }) } }); } } } </script> <script setup> import { Avatar,Lock } from '@element-plus/icons-vue' </script> <style scoped> </style>
3. 路由同步调整

4. 后端增加register注册处理方法

效果:
登录页

点击注册

注册成功后跳转到登录页


浙公网安备 33010602011771号