【第3章】SpringBoot实战篇之登录接口(含JWT和拦截器)

文章目录

  • 前言
  • 一、JWT
    • 1. 什么是JWT
    • 2. 使用场景
    • 3. 结构
      • 3.1 Header
      • 3.2 Payload
      • 3.3 Signature
    • 4. 使用
  • 二、案例
    • 1.引入库
    • 2.JwtUtils
    • 3. UserController1
    • 4. ArticleController
  • 三、拦截器
    • 1. 定义拦截器
    • 2. 注册拦截器
  • 四、测试
    • 1. 登录
    • 2. 无token
    • 3. 有token
    • 4. 全局配置
  • 总结


前言

前面已经完成了用户注册,这里我们基于之前的用户表,完成用户的登录接口。


一、JWT

1. 什么是JWT

JWT全称JSON Web Token,JSON Web令牌(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于作为JSON对象在各方之间安全地传输信息。此信息是经过数字签名的,因此可以验证和信任。JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。

虽然JWT可以加密,也可以在各方之间提供保密性,但我们将专注于签名代币。签名令牌可以验证其中包含的声明的完整性,而加密令牌则向其他方隐藏这些声明。当使用公钥/私钥对对令牌进行签名时,签名还证明只有持有私钥的一方才是签名方。

2. 使用场景

以下是JSON Web令牌有用的一些场景:

授权:这是使用JWT最常见的场景。一旦用户登录,每个后续请求都将包括JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是目前广泛使用JWT的一个功能,因为它的开销很小,并且能够在不同的域中轻松使用。

信息交换:JSON Web令牌是在各方之间安全传输信息的好方法。因为JWT可以签名——例如,使用公钥/私钥对——所以你可以确保发送者就是他们所说的那个人。此外,由于签名是使用标头和有效载荷计算的,因此还可以验证内容是否未被篡改。

3. 结构

.作为各部分分隔符
xxxxx.yyyyy.zzzzz

  • Header
  • Payload
  • Signature

3.1 Header

算法可以使用 HMAC SHA256 or RSA

{
  "alg": "HS256",
  "typ": "JWT"
}

java-jwt 支持以下签名和验证算法:

JWSAlgorithmDescription
HS256HMAC256HMAC with SHA-256
HS384HMAC384HMAC with SHA-384
HS512HMAC512HMAC with SHA-512
RS256RSA256RSASSA-PKCS1-v1_5 with SHA-256
RS384RSA384RSASSA-PKCS1-v1_5 with SHA-384
RS512RSA512RSASSA-PKCS1-v1_5 with SHA-512
ES256ECDSA256ECDSA with curve P-256 and SHA-256
ES384ECDSA384ECDSA with curve P-384 and SHA-384
ES512ECDSA512ECDSA with curve P-521 and SHA-512

3.2 Payload

有效载荷用来存放一些用户信息,但不建议存放用户私密信息,以免造成不必要的安全隐患。

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

3.3 Signature

签名用于验证消息在发送过程中没有更改,并且,在使用私钥签名的令牌的情况下,它还可以验证JWT的发送者是它所说的那个人。

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

4. 使用

在身份验证中,当用户使用其凭据成功登录时,将返回一个JSON Web令牌。由于令牌是凭据,因此必须非常小心地防止出现安全问题。一般来说,您不应将令牌保存的时间超过所需的时间。

每当用户想要访问受保护的路由或资源时,用户代理都应该发送JWT,通常在Authorization头中使用Bearer模式。标头的内容应如下所示:

Authorization: Bearer <token>

请注意,如果您通过HTTP头发送JWT令牌,则应尽量防止它们变大。有些服务器不接受超过8 KB的标头。如果您试图在JWT令牌中嵌入太多信息,例如通过包括所有用户的权限,您可能需要一个替代解决方案,如Auth0细粒度授权。

如果令牌是在Authorization标头中发送的,则跨来源资源共享(CORS)不会成为问题,因为它不使用cookie。

下图显示了如何获取JWT并将其用于访问API或资源:
在这里插入图片描述

二、案例

1.引入库

<dependency>
  <groupId>com.auth0</groupId>
  <artifactId>java-jwt</artifactId>
  <version>4.4.0</version>
</dependency>

2.JwtUtils

package org.example.springboot3.bigevent.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
import java.util.Map;

public class JwtUtils {

    private static final String SECRET = "167589447321";

    /**
     * 生成token
     * @param claims 用户信息
     * @return String
     */
    public static String create(Map<String, Object> claims) {
        return JWT.create()
                .withClaim("claims", claims)
                .withIssuer("auth0")
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24))
                .sign(Algorithm.HMAC256(SECRET));
    }

    /**
     * 验证token
     * @param token token
     * @return boolean
     */
    public static boolean verify(String token) {
        DecodedJWT decodedJWT;
        try {
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            JWTVerifier verifier = JWT.require(algorithm)
                    // specify any specific claim validations
                    .withIssuer("auth0")
                    // reusable verifier instance
                    .build();

            decodedJWT = verifier.verify(token);
        } catch (JWTVerificationException exception){
            // Invalid signature/claims
            return false;
        }
        return true;
    }

    /**
     * 解析token数据
     * @param token token
     * @return Map
     */
    public static Map<String, Object> getClaims(String token) {
        return JWT.require(Algorithm.HMAC256(SECRET))
                .withIssuer("auth0")
                .build()
                .verify(token)
                .getClaim("claims")
                .asMap();
    }

}

3. UserController1

package org.example.springboot3.bigevent.controller;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Pattern;
import org.example.springboot3.bigevent.entity.Result;
import org.example.springboot3.bigevent.entity.User;
import org.example.springboot3.bigevent.service.UserSerivce;
import org.example.springboot3.bigevent.utils.JwtUtils;
import org.example.springboot3.bigevent.utils.Md5Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;

/**
 * Create by zjg on 2024/5/22
 */
@RequestMapping("/user/")
@RestController
@Validated
public class UserController1 {
    @Autowired
    UserSerivce userSerivce;
    @RequestMapping("register")
    public Result register(@Pattern(regexp = "^\\S{6,20}$",message = "用户名长度为6-20位") String username,@Pattern(regexp = "^\\S{8,20}$",message = "密码为8-20位") String password){
        User user=userSerivce.findUserByName(username);
        if(user==null){//用户不存在,可以注册
            int i=userSerivce.addUser(username,password);
            if(i!=1){
                return Result.error("失败注册,请稍后重新注册!");
            }
        }else{
            return Result.error("该用户已存在,请重新注册!");
        }
        return Result.success();
    }
    @RequestMapping("register1")
    public Result register1(@Valid User user){
        if(userSerivce.findUserByName(user.getUsername())==null){//用户不存在,可以注册
            int i=userSerivce.addUser(user.getUsername(),user.getPassword());
            if(i!=1){
                return Result.error("失败注册,请稍后重新注册!");
            }
        }else{
            return Result.error("该用户已存在,请重新注册!");
        }
        return Result.success();
    }
    @RequestMapping("login")
    public Result login(@Valid User loginUser){
        String message="用户名/密码不正确";
        User user = userSerivce.findUserByName(loginUser.getUsername());
        if(user!=null){//用户存在
            if(user.getPassword().equals(Md5Util.getMD5String(loginUser.getPassword()))){//密码正确
                Map<String,Object> claims=new HashMap();
                claims.put("userId",user.getId());
                claims.put("username",user.getUsername());
                String token = JwtUtils.create(claims);
                return Result.success("登录成功",token);
            }
        }
        return Result.error(message);
    }
    //todo 登出暂未使用
    @RequestMapping("logout")
    public Result logout(@Valid User loginUser){
        String message="用户名/密码不正确";
        User user = userSerivce.findUserByName(loginUser.getUsername());
        if(user!=null){//用户存在
            if(user.getPassword().equals(Md5Util.getMD5String(loginUser.getPassword()))){//密码正确
                Map<String,Object> claims=new HashMap();
                claims.put("userId",user.getId());
                claims.put("userUsername",user.getUsername());
                String token = JwtUtils.create(claims);
                return Result.success(token);
            }
        }
        return Result.error(message);
    }
}

4. ArticleController

主要为测试使用

package org.example.springboot3.bigevent.controller;

import org.example.springboot3.bigevent.entity.Result;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Create by zjg on 2024/5/26
 */
@RestController
@RequestMapping("/article/")
public class ArticleController {
    @RequestMapping("list")
    public Result<String> list(){
        return Result.success("article.list");
    }
}

三、拦截器

1. 定义拦截器

我们使用拦截器来对接口进行横切面的token验证

package org.example.springboot3.bigevent.inceptors;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.springboot3.bigevent.entity.Result;
import org.example.springboot3.bigevent.utils.JwtUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

/**
 * Create by zjg on 2024/5/26
 */
@Component
public class LoginInceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("Authorization");
        if(token!=null&&token.contains("Bearer")){
            boolean verify = JwtUtils.verify(token.substring(token.indexOf("Bearer")+7));
            if(verify){
                return true;
            }
        }
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json;charset=UTF-8");
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.writerFor(Result.class);
        String message = objectMapper.writeValueAsString(Result.error("token验证失败,请重新获取token后重试!"));
        response.getWriter().println(message);
        return false;
    }
}

2. 注册拦截器

拦截器拦截初注册和登录之外的所有请求

package org.example.springboot3.config;

import org.example.springboot3.bigevent.inceptors.LoginInceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList;
import java.util.List;

/**
 * Create by zjg on 2024/5/26
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    LoginInceptor loginInceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        List<String> patterns=new ArrayList<>();
        patterns.add("/user/register");
        patterns.add("/user/login");
        registry.addInterceptor(loginInceptor).excludePathPatterns(patterns);
    }
}

四、测试

1. 登录

在这里插入图片描述

2. 无token

在这里插入图片描述

3. 有token

在这里插入图片描述

4. 全局配置

在Postman中可以对目录配置token认证信息,对目录下的全部接口生效。

在这里插入图片描述


总结

回到顶部
JWT官网
JWT快速入门

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

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

相关文章

vscode怎么点击路径直接跳转对应文件

在vue项目中经常要引入工具类、组件、模版等&#xff0c;想要直接去看对应文件&#xff0c;只能自己找到对应路径再去打开。 我们可用在js项目中创建一个 jsconfig.json文件&#xff0c;TS项目可以创建tsconfig.json 文件代码 {"compilerOptions": {"baseUrl&…

批量归一化(BN)和层归一化(LN)的区别

批量归一化&#xff08;Batch Normalization, BN&#xff09;和层归一化&#xff08;Layer Normalization, LN&#xff09;是深度学习中常用的两种归一化技术&#xff0c;它们主要用于解决训练过程中的内部协变量偏移问题&#xff0c;加速模型收敛和提高稳定性。 1. 为什么需要…

智能工厂生产设备实时监控技术的UI设计

智能工厂生产设备实时监控技术的UI设计

Java面试八股之死锁和饥饿的区别

死锁和饥饿的区别 定义与现象&#xff1a; 死锁&#xff08;Deadlock&#xff09;是指两个或多个线程互相等待对方持有的资源而无法继续执行的情况。每个线程至少持有一个资源&#xff0c;并尝试获取另一个由其他线程持有的资源&#xff0c;从而形成一个循环等待的僵局&#…

QAnything-1.4.01.4.1版本更新!使用指北!

久等了各位&#xff01;时隔一个多月&#xff0c;我们在4月26日和5月20日接连发布了v1.4.0和v1.4.1两个版本&#xff0c;带来了问答性能&#xff0c;解析效果等多方面的改进&#xff0c;并新增了大量的新功能和新特性 详见&#xff1a;releases 以及 使用说明 最新特性表 开发…

Android 调试桥_ADB命令

Android 调试桥 ADB全称 【Android Debug Bridge】 是Android SDK中的一个命令行工具&#xff0c;adb命令可以直接操作管理Android模拟器或真实的Android设备&#xff08;手机&#xff09; ADB的工作原理 启动一个 adb 客户端时&#xff0c;此客户端首先检查是否有已运行的 …

1961. 检查字符串是否为数组前缀 - 力扣

1. 题目 给你一个字符串 s 和一个字符串数组 words &#xff0c;请你判断 s 是否为 words 的 前缀字符串 。 字符串 s 要成为 words 的 前缀字符串 &#xff0c;需要满足&#xff1a;s 可以由 words 中的前 k&#xff08;k 为 正数 &#xff09;个字符串按顺序相连得到&#xf…

kaggle竞赛系列基于图像对水稻分类代码案例

目录 依赖环境 代码 导入依赖包 定义数据集路径&#xff1a; 创建训练集、验证集和测试集的文件夹&#xff1a; 代码的作用&#xff1a; 设置新的数据集路径与类别名称 代码的作用&#xff1a; 定义数据预处理和增强变换&#xff1a; 代码的作用&#xff1a; 定义数…

Appium自动化环境搭建保姆级教程

APP自动化测试运行环境比较复杂&#xff0c;稍微不注意安装就会失败。我见过不少朋友&#xff0c;装了1个星期&#xff0c;Appium 的运行环境还没有搭好的。 搭建环境本身不是一个有难度的工作&#xff0c;但是 Appium 安装过程中确实存在不少隐藏的比较深的坑&#xff0c;如果…

kafka-集群搭建(在docker中搭建)

文章目录 1、kafka集群搭建1.1、下载镜像文件1.2、创建zookeeper容器并运行1.3、创建3个kafka容器并运行1.3.1、9095端口1.3.2、9096端口1.3.3、9097端口 1.4、重启kafka-eagle1.5、查看 efak1.5.1、查看 brokers1.5.2、查看 zookeeper 1、kafka集群搭建 1.1、下载镜像文件 d…

Vuforia AR篇(五)— 地平面检测

目录 前言一、什么是地平面识别&#xff1f;二、使用步骤三、示例代码四、效果五、总结 前言 在增强现实&#xff08;AR&#xff09;应用程序的开发中&#xff0c;地平面识别是一项关键技术&#xff0c;它允许虚拟对象与现实世界的地面进行互动。Vuforia 是一个功能强大的 AR …

javacv ffmpeg使用笔记 (补充中...)

javacv ffmpeg使用笔记 一、maven依赖二、示例代码1. 获取视频时长 三、小技巧 一、maven依赖 使用javacv ffmpeg并指定classifier之后&#xff0c;就不需要额外安装ffmpeg软件&#xff08;jar包中已经内置&#xff09;了。 全量依赖包&#xff08;不推荐&#xff09;安装包总大…

6、架构-服务端缓存

为系统引入缓存之前&#xff0c;第一件事情是确认系统是否真的需要缓 存。从开发角度来说&#xff0c;引入缓存会提 高系统复杂度&#xff0c;因为你要考虑缓存的失效、更新、一致性等问题&#xff1b;从运维角度来说&#xff0c;缓存会掩盖一些缺 陷&#xff0c;让问题在更久的…

HashMap的get和put方法

在 JDK 1.8 中&#xff0c;HashMap 是一个常用的实现了 Map 接口的哈希表&#xff0c;它允许存储键值对&#xff0c;并且键和值都可以为 null。HashMap 的主要特点是其基于哈希表的实现&#xff0c;提供了快速的查找和插入操作。以下是 HashMap 中 get 和 put 方法的介绍及其实…

Flink状态State | 大数据技术

⭐简单说两句⭐ ✨ 正在努力的小叮当~ &#x1f496; 超级爱分享&#xff0c;分享各种有趣干货&#xff01; &#x1f469;‍&#x1f4bb; 提供&#xff1a;模拟面试 | 简历诊断 | 独家简历模板 &#x1f308; 感谢关注&#xff0c;关注了你就是我的超级粉丝啦&#xff01; &a…

使用J-LINK COMMANDER检查极海APM32F072烧录

键入 connect: 此时会显示默认设备&#xff0c;如果之前设置过会有&#xff0c;为了演示&#xff0c;我不选 键入 &#xff1f; 然后会弹出设备选择界面&#xff1a; 根据自己的设备搜索型号&#xff1a; 我这里搜索“APM32F072VB”,点击OK: 选择接口类型&#xff1a; 如果要…

用Python优雅地写LaTeX

latexify用于生成 LaTeX 数学公式的 Python 库。LaTeX 是一种基于 ΤΕΧ 的排版系统&#xff0c;对于展示复杂的数学公式表现极为出色。该项目可以用 Python 函数&#xff0c;轻松生成复杂的 LaTeX 数学公式描述。 安装库 查看版本号 0.4.2 案例演示 我们需要以装饰器的形式…

jquery发ajax自动302、xhrredirect,莫名弹出登录窗口。tomcat部署情况下

效果如下&#xff1a; 原因如下&#xff1a; 跟tomcat自带的一个项目同名了&#xff0c;只要前缀跟那个项目同名 都被拦截。 解决方案&#xff1a; 我直接改了一个接口名字&#xff0c;只要不和tomcat自带项目名字一样即可

Linux基础 (十二):Linux 线程的创建与同步

本篇博客详细介绍与线程有关的内容&#xff0c;这部分也是笔试面试的重点&#xff0c;需要我们对线程有深刻的理解&#xff0c;尤其是线程的并发运行以及线程同步的控制&#xff01;接下来&#xff0c;让我们走进线程的世界&#xff0c;去理解线程&#xff0c;使用线程&#xf…

RocketMQ学习(3) 秒杀实战

学习完RocketMQ的用法,现在用它来做一个简单的秒杀项目练练手。 关于秒杀,我之前其实有专门的学习过其中的一些业务逻辑和常见问题,我在这篇博客中有写过多并发场景下的秒杀场景,需要考虑哪些问题?也可以学习一下 除了RocketMQ,本文还需要会springBoot + Redis + Mysql…