分布式环境集成JWT(Java Web Token)

news2024/11/17 17:50:05

目录

  • 一,说明:
  • 二,Token、Session和Cookie比较
  • 三,Spring Boot项目集成JWT
    • 1,引入依赖
    • 2,Token工具类
    • 3,定义拦截器
    • 4,注册拦截器
    • 5,编写登录代码
    • 6,测试
  • 四,说明

一,说明:

  • Token的引入:客户端向服务端请求数据时一般都会加入验证信息,比如客户端在请求的信息中携带用户名、密码,服务端会校验用户名和密码是否正确,校验通过响应该客户端请求。但是每次都携带用户名和密码无疑有些繁琐,而且也不安全,在这种背景下,Token便应运而生。Token在计算机身份认证中是令牌的意思。
  • Token的定义:Token是服务端生成的一串字符串,用来作为客户端请求的一个令牌。Token是客户端第一次登录时,由服务端生成并将其返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需带上用户名和密码。

二,Token、Session和Cookie比较

  • Session和Cookie区别:
    • 数据存放位置不同:Session数据是存在服务器中的,Cookie数据存放在浏览器当中;
    • 安全程度不同:Session存储在服务器中,比Cookie存储在浏览器中,安全程度要高;
    • 性能使用程度不同:Session存储在服务器上,数量过多会影响服务器性能;
    • 数据存储大小不同:单个Cookie保存的数据不能超过4K,Session存储在服务端,根据服务器大小决定。
  • Token和Session区别:
    • Token是开发定义的,Seesion是http协议规定的;
    • Token一般不存储,Session存储在服务器中,且在分布式环境中,Session会失效;

三,Spring Boot项目集成JWT

  • 我们实现如下功能:客户端登录获取Token,请求时携带Token,服务端对携带的Token进行验证,若是有效Token则放行,非法或是过期Token拦截,给出说明信息后直接返回。
  • 代码中抛出的异常,以及返回的实体类信息,是自定义异常,详细可参考该博客自定义异常。

1,引入依赖

    <!--hutool-->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
    </dependency>
    <!---jwt(java web token)-->
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
    </dependency>

2,Token工具类

通过该工具类来生成Token,以及从请求头中获取token来获取当前用户的信息。

package com.tick.tack.utils;

import cn.hutool.core.date.DateUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.tick.tack.manager.entity.User;
import com.tick.tack.manager.service.IUserService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 final Logger log= LoggerFactory.getLogger(TokenUtils.class);

    private static IUserService staticUserService;
    
    @Resource
    private IUserService userService;

    @PostConstruct
    public void setUserService() {
        //必须加@Component注解后才会执行该段代码,在spring容器中初始化
        staticUserService = userService;
    }

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

    /**
     * 获取当前登录的用户信息
     */
    public static User getCurrentUser() {
        try {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            // 从请求头中获取token信息
            String token = request.getHeader("token");
            if (StringUtils.isNotBlank(token)) {
                String userAccount = JWT.decode(token).getAudience().get(0);
                return staticUserService.queryUserByAccount(userAccount);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

3,定义拦截器

AuthAccess是一个自定义的注解,在拦截器中判断如果方法上有加入该注解,则放行,不校验token

package com.tick.tack.common.interceptor;

import cn.hutool.jwt.JWTException;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.tick.tack.common.Constants;
import com.tick.tack.config.AuthAccess;
import com.tick.tack.exception.ServiceException;
import com.tick.tack.manager.entity.User;
import com.tick.tack.manager.service.IUserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

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

public class JWTInterceptor implements HandlerInterceptor {

    @Autowired
    private IUserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String token = request.getHeader("token");
        //如果不是映射到方法直接通过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        } else {
            // 判断是否为自定义注解AuthAccess,如果是,就不校验了,直接放行
            HandlerMethod h = (HandlerMethod) handler;
            AuthAccess authAccess = h.getMethodAnnotation(AuthAccess.class);
            if (authAccess != null) {
                return true;
            }
        }
        //执行认证
        if (StringUtils.isBlank(token)) {
            throw new ServiceException(Constants.CODE_401, "无token,请重新登录");
        }
        
        //获取token中的user id,验证是否合法
        String userAccount;
        try {
            userAccount = JWT.decode(token).getAudience().get(0);
        } catch (JWTException jwt) {
            throw new ServiceException(Constants.CODE_401, "token验证失败");
        }

        //根据token中的用户账号查询数据库信息
        User user = userService.queryUserByAccount(userAccount);
        if (user == null) {
            throw new ServiceException(Constants.CODE_401, "用户不存在,请重新登录");
        }

        //用户密码加签验证token
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
        try {
            //通过 verifier.verify() 方法检验 token,如果token不符合则抛出异常
            jwtVerifier.verify(token);
        } catch (Exception e) {
            throw new ServiceException(Constants.CODE_401, e.getMessage());
        }
        return true;
    }
}

自定义注解:

package com.tick.tack.config;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthAccess {
}

4,注册拦截器

将拦截器注册到SpringMVC中

package com.tick.tack.config;

import com.tick.tack.common.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
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor())
                //拦截的路径
                .addPathPatterns("/**") //拦截所有请求,通过判断token是否合法来决定是否需要登录
                //排除不校验的接口
                .excludePathPatterns(
                        "/loginUser", //排除路径的时候不用考虑全局上下文context-path
                        "/register",
                        //Swagger页面拦截取消
                        "/swagger-resources/**", "/webjars/**", "/v3/**", "/swagger-ui.html/**", "doc.html", "/error");
    }

    //考虑到UserService,此处需要注入一下
    @Bean
    public JWTInterceptor jwtInterceptor() {
        return new JWTInterceptor();
    }
}

5,编写登录代码

  • 1,登录实体类
@Data
public class LoginUser {
    // 登入用户名
    private String userAccount;
    // 登录密码
    private String password;
}
  • 2,token实体类
@Data
public class TickToken {
    // 用户名
    private String userAccount;
    // 密码
    private String password;
    // token
    private String token;
    // 到期时间
    private Date expireTime;
}
  • 3,系统登录控制类
@RestController
public class LoginController {
    @Autowired //按照类型注入
    @Qualifier(value = "loginServiceImpl")
    private ILoginService ILoginService;
    
    //登录系统
    @PostMapping("/loginUser")
    public Result loginSystem(@RequestBody LoginUser user) {
        if (StringUtils.isBlank(user.getUserAccount()) || StringUtils.isBlank(user.getPassword())) {
            return Result.error(Constants.CODE_400, "参数错误");
        }
        TickToken tickToken = ILoginService.loginSystem(user);

        return Result.success(tickToken);
    }
}
  • 4,业务逻辑实现类
public TickToken loginSystem(LoginUser user) {
        User one = userService.queryUserByAccount(user.getUserAccount());
        if (one != null && one.getPassword().equals(user.getPassword())) {
            TickToken tickToken = new TickToken();
            //生成token信息并返回
            String token = TokenUtils.getToken(one.getUserAccount(), one.getPassword());
            tickToken.setToken(token);
            // 设置过期时间:当前时间两小时以后
            tickToken.setExpireTime(DateUtil.offsetHour(new Date(),2));

            // 处理用户的菜单信息,在登录的时候返回给用户
            List<Menu> roleMenus = getRoleMenus(one);
            //tickToken.setMenus(roleMenus);
            return tickToken;
        } else {
            throw new ServiceException(Constants.CODE_600, "用户名或密码错误");
        }
    }

6,测试

  • 1,测试拦截
    当前未登录,测试拦截是否生效
@RestController
@RequestMapping("/demo")
public class DemoController {
	@GetMapping("/{id}")
    //@AuthAccess
    public Result getUser(@PathVariable("id") Integer id) {
        User user = new User(1, "zhangSan");
        return Result.success(user);
    }
}

未登录,也没有加相应注解,会提示没有token信息。
在这里插入图片描述

  • 2,登录获取token
    在这里插入图片描述* 3,携带token请求
    在这里插入图片描述
    如部分接口不希望被拦截,则为该接口方法加上@AuthAccess注解即可。

四,说明

该token是基于账户和密码来生成的一串字符串,并指定了过期时间,假如登录请求是在A机器实现,下一次请求在经过负载均衡后负载到B机器,B机器也可对其验证,因为token已经包括了全部的验证信息,服务器不保存相关信息,这样在分布式环境下也可正常使用。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++day7

一、 封装一个学生的类&#xff0c;定义一个学生这样类的vector容器, 里面存放学生对象&#xff08;至少3个&#xff09; 再把该容器中的对象&#xff0c;保存到文件中。 再把这些学生从文件中读取出来&#xff0c;放入另一个容器中并且遍历输出该容器里的学生。 二、XMind思…

后缀自动机SAM

https://www.luogu.com.cn/problem/P3804 fail&#xff1a;当前区间-1&#xff08;最短串 去掉最前面 的字符&#xff09; nxt&#xff1a;任意串 加上最后面 考虑新加入的字符为x&#xff0c;上一个为p&#xff0c;则 n x t [ p ] [ x ] c nxt[p][x]c nxt[p][x]c 当前的每…

vue h5项目 打包加载优化

打包美化: 1&#xff09;npx browserslistlatest --update-db 更新去除警告 2&#xff09;打包进度条 npm add progress-bar-webpack-plugin -D npm add webpackbar -D npm install --save-dev webpack-bundle-analyzer 优化&#xff1a; 1.各个插件和loader所花费的时间 …

OpenGL ES视频特效开发参考Shadertoy参数详解参考Godot文档

今天一个大厂的学员过来问shadertoy上一些参数的问题&#xff0c;因为我之前用过一段时间Godot引擎&#xff0c; 我清晰记得Godot官方文档有明确的解释&#xff0c;所以整理下发给做特效的同学。 Shadertoy是一个网站&#xff0c;它方便用户编写片段着色器并创造出纯粹的魔法。…

OpenHarmony设备截屏的5种方式

本文转载自《OpenHarmony设备截屏的5种方式 》&#xff0c;作者westinyang 目录 方式1&#xff1a;系统控制中心方式2&#xff1a;OHScrcpy投屏工具方式3&#xff1a;DevEcoStudio截屏功能方式4&#xff1a;hdc shell snapshot_display方式5&#xff1a;hdc shell wukong持续关…

【UI 设计】触摸界面设计

触摸界面设计是一种以触摸操作为主的用户界面设计。以下是一些触摸界面设计的要点&#xff1a; 界面布局&#xff1a;设计简洁、直观的界面布局&#xff0c;使用户可以快速找到所需的功能和信息。避免过于拥挤的布局&#xff0c;保持按钮和菜单的大小适中&#xff0c;以便用户能…

android多屏触摸相关的详解方案-安卓framework开发手机车载车机系统开发课程

背景 直播免费视频课程地址&#xff1a;https://www.bilibili.com/video/BV1hN4y1R7t2/ 在做双屏相关需求开发过程中&#xff0c;经常会有对两个屏幕都要求可以正确触摸的场景。但是目前我们模拟器默认创建的双屏其实是没有办法进行触摸的 修改方案1 静态修改方案 使用命令…

ssm+vue校园教务系统源码和论文

ssmvue校园教务系统源码和论文086 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm 1、课题背景 信息系统的目的是让人可以高效的进行工作&#xff0c;包括对信息的一-系列操作&#xff0c;如添加信息、修改信…

SystemVerilog interface详细介绍

1. Interface概念 System Verilog中引入了接口定义&#xff0c;接口与module 等价的定义&#xff0c;是要在其他的接口、module中直接定义&#xff0c;不能写在块语句中&#xff0c;跟class是不同的。接口是将一组线捆绑起来&#xff0c;可以将接口传递给module。 2. 接口的优…

李宏毅 2022机器学习 HW2 strong baseline 上分路线

strong baseline上分路线 baseline增加concat_nframes &#xff08;提升明显&#xff09;增加batchnormalization 和 dropout增加hidden layer宽度至512 &#xff08;提升明显&#xff09; 提交文件命名规则为 prediction_{concat_nframes}[{n_hidden_layers}{dropout}_bn].c…

没有 JavaScript 计时器的自动播放轮播 - CSS 动画

先看效果&#xff1a; 再看代码&#xff08;查看更多&#xff09;&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>计时器</title><style>* {padding: 0;margin: 0;box-siz…

springcloud-gateway简述

Spring Cloud Gateway 是一个用于构建 API 网关的项目&#xff0c;它是 Spring Cloud 生态系统中的一部分&#xff0c;旨在为微服务架构提供动态路由、负载均衡、安全性和监控等功能。 网关工程对应pom文件 <?xml version"1.0" encoding"UTF-8"?>…

kafka消息系统实战

kafka是什么&#xff1f; 是一种高吞吐量的、分布式、发布、订阅、消息系统 1.导入maven坐标 <dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-clients</artifactId><version>2.4.1</version></dependency&…

Python文本终端GUI框架详解

今天笔者带大家&#xff0c;梳理几个常见的基于文本终端的 UI 框架&#xff0c;一睹为快&#xff01; Curses 首先出场的是 Curses。 Curses 是一个能提供基于文本终端窗口功能的动态库&#xff0c;它可以: 使用整个屏幕 创建和管理一个窗口 使用 8 种不同的彩色 为程序提供…

web之利用延迟实现复杂动画、animation

文章目录 效果图htmlstyleJavaScript 效果图 html <div class"container"><div class"ball"></div><input type"range" min"0" max"1" step"0.01" /> </div>style body {display…

有机器视觉工程师假装在工作

没有节假日&#xff0c;没有任何业务时间&#xff0c;去充实自己&#xff0c;甚至都没有时间陪女朋友&#xff0c;甚至都没有时间找女朋友。 没有人休息的工作&#xff1a; 每天上班三个地点&#xff0c;住宿&#xff0c;现场&#xff0c;吃饭的地方。搞得和高考似的&#xff…

【算法训练-哈希】两数之和、三数之和

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是两数之和和三数之和&#xff0c;使用哈希这个基本的数据结构来实现 两数之和【EASY】 照例先从简单往难搞 题干 输入&#xff1a; [3,2,4],6返…

【LeetCode-中等题】148. 排序链表

文章目录 题目方法一&#xff1a;集合排序&#xff08;核心是内部的排序&#xff09;方法二&#xff1a; 优先队列&#xff08;核心也是内部的排序&#xff09;方法三&#xff1a;归并排序&#xff08;带递归&#xff09; 从上往下方法四&#xff1a;归并排序&#xff08;省去递…

【juc】读写锁ReentrantReadWriteLock

目录 一、说明二、读读不互斥2.1 代码示例2.2 截图示例 三、读写互斥3.1 代码示例3.2 截图示例 四、写写互斥4.1 代码示例4.2 截图示例 五、注意事项5.2.1 代码示例5.2.2 截图示例 一、说明 1.当读操作远远高于写操作时&#xff0c;使用读写锁让读读可以并发&#xff0c;来提高…

excel功能区(ribbonx)编程笔记--2 button控件与checkbox控件

我们上一章简单先了解了ribbonx的基本内容,以及使用举例实现自己修改ribbox的内容,本章紧接上一章,先讲解一下ribbonx的button控件。 在功能区的按钮中,可以使用内置图像或提供自已的图像,可以指定大按钮或者更小的形式,添加少量的代码甚至可以同时提供标签。此外,可以利…