SpringBoot+vue实现token认证登录

目录

后端(Spring Boot)

1. 创建用户实体和数据库表

2. 用户注册和登录接口

3. JWT Token生成

4. JWT Token验证

前端(Vue.js)

1. 用户界面

2. 发送登录请求

3. 接收并存储Token

4. 发送请求时携带Token

5. 路由守卫

注意事项


在Spring Boot和Vue.js中实现token认证登录是一种常见的前后端分离的认证机制。

以下是实现这一机制的基本步骤:

后端(Spring Boot)

1. 创建用户实体和数据库表

首先,你需要定义一个用户实体,比如User,并为其创建相应的数据库表。

@TableField(exist = false)
private String token; // 表示我们数据库没有这个字段,但是在前端我们需要返回这个字段

2. 用户注册和登录接口

在Spring Boot应用中创建用于注册和登录的REST API。

@Override
public User login(UserPasswordDTO userPasswordDTO) {
    User one = getUserInfo(userPasswordDTO);
    if (one != null) {
        BeanUtil.copyProperties(one, userPasswordDTO, true);
        // 设置token
        String token = TokenUtils.genToken(one.getId().toString(), one.getPassword());
        one.setToken(token);
        return one;
    }else{
        return null;
    }
}

private User getUserInfo(UserPasswordDTO userPasswordDTO){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("username", userPasswordDTO.getUsername());
    queryWrapper.eq("password", SecureUtil.md5(userPasswordDTO.getPassword()));
    List<User> one =  userMapper.selectList(queryWrapper);
    if (one.size()==0){
        return null;
    }
    return one.get(0);
}

3. JWT Token生成

用户登录成功后,生成一个JWT(JSON Web Token),并将其发送回客户端。

首先在pom.xml文件中导入jwt包:

<!-- JWT -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.10.3</version>
</dependency>

在config包中新建TokenUtils类:

package com.lyk.xuelang.config;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.lyk.xuelang.entity.User;
import com.lyk.xuelang.mapper.UserMapper;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;

@Component
public class TokenUtils {
    private static UserMapper staticUserMapper;

    @Resource
    private UserMapper userMapper;

    @PostConstruct
    public void setUserService() {
        staticUserMapper = userMapper;
    }

    /**
     * 生成token
     *
     * @return
     */
    public static String genToken(String userId, String sign) {
        return JWT.create().withAudience(userId) // 将 user id 保存到 token 里面,作为载荷
                .withExpiresAt(DateUtil.offsetHour(new Date(), 2)) // 2小时后token过期
                .sign(Algorithm.HMAC256(sign)); // 以 password 作为 token 的密钥
    }

    /**
     * 获取当前登录的用户信息
     *
     * @return user对象
     */
    public static User getCurrentUser() {
        try {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String token = request.getHeader("token");
            if (StrUtil.isNotBlank(token)) {
                String userId = JWT.decode(token).getAudience().get(0);
                return staticUserMapper.selectById(Integer.valueOf(userId));
            }
        } catch (Exception e) {
            return null;
        }
        return null;
    }
}

4. JWT Token验证

在config包下新建interceptor包,然后新建JwtInterceptor类:

package com.lyk.xuelang.config.interceptor;

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.lyk.xuelang.common.Constants;
import com.lyk.xuelang.entity.User;
import com.lyk.xuelang.exception.ServiceException;
import com.lyk.xuelang.service.IUserService;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class JwtInterceptor implements HandlerInterceptor {
    @Resource
    private IUserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("token");
        if(!(handler instanceof HandlerMethod)){
            return true;
        }

        // 执行认证
        if (StrUtil.isBlank(token)) {
            throw new ServiceException(Constants.CODE_401, "无token验证失败");
        }
        // 获取 token 中的 userId
        String userId;
        try {
            userId = JWT.decode(token).getAudience().get(0);
        } catch (JWTDecodeException j) {
            String errMsg = "token验证失败,请重新登录";
            throw new ServiceException(Constants.CODE_401, errMsg);
        }
        // 根据token中的userid查询数据库
        User user = userService.getById(userId);
        if (user == null) {
            throw new ServiceException(Constants.CODE_401, "用户不存在,请重新登录");
        }
        // 用户密码加签验证 token
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
        try {
            jwtVerifier.verify(token); // 验证token
        } catch (JWTVerificationException e) {
            throw new ServiceException(Constants.CODE_401, "token验证失败,请重新登录");
        }
        return true;
    }
}

添加自定义拦截器

在config包下新建InterceptorConfig类:

package com.lyk.xuelang.config;

import com.lyk.xuelang.config.interceptor.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.WebMvcConfigurer;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

        @Override
        // 加自定义拦截器JwtInterceptor,设置拦截规则
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(jwtInterceptorl())
                    .addPathPatterns("/**") //拦截所有请求,通过判断token是否合法来决定是否登录
                    .excludePathPatterns("/login","/role/page","/**/export","/**/import");//排除这些接口,也就是说,这些接口可以放行
        }
       @Bean
       public JwtInterceptor jwtInterceptorl(){
            return new JwtInterceptor();
        }
}

前端(Vue.js)

1. 用户界面

创建登录表单,允许用户输入用户名和密码。

<div class="login-container">
  <el-card class="login-card">
    <h2 class="login-title">仓库管理系统</h2>
    <el-form :model="loginForm" :rules="rules" ref="loginForm" label-width="20">
      <el-form-item label="用户名" prop="username">
        <el-input v-model="loginForm.username" placeholder="请输入用户名"></el-input>
      </el-form-item>
      <el-form-item label="密码" prop="password">
        <el-input type="password" v-model="loginForm.password" placeholder="请输入密码"></el-input>
      </el-form-item>
      <el-form-item style="margin: 10px 0;text-align: right;" label="温馨提示:忘记密码?联系管理员!">
        <el-button type="primary" autocomplete="off" @click="submitForm">登录</el-button>
      </el-form-item>
    </el-form>
  </el-card>
</div>

2. 发送登录请求

用户提交表单后,前端发送登录请求到后端的登录接口。

login.js代码如下:

import request from '@/utils/request'

// 用户登录
export function login (user) {
  return request({
    url: '/login',
    method: 'post',
    data: user
  })
}

Home.vue页面代码如下:

<script>
import { login } from '@/api/login'

export default {
  data () {
    return {
      loginForm: {
        username: '',
        password: ''
      },
      rules: {
        username: [
          { required: true, message: '请输入用户名!', trigger: 'blur' }
        ],
        password: [
          { required: true, message: '请输入密码!', trigger: 'blur' }
        ]
      }
    }
  },
  methods: {
    submitForm () {
      this.$refs.loginForm.validate(async (valid) => {
        if (valid) {
          // 在这里添加你的登录逻辑
          const res = await login(this.loginForm)
          if (res.code === 200) {
            this.$message.success('登录成功')
            localStorage.setItem('user', JSON.stringify(res.data)) // 存储用户信息到浏览器
            localStorage.setItem('token', JSON.stringify(res.data.token))
            this.$router.push('/main')
          } else {
            this.$message.error(res.msg)
          }
        } else {
          this.$message.error('账号密码错误,请重新输入!')
          return false
        }
      })
    }
  }
}
</script>

3. 接收并存储Token

登录成功后,前端接收JWT Token,并将其存储在本地存储(localStorage)或Vuex状态管理中。

// 在这里添加你的登录逻辑
const res = await login(this.loginForm)
if (res.code === 200) {
  this.$message.success('登录成功')
  localStorage.setItem('user', JSON.stringify(res.data)) // 存储用户信息到浏览器
  localStorage.setItem('token', JSON.stringify(res.data.token))
  this.$router.push('/main')
}

4. 发送请求时携带Token

在发送需要认证的请求时,前端需要在请求头中携带JWT Token。

// 添加请求拦截器,一下内容是axios的拦截器,可以不用写
request.interceptors.request.use(config => {
  config.headers['Content-Type'] = 'application/json;charset=utf-8'
  const user = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user')) : null
  if (user) {
    config.headers.token = user.token
  }

  return config
}, error => {
  return Promise.reject(error)
})

5. 路由守卫

使用Vue Router的路由守卫来保护需要认证的路由。

{ path: '/dashboard', 
    component: Dashboard,
    meta: 
       {requiresAuth: true} 
} // 标记需要验证的路由
router.beforeEach((to, from, next) => {
  const auth = require('@/router/auth').default // 引入认证守卫
  auth.redirectIfNotAuthenticated(to, from, next)
})

auth.js代码如下:

export default {
  isAuthenticated () {
    // 这里应该根据你的应用逻辑来检查用户是否登录
    // 例如,检查本地存储(localStorage)中是否有token
    return localStorage.getItem('token') !== null
  },

  redirectIfNotAuthenticated (to, from, next) {
    if (!this.isAuthenticated()) {
      to.path !== '/login' && to.matched.some(record => record.meta.requiresAuth) ? next({ path: '/login' }) : next()
    } else {
      next()
    }
  }
}

注意事项

  • 安全性:确保使用HTTPS来传输JWT Token。
  • Token存储:考虑使用HttpOnly的Cookie来存储Token,以避免XSS攻击。
  • Token过期:JWT Token应该有过期时间,并且后端需要处理Token的刷新。

这只是一个简单的示例,实际应用中可能需要更复杂的逻辑,比如密码加密、Token刷新机制、用户角色和权限管理等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/603416.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[初阶数据结构】单链表

前言 &#x1f4da;作者简介&#xff1a;爱编程的小马&#xff0c;正在学习C/C&#xff0c;Linux及MySQL。 &#x1f4da;本文收录于初阶数据结构系列&#xff0c;本专栏主要是针对时间、空间复杂度&#xff0c;顺序表和链表、栈和队列、二叉树以及各类排序算法&#xff0c;持…

添砖Java之路其三——自增自减运算符,数据转换与原码反码补码。

目录 运算符&#xff1a; 转换&#xff1a; 隐式转换&#xff1a; 小范围数据可以直接可以给大范围数据&#xff1a; 这里做了一张图范围向下兼容表​编辑 运算时&#xff0c;数据范围小的和数据范围大的&#xff0c;需要讲运算范围小的提升为运算范围大的同类&#xff0c…

软考系列必过资料分享-系统架构师-系统分析师-信息系统项目管理师

建议,写在前面 知识点是公用的,原则上不分新旧。每年会有少部分的题目切合当前时间段&#xff08;也是通过旧的知识演变的&#xff09; 信息系统项目管理师证书 系统架构师证书 系统分析师证书 资料分享 关注公众号 回复 信息系统项目管理师资料 即可获取信息系统项目管理师资…

如何使用香草看涨期权进行投机?

如何使用香草看涨期权进行投机&#xff1f; 香草看涨期权&#xff0c;通常也称为香草期权&#xff0c;是金融市场上的一种金融衍生品&#xff0c;由券商或金融机构推出。使用香草看涨期权进行投机&#xff0c;主要依赖于对市场走势的预测和对杠杆效应的运用。以下是一些关键步…

【前端】-【前端文件操作与文件上传】-【前端接受后端传输文件指南】

目录 前端文件操作与文件上传前端接受后端传输文件指南 前端文件操作与文件上传 一、前端文件上传有两种思路&#xff1a; 二进制blob传输&#xff1a;典型案例是formData传输&#xff0c;相当于用formData搭载二进制的blob传给后端base64传输&#xff1a;转为base64传输&…

力扣HOT100 - 35. 搜索插入位置

解题思路&#xff1a; 二分法模板 class Solution {public int searchInsert(int[] nums, int target) {int left 0;int right nums.length - 1;while (left < right) {int mid left ((right - left) >> 1);if (nums[mid] target)return mid;else if (nums[mid…

spring模块(六)spring监听器(1)ApplicationListener

一、介绍 1、简介 当某个事件触发的时候&#xff0c;就会执行的方法块。 当然&#xff0c;springboot很贴心地提供了一个 EventListener 注解来实现监听。 2、源码&#xff1a; package org.springframework.context;import java.util.EventListener; import java.util.fu…

深入解析智能指针:从实践到原理

&#x1f466;个人主页&#xff1a;晚风相伴 &#x1f440;如果觉得内容对你有所帮助的话&#xff0c;还请一键三连&#xff08;点赞、关注、收藏&#xff09;哦 如果内容有错或者不足的话&#xff0c;还望你能指出。 目录 智能指针的引入 内存泄漏 RAII 智能指针的使用及原…

安卓LayoutParams浅析

目录 前言一、使用 LayoutParams 设置宽高二、不设置 LayoutParams2.1 TextView 的 LayoutParams2.2 LinearLayout 的 LayoutParams 三、getLayoutParams 的使用四、setLayoutParams 的作用五、使用 setWidth/setHeight 设置宽高 前言 先来看一个简单的布局&#xff0c;先用 x…

百元挂耳式耳机哪款好?五款高品质一流机型不容错过

开放式耳机以其独特的不入耳设计&#xff0c;大大提升了佩戴的舒适度。相较于传统的入耳式耳机&#xff0c;它巧妙地避免了对耳朵的压迫&#xff0c;降低了中耳炎等潜在风险。不仅如此&#xff0c;开放式耳机还能让你保持对周边声音的灵敏度&#xff0c;无论是户外跑步还是骑行…

Day08-JavaWeb开发-MySQL(多表查询内外连接子查询事务索引)Mybatis入门

1. MySQL多表查询 1.1 概述 1.2 内连接 -- 内连接 -- A. 查询员工的姓名, 及所属的部门名称(隐式内连接实现) select tb_emp.name, tb_dept.name from tb_emp,tb_dept where tb_emp.dept_id tb_dept.id;-- 起别名 select * from tb_emp e, tb_dept d where e.dept_id d.id…

信息化飞速发展下,源代码防泄密方案该如何选择?常见四种有效方案分享

信息化时代发展迅速&#xff0c;数据防泄露一词也频繁的出现在我们身边。无论企业或政府单位&#xff0c;无纸化办公场景越来越多&#xff0c;数据泄露的时间也层出不穷。例如&#xff1a;世界最大职业中介网站Monster遭到黑客大规模攻击&#xff0c;黑客窃取在网站注册的数百万…

The Sandbox 案例|Web3 项目引领娱乐业的发展

Web3 如何通过 RZR 系列等项目开创娱乐新纪元。 我们已经看到技术和 Web3 如何颠覆金融和银行等行业&#xff0c;然而娱乐业在不断变化的环境中似乎发展滞后。传统的制片厂生态系统、高成本制作以及历史悠久的运作模式一直占据主导地位&#xff0c;而 Web3 项目的出现为创作者提…

CondaHTTPError: HTTP 404 NOT FOUND for url xxx

今天在创建新环境的时候给我报这个错 根据报错内容大概猜测&#xff0c;连接不到清华源&#xff1f; 然后我去清华源那边重新复制了一下配置 点这里跳转清华源 复制这一块东西 然后打开C盘->用户->找到.condarc文件打开 复制粘贴把里面的东西覆盖了就行 然后保存退出…

【excel】统计单元格内数字/字符串的数量

文章目录 一、问题二、步骤&#xff08;一&#xff09;将A1中的数字分解出来&#xff0c;在不同的单元格中显示&#xff08;二&#xff09;统计每个数字出现的个数&#xff08;三&#xff09;去重 三、尾巴 一、问题 单元格中有如下数值&#xff1a;12345234534545&#xff0c…

给excel中某列数值前后分别添加单引号

将这一列的数值&#xff0c;复制到 Sublime Text中&#xff0c;并CtrlA全选内容后&#xff0c;按CtrlH 选中工具栏左上角的 如果要给前面添加符号&#xff08;如单引号&#xff09;&#xff0c;则在Fina查找内容中&#xff0c;填入 ^ 如果要给后面添加符号&#xff08;如单引…

【操作系统】内存管理——地址空间连续内存分配与非连续内存分配

内存管理——地址空间&连续内存分配与非连续内存分配 一、地址空间1.1 计算机存储层次1.2 地址和地址空间1.3 虚拟存储的作用 二、内存分配2.1 静态内存分配2.2 动态内存分配 三、连续内存分配3.1 动态分区分配3.2 伙伴系统&#xff08;Buddy System&#xff09; 四、非连续…

5.1 Java全栈开发前端+后端(全栈工程师进阶之路)-服务端框架-MyBatis框架-相信我看这一篇足够

0.软件框架技术简介 软件框架&#xff08;software framework&#xff09;&#xff0c;通常指的是为了实现某个业界标准或完成特定基本任务的软件组件规范&#xff0c;也 指为了实现某个软件组件规范时&#xff0c;提供规范所要求之基础功能的软件产品。 框架的功能类似于基础设…

cufftPlanMany参数说明

背景 最近在看cufft这个库&#xff0c;传统的cufftPlan3d()这种plan接口逐渐被nvidia舍弃了&#xff0c;说是要用最新的cufftPlanMany&#xff0c;这个函数呢又依赖一个什么Advanced Data Layout(地址)&#xff0c;最终把这个api搞得乌烟瘴气很难理解&#xff0c;为了理解自己…

瓷器三维虚拟展示编辑平台为您量身定制高效实惠的展示方案

在竞争激烈的机械产品行业中&#xff0c;如何脱颖而出、展现产品魅力与企业实力?深圳vr公司华锐视点以其独特的三维动画设计制作服务&#xff0c;为您量身定制全方位的展示方案&#xff0c;让您的机械产品在市场中熠熠生辉。 全方位展示&#xff0c;细节尽收眼底 我们的三维展…
最新文章