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注册处理方法

 效果:

登录页

 点击注册

 注册成功后跳转到登录页

 

posted @ 2024-08-02 16:37  少年阿川  阅读(433)  评论(0)    收藏  举报