【SpringBoot】SpringBoot整合JWT

目录

  • 先说token
  • 单点登录(SSO)
    • 简介
    • 原理
    • 单点登录的优势
    • 单点登录流程
    • 分布式单点登录方式
      • 方式一:session广播机制实现
      • 方式二:使用cookie+redis实现。
      • 方式三:token认证
  • JWT
    • 数字签名
    • JWT的作用
    • JWT和传统Session
      • 1、无状态:
      • 2、避免CSRF 攻击:
      • 3、适合移动端应用
      • 4、单点登录友好
      • 总结
    • JWT的核心应用
      • Authorization (授权)
      • Information Exchange (信息交换)
    • JWT的组成部分
      • header:头部信息
      • payload:有效载荷
      • signatur:签名算法
    • JWT的工作流程
      • 思路
      • 步骤
    • JWT的demo【单独案例】
      • 1.依赖
      • 2.生成Token
      • 3.输出结果
      • 解密
        • 在线token解密
        • 代码实现
        • 输出结果
      • 常见的异常整理
    • 常见异常信息
  • SpringBoot整合JWT
    • 1.依赖
    • 2.配置
    • 3.封装工具类
    • 4.其他类
    • 5.拦截器
    • 6.拦截器配置
    • 7.controller类
    • 8.测试

先说token

  • 随着前后端分离的普及以及分布式、微服务、Restful API的普遍应用,Token认证已经是所有系统都绕不开的一个技术话题。
  • 基于token的用户认证是一种服务端无状态的认证方式。所谓服务端无状态指的token本身包含登录用户所有的相关数据,而客户端在认证后的每次请求都会携带token,因此服务器端无需存放token数据。
  • 当用户认证后,服务端生成一个token发给客户端,客户端可以放到 cookie 或 localStorage 等本地存储中,在客户端每次发起请求时带上 token,服务端收到token通过验证后即可确认用户身份
  • 简单来说token就像是一个令牌,好比我们办的某个超市、健身房、酒店等机构的会员卡。而且如果某个机构是连锁性质的,那么这一个会员卡是可以在所有连锁单位都能认证的,这种就是当下流行的一个词:单点登录

单点登录(SSO)

简介

  • 单点登录(Single Sign-On,简称SSO)是一种身份验证和访问控制的技术,它允许用户使用一组凭据(如用户名和密码)登录到多个相关但相对独立的系统中,而不需要再次输入凭据。
  • 通过SSO,用户只需登录一次,就可以访问多个应用或系统,简化了用户的登录流程和管理,提高了用户体验。

原理

  • 在一个典型的SSO系统中,有一个中心身份提供者(Identity Provider,简称IdP),其负责处理用户的身份验证和生成令牌。
  • 当用户尝试访问其他应用或系统时,这些应用或系统会将用户重定向到IdP进行身份验证。
  • 一旦用户成功登录,IdP会生成一个令牌,并返回给应用或系统。
  • 应用或系统可以使用此令牌来验证用户的身份,并为其提供相应的访问权限。

单点登录的优势

  • SSO的好处包括提高用户体验、减少用户的密码负担、简化身份验证和访问管理、提高安全性等。
  • 通过使用SSO,用户可以通过一个登录凭据访问多个应用或系统,无需记住多个用户名和密码。
  • 同时,SSO还可以加强安全性,通过集中管理和控制用户的访问权限,减少安全漏洞的发生。

单点登录流程

在这里插入图片描述

分布式单点登录方式

方式一:session广播机制实现

  • 简单来说:就是把session复制到另一台服务器中
  • 缺点:
    • 模块较多时,拷贝session比较浪费资源;
    • 比如 中间会存在多份一样的数据 ;
    • session默认过期时间30分钟,过期需要重新登录

方式二:使用cookie+redis实现。

  • cookie客户端技术:存在浏览器中,每次发送请求,带着cookie值进行发送
  • redis,读取速度快,基于key-value存储(keys *)
  • 用户登录后,把数据分别放到两个地方cookie、redis
    • redis:在key里生成唯一随机值(ip、用户id、uuid) ,在value里放用户数据
    • cookie:把redis里面生成key值放到cookie里面。
  • 访问项目其他模块时,发送请求带着cookie进行发送,然后其他模块去获取cookie值,也就是拿着cookie去redis中查询,如果能查到数据表示这个用户已登录。

方式三:token认证

  • 按照一定规则生成字符串,字符串可以包含用户信息——jwt

JWT

  • JSON Web Token,通过数字签名的方式,以JSON对象为载体,在不同的服务终端之间安全的传输信息

数字签名

  • 数字签名(又称公钥数字签名)是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。
  • 它是一种类似写在纸上的普通的物理签名,但是在使用了公钥加密领域的技术来实现的,用于鉴别数字信息的方法。
  • 一套数字签名通常定义两种互补的运算,一个用于签名,另一个用于验证。
  • 数字签名是非对称密钥加密技术数字摘要技术的应用

JWT的作用

  • jwt最常见的场景就是授权认证,一旦用户登录,后续每个请求都将包含jwt
  • 系统在每次处理用户请求之前,都要先进行jwt安全校验,痛过之后才能进行处理

JWT和传统Session

1、无状态:

  • token 自身包含了身份验证所需要的所有信息,使得我们的服务器不需要存储 Session 信息,这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。
  • 也导致了它最大的缺点:当后端在token 有效期内废弃一个 token 或者更改它的权限的话,不会立即生效,一般需要等到有效期过后才可以。另外,当用户 Logout 的话,token 也还有效。除非,我们在后端增加额外的处理逻辑。

2、避免CSRF 攻击:

  • 攻击者就可以通过让用户误点攻击链接,达到攻击效果。
  • 防止误触操作,避免请求直接获取本地的session值进行请求访问。

3、适合移动端应用

  • 使用 Session 进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到 Cookie(需要 Cookie 保存 SessionId),所以不适合移动端。
  • 但是,使用 token 进行身份认证就不会存在这种问题,因为只要 token 可以被客户端存储就能够使用,而且 token 还可以跨语言使用。

4、单点登录友好

  • 使用 Session 进行身份认证的话,实现单点登录,需要我们把用户的 Session 信息保存在一台电脑上,并且还会遇到常见的 Cookie 跨域的问题。
  • 但是,使用 token 进行认证的话, token 被保存在客户端,不会存在这些问题。

总结

JWT传统Session
存储位置客户端服务器
存储数据TokenSession ID + 服务器端存储的会话数据
存储方式无状态有状态
跨域支持支持需要额外配置
可扩展性
安全性
网络开销
扩展性
动态更改权限需要重新签发Token服务器端配置即可
服务器状态管理无需管理需要管理和维护

JWT的核心应用

Authorization (授权)

  • 这是使用JWT的最常见场景。
  • 一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。
  • 单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。

Information Exchange (信息交换)

  • 对于安全的在各方之间传输信息而言,JSON Web Tokens无疑是一种很好的方式。
  • 因为JWTs可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人。
  • 另外,由于签名是使用头和有效负载计算的,您还可以验证内容没有被篡改。

JWT的组成部分

JWT的结构由三部分组成,分别是标头、有效负载、签名算法,中间使用 点 进行隔开。

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMmY0MzMyYy01MmRhLTQ0MDktODJjZS1hODBkZjNmMDIwYjMiLCJzdWIiOiJhbGwiLCJpYXQiOjE3MTcxMTc3MTMsImV4cCI6MTcxNzExOTUxMywidXNlcm5hbWUiOiJ6aGFuZ3NhbiIsInVzZXJJZCI6IjEwMDEifQ.wfrMHoLZubZksALfad5BAG7oNUXbMwrXxHhgRTAtOtI

header:头部信息

  • 通常由两部分组成:令牌的类型 和 所用的加密算法
  • 然后将该JSON对象数据进行Base64 URL编码,得到的字符串就是JWT令牌的第一部分。
  • 例如:{ “typ”: “JWT”, “alg”: “HS256” }。然后要转成base64字符串。

payload:有效载荷

  • 有效数据存储区,主要定义自定义字段和内置字段数据。
  • 通常会把用户信息和令牌过期时间放在这里,同样也是一个JSON对象,里面的key和value可随意设置,然后经过Base64 URL编码后得到JWT令牌的第二部分
  • 由于这个部分是没有加密的(因为Base64是编码,可以直接解码),建议只存放一些非敏感信息
Payload的内置字段说明
iss(Issuer)令牌的签发者
sub(Subject)所面向的用户或实体
aud(Audience)令牌的接收者
exp(Expiration Time)令牌的过期时间(以UNIX时间戳表示)
nbf(Not Before)令牌的生效时间(以UNIX时间戳表示)
iat(Issued At)令牌的签发时间(以UNIX时间戳表示)
jti(JWT ID)令牌的唯一标识符

原文链接:https://blog.csdn.net/qq_46921028/article/details/131298671

signatur:签名算法

  • 使用头部Header定义的加密算法,对前两部分Base64编码拼接的结果进行加密
  • 加密时的秘钥服务私密保存
  • 加密后的结果在通过Base64Url编码得到JWT令牌的第三部分。
  • 签名的作用:防止JWT令牌被篡改。
var encodestr = base64urlEncode(header) + "." + base64urlEncode(paylod);
var signature = HMACSHA256(encodestr,"secret");

JWT的工作流程

思路

  • 在身份验证中,当用户成功登录系统时,授权服务器将会把 JSON Web Token(JWT)返回给客户端,用户需要将此凭证信息存储在本地(cookie或浏览器缓存)。
  • 当用户发起新的请求时,需要在请求头中附带此凭证信息,当服务器接收到用户请求时,会先检查请求头中有无凭证,是否过期,是否有效。
    • 如果凭证有效,将放行请求;
    • 若凭证非法或者过期,服务器将回跳到认证中心,重新对用户身份进行验证,直至用户身份验证成功。

步骤

  1. 发送登录请求,携带username、password
  2. 进行验证,验证通过返回JWT
  3. 请求头携带JWT发请求到应用服务
  4. 验证携带过来的JWT的合法性
  5. 验证通过返回,执行后续操作

在这里插入图片描述

JWT的demo【单独案例】

1.依赖

	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    ...
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
    </dependencies>
  • 如果jdk大于1.8,还需要引入以下依赖

            <dependency>
                <groupId>javax.xml.bind</groupId>
                <artifactId>jaxb-api</artifactId>
                <version>2.3.0</version>
            </dependency>
            <dependency>
                <groupId>com.sun.xml.bind</groupId>
                <artifactId>jaxb-impl</artifactId>
                <version>2.3.0</version>
            </dependency>
            <dependency>
                <groupId>org.glassfish.jaxb</groupId>
                <artifactId>jaxb-core</artifactId>
                <version>2.3.0</version>
            </dependency>
            <dependency>
                <groupId>javax.activation</groupId>
                <artifactId>activation</artifactId>
                <version>1.1.1</version>
            </dependency>
    

2.生成Token

@SpringBootTest
class JwtApplicationTests {
    /** AES 算法 */
    private static final String ALGORITHM_AES="AES";
    @Test
    public void testCreatJwt() throws NoSuchAlgorithmException {
        //定义秘钥,可以自己定义,随便一个字符串都可以,专业一些的话就用密钥生成工具吧
        String secretKey = getKey();
        System.out.println("生成的密钥是:" + secretKey);
        // 使用Jwts工具类构建一个令牌
        String token = Jwts.builder()
                // 1.设置JWT头部信息(类型和加密算法)
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                // 2.设置JWT载荷数据
                .setId(UUID.randomUUID().toString()) //内置字段jti:表示唯一ID
                .setSubject("all") //内置字段sub:面向所有用户
                .setIssuedAt(new Date()) //内置字段ita:token创建时间
                .setExpiration(new Date(System.currentTimeMillis() + 30 * 60 * 1000)) //内置字段exp:token过期时间,30分钟
                .claim("username", "zhangsan") //自定义字段,kv格式
                .claim("userId", "1001") //自定义字段
                // 3.设置JWT签名信息(加密算法,秘钥)
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact(); //最后调用compact()方法生成最终的token

        //由于使用UUID生成唯一标识,所以每次生成的token都不一样
        System.out.println("token = " + token);
    }

    /**
     * 生成密钥
     * @return
     * @throws NoSuchAlgorithmException
     */
    private String getKey() throws NoSuchAlgorithmException {
        /**
         * 创建KeyGenerator实例
         *      algorithm密钥算法
         *          AES
         *          DES
         *          DESede
         *          HmacSHA1
         *          HmacSHA224
         *          HmacSHA256
         *          HmacSHA384
         *          HmacSHA512
         *          RC2
         */
        KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM_AES);
        //指定生成密钥的大小;AES密钥长度只能=128、192、256
        keyGenerator.init(256);
        //指定生成密钥随机源:keyGenerator.init(SecureRandom secureRandom)
        //指定生成密钥大小、随机源:keyGenerator.init(int size, SecureRandom secureRandom)

        /**
         * 借助Base64转换生成的密钥
         *      通常加密后要把密钥保存下来,解密时使用密钥重建SecertKey,生成的密钥是字节数组不利于保存,所以借助Base64转换成字符串
         */
        return Base64.getEncoder().encodeToString(keyGenerator.generateKey().getEncoded());
    }
}

3.输出结果

生成的密钥是:pI9g7kkh0IgqHC27U7FYgAQtquy9PGPINCUvko2Qyyo=
token = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMmY0MzMyYy01MmRhLTQ0MDktODJjZS1hODBkZjNmMDIwYjMiLCJzdWIiOiJhbGwiLCJpYXQiOjE3MTcxMTc3MTMsImV4cCI6MTcxNzExOTUxMywidXNlcm5hbWUiOiJ6aGFuZ3NhbiIsInVzZXJJZCI6IjEwMDEifQ.wfrMHoLZubZksALfad5BAG7oNUXbMwrXxHhgRTAtOtI

解密

在线token解密

地址:https://tooltt.com/jwt-decode/
在这里插入图片描述

代码实现

刚才的token过期了,重新生成了一下

    @Test
    public void testcheckToken() {
        // 秘钥,刚才生成的密钥
        String secretKey = "Y28Ijg521FgN31ZgpD1hZpOYd8fTMrZwNcMgds+D91I=";
        // 待验证的token
        String tokenStr = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4M2M3NTVkNC1jNzJlLTRlZjctYjY1MC1jYjdlZWRkYWNjYWIiLCJzdWIiOiJhbGwiLCJpYXQiOjE3MTcxMjQxNjQsImV4cCI6MTcxNzEyNTk2NCwidXNlcm5hbWUiOiJ6aGFuZ3NhbiIsInVzZXJJZCI6IjEwMDEifQ.gM89JWUOAQu8KpYgXbom9KGXB1ZcqSUqzj5eW8cg_HU";
        // 通过密钥验证签名是否被篡改
        JwtParser jwtParser = Jwts.parser();
        Jws<Claims> claimsJws = jwtParser
                .setSigningKey(secretKey)
                .parseClaimsJws(tokenStr);

        // 获取头
        JwsHeader header = claimsJws.getHeader();
        // 获取载荷
        Claims body = claimsJws.getBody();
        // 获取签名
        String signature = claimsJws.getSignature();
        System.out.println("头信息:" + header);
        System.out.println("载荷信息:" + body);
        System.out.println("签名信息:" + signature);
    }
输出结果
头信息:{typ=JWT, alg=HS256}
载荷信息:{jti=83c755d4-c72e-4ef7-b650-cb7eeddaccab, sub=all, iat=1717124164, exp=1717125964, username=zhangsan, userId=1001}
签名信息:gM89JWUOAQu8KpYgXbom9KGXB1ZcqSUqzj5eW8cg_HU

常见的异常整理

常见异常信息

异常原因
SignatureVerificationException签名不一致异常
TokenExpiredException令牌过期异常
AlgorithmMismatchException算法不匹配异常
InvalidClaimException:失效的payload异常

SpringBoot整合JWT

1.依赖

用这个吧,反正后面要写OAuth2

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

2.配置

# JWT 配置
jwt:
  secret: Y28Ijg521FgN31ZgpD1hZpOYd8fTMrZwNcMgds+D91I= # 加密密钥
  expire: 1800 # token有效时长 S
server:
  port: 9999
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
	# ...... 其他配置

3.封装工具类

package com.kgc.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Calendar;
import java.util.Map;

/**
 * @author: zjl
 * @datetime: 2024/5/31
 * @desc: 复兴Java,我辈义不容辞
 */
@Component
public class JWTUtils {
    @Value("${jwt.secret}")
    public  String secret;

    @Value("${jwt.expire}")
    public  Integer tokenExpire;

    /**
     * 生成 JWT 令牌
     * @param map 传入的 Payload 数据
     * @return 返回生成的令牌
     */
    public String getToken(Map<String,String> map){
        JWTCreator.Builder builder = JWT.create();

        // 遍历传入的 Payload 数据,并添加到 Builder 中
        map.forEach((k,v)->{
            builder.withClaim(k,v);
        });

        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.SECOND,tokenExpire);

        // 设置过期时间为 XX 秒后
        builder.withExpiresAt(instance.getTime());

        // 使用 HMAC256 签名算法进行签名,并返回令牌字符串
        return builder.sign(Algorithm.HMAC256(secret)).toString();
    }

    /**
     * 获取令牌中的 Payload 数据
     * @param token 要解析的令牌字符串
     * @return 解码后的令牌对象(DecodedJWT)
     */
    public DecodedJWT verify(String token){
        // 创建一个 JWTVerifier 实例,使用相同的密钥构建,并对令牌进行验证和解码
        return JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
    }
}

4.其他类

  • 实体类

    @Data
    @NoArgsConstructor
    @ToString
    @AllArgsConstructor
    public class User {
        private int id;
        private String userCode;
        private String userName;
        private String userPassword;
        private String phone;
    }
    
  • mapper

    public interface UserMapper {
        @Select("SELECT * FROM SMBMS_USER WHERE USERCODE=#{userCode}")
        User selectUserByUserCode(String userCode);
    }
    
  • service

    @Service
    @Slf4j
    public class UserService {
        @Resource
        private UserMapper userMapper;
        public User login(String userCode,String userPassword){
            User user = userMapper.selectUserByUserCode(userCode);
            if(user!=null && userPassword.equals(user.getUserPassword())){
                return user;
            }
            return null;
        }
    }
    
  • 统一返回模板

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Result<T> {
    
        private int code;
        private String message;
        private T data;
    
        public Result(T data) {
            this.code = 200;
            this.message = "success";
            this.data = data;
        }
    
        public Result(T data, boolean success, String message) {
            if (success) {
                this.code = 200;
                this.message = "success";
            } else {
                this.code = 500; // 自定义错误状态码(示例为500)
                this.message = message;
            }
            this.data = data;
        }
    
        public Result(int code, String message) {
            this.code = code;
            this.message = message;
            this.data = null;
        }
    
        /**
         * 返回执行失败的结果(默认状态码为500)
         *
         * @param message 提示信息
         * @return 失败的结果对象
         */
        public static <T> Result<T> fail(String message) {
            return new Result<>(500, message);
        }
    
        /**
         * 返回执行失败的结果(自定义状态码和提示信息)
         *
         * @param code    状态码
         * @param message 提示信息
         * @return 失败的结果对象
         */
        public static <T> Result<T> fail(int code, String message) {
            return new Result<>(code, message);
        }
    }
    

5.拦截器

package com.kgc.interceptor;

/**
 * @author: zjl
 * @datetime: 2024/5/31
 * @desc: 复兴Java,我辈义不容辞
 */

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kgc.utils.JWTUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * JWTInterceptor是一个拦截器,用于验证请求头中的JWT令牌是否有效。
 * 当有请求进入时,该拦截器会首先从请求头中获取令牌,并尝试验证其有效性。
 * 如果令牌验证成功,则放行请求;否则,拦截请求并返回相应的错误信息。
 */
@Component
public class JWTInterceptor implements HandlerInterceptor {
    @Resource
    private JWTUtils jwtUtils;
    @Resource
    private ObjectMapper objectMapper;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 创建一个Map对象,用于存储响应信息
        Map<String, Object> map = new HashMap<>();

        // 从请求头中获取令牌
        String token = request.getHeader("token");

        try {
            jwtUtils.verify(token); // 验证令牌的有效性
            return true; // 放行请求
        } catch (SignatureVerificationException e) {
            e.printStackTrace();
            map.put("msg", "无效签名!");
        } catch (TokenExpiredException e) {
            e.printStackTrace();
            map.put("msg", "token过期!");
        } catch (AlgorithmMismatchException e) {
            e.printStackTrace();
            map.put("msg", "token算法不一致!");
        } catch (Exception e) {
            e.printStackTrace();
            map.put("msg", "token无效!!");
        }

        map.put("state", false); // 设置状态为false

        // 将Map转化为JSON字符串(使用Jackson库)
        String json = objectMapper.writeValueAsString(map);

        response.setContentType("application/json;charset=UTF-8"); // 设置响应的Content-Type
        response.getWriter().println(json); // 将JSON字符串写入响应中

        return false; // 不放行请求
    }
}

6.拦截器配置

/**
 * InterceptorConfig 是一个配置类,用于添加拦截器。
 * 在这个类中,我们可以配置需要拦截的接口路径以及排除不需要拦截的接口路径。
 * 在这个例子中,我们添加了JWTInterceptor拦截器来对请求进行token验证,
 * 并设置了"/user/test"接口需要进行验证,而"/user/login"接口则被排除在验证之外,即所有用户都放行登录接口。
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Resource
    private JWTInterceptor jwtInterceptor;
    /**
     * 添加拦截器配置
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor)
                .addPathPatterns("/user/test")         // 对"/user/test"接口进行token验证
                .excludePathPatterns("/user/login");  // 所有用户都放行登录接口
    }
}

7.controller类

package com.kgc.controller;

import com.auth0.jwt.exceptions.*;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.kgc.pojo.User;
import com.kgc.service.UserService;
import com.kgc.utils.JWTUtils;
import com.kgc.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: zjl
 * @datetime: 2024/5/31
 * @desc: 复兴Java,我辈义不容辞
 */
@RestController
@Slf4j
@RequestMapping("/user")
public class UserController {
    @Resource
    private UserService userService;
    @Resource
    private JWTUtils jwtUtils;
    @RequestMapping("/login")
    public Result<Map<String, Object>> login(User user) {
        // 打印用户名和密码
        log.info("用户名: [{}]", user.getUserCode());
        log.info("密码: [{}]", user.getUserPassword());
        // 创建结果对象
        Result<Map<String, Object>> result;
        try {
            // 调用userService的login方法进行用户认证
            User loginUser = userService.login(user.getUserCode(),user.getUserPassword());
            if(loginUser == null){
                return new Result<>(0, "认证失败");
            }
            // 获取用户ID和用户名,并将其放入payload
            Map<String, String> payload = new HashMap<>();
            payload.put("id", String.valueOf(loginUser.getId()));
            payload.put("name", loginUser.getUserName());
            // 生成JWT的令牌
            String token = jwtUtils.getToken(payload);
            // 构造成功的结果对象
            result = new Result<>(200, "认证成功");
            result.setData(new HashMap<>());
            result.getData().put("token", token); // 响应token
        } catch (Exception e) {
            // 构造失败的结果对象
            result = Result.fail(500, e.getMessage());
        }
        return result;
    }

    @RequestMapping("/test")
    public Result<Map<String, Object>> test(HttpServletRequest request) {
        // 创建结果对象
        Result<Map<String, Object>> result;
        try {
            Map<String, Object> map = new HashMap<>();
            // 处理自己的业务逻辑

            // 从请求头中获取token
            String token = request.getHeader("token");
            if(StringUtils.isEmpty(token)){
                return new Result<>(0, "请先登录!");
            }
            // 校验并解析token
            DecodedJWT verify = jwtUtils.verify(token);
            // 打印解析出的用户id和用户名
            log.info("用户id: [{}]", verify.getClaim("id").asString());
            log.info("用户name: [{}]", verify.getClaim("name").asString());
            // 构造成功的结果对象
            result = new Result<>(200, "请求成功!");
            result.setData(map);
        } catch (Exception e) {
            // 构造失败的结果对象
            result = Result.fail(500, e.getMessage());
        }
        return result;
    }

    @RequestMapping("/other")
    public Map<String, Object> test(String token) {
        Map<String, Object> map = new HashMap<>();
        try {
            jwtUtils.verify(token);
            map.put("msg", "验证通过~~~");
            map.put("state", true);
        } catch (TokenExpiredException e) {
            map.put("state", false);
            map.put("msg", "Token已经过期!!!");
        } catch (SignatureVerificationException e){
            map.put("state", false);
            map.put("msg", "签名错误!!!");
        } catch (AlgorithmMismatchException e){
            map.put("state", false);
            map.put("msg", "加密算法不匹配!!!");
        } catch (Exception e) {
            e.printStackTrace();
            map.put("state", false);
            map.put("msg", "无效token~~");
        }
        return map;
    }
}

8.测试

  • 先登录,用户名密码正确、不正确的
  • 正确的生成token
  • 然后访问其他接口,带token和不带token的。【header里带token】

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

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

相关文章

【Linux 网络】网络基础(三)(其他重要协议或技术:DNS、ICMP、NAT)

一、DNS&#xff08;Domain Name System&#xff09; DNS 是一整套从域名映射到 IP 的系统。 1、DNS 背景 TCP/IP 中使用 IP 地址和端口号来确定网络上的一台主机的一个程序&#xff0c;但是 IP 地址不方便记忆。于是人们发明了一种叫主机名的东西&#xff0c;是一个字符串&…

【Python】解决Python报错:AttributeError: ‘NoneType‘ object has no attribute ‘xxx‘

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

开利网络参加广州数据交易所学习活动

开利网络做为南沙广州数据交易所的会员参加了由“广东三会”组织的“数据资产”相关学习活动。&#xff08;下图为开利董事长付立军先生在签到&#xff09; 学习内容提现了数字时代企业数字化转型的核之心“发掘数据价值&#xff0c;驱动高速发展”&#xff0c;交易中心组织大家…

jpom ruoyi 发布后端

添加ssh 添加标签 添加仓库 添加构建 构建 命令 APP_NAMEenterprise IMAGE_NAMEenterprise:latest APP_PORT8080 RUN_ENVjenkins cd ruoyi-admin docker stop $APP_NAME || true docker rm $APP_NAME || true docker rmi $IMAGE_NAME || true docker build -f Dockerfil…

国际物流管理系统的选择:花钱不怕,就怕花冤枉钱

现在市场上的国际物流管理系统还是非常多的&#xff0c;想在这么多类型的系统中选择一套适合自己的系统确实不是个简单的事情。 尤其是现在很多物流商其实都是比较小的国际物流商&#xff0c;很多大型的&#xff0c;过于复杂的系统并不适合这个群体。那这个群体应该怎么选择国…

智慧车站管理:提升地铁站新质生产力的策略

应用图扑自研产品 HT for Web 结合 BIM 技术&#xff0c;搭建轻量化的 WebGIS 智慧车站系统。 该系统通过整合轨道交通信息&#xff0c;实现了车站数据的多维互联与融合。提升了车站信息管理效率和运营效能&#xff0c;并优化了乘客出行体验。对构建智能、高效、环保的轨道交通…

利用博弈论改进大模型性能:MIT最新研究解读

引言 在人工智能和大模型的发展过程中&#xff0c;我们常常遇到一个有趣的现象&#xff1a;同一个问题在不同形式下可能得到不同的答案。这种不一致性不仅降低了大模型的可信度&#xff0c;也限制了其在实际应用中的效果。为了应对这一问题&#xff0c;来自MIT的研究人员提出了…

微信公众号开发(三):自动回复“你好”

上一篇做了服务器校验&#xff0c;但没有处理用户发来的消息&#xff0c;为了完成自动回复的功能&#xff0c;需要增加一些功能&#xff1a; 1、调整服务器校验函数&#xff1a; def verify_wechat(request):tokentokendatarequest.argssignaturedata.get(signature)timestamp…

安全测试用例及解析(Word原件,直接套用检测)

5 信息安全性测试用例 5.1 安全功能测试 5.1.1 标识和鉴别 5.1.2 访问控制 5.1.3 安全审计 5.1.4 数据完整性 5.1.5 数据保密性 5.1.6 软件容错 5.1.7 会话管理 5.1.8 安全漏洞 5.1.9 外部接口 5.1.10 抗抵赖 5.1.11 资源控制 5.2 应用安全漏洞扫描 5.2.1 应用安全漏洞扫描 5.3…

vim使用技巧

1&#xff0c;使用内置帮助&#xff08;built-in help&#xff09; 使用 vim 的内置帮助是一个好习惯&#xff08;虽然很多朋友更喜欢在网上搜索相关的使用方法&#xff09;。查看帮助的语法如下表格所示&#xff1a; 前缀例子说明::help :w有关 :w 命令的帮助none:help j有关…

Python—面向对象小解(5)

一、多任务介绍 1.1 进程与线程 进程是操作系统分配资源的最小单元 线程执行程序的的最小单元 线程依赖进程&#xff0c;可以获取进程的资源 一个程序执行 先要创建进程分配资源&#xff0c;然后使用线程执行任务 默认情况下一个进程中有一个线程 1.2 多任务介绍 运行多个进程…

利用二维数组的输出下列图形

利用二维数组的输出下列图形 #include <stdio.h> int main () {int i,j;char a[5][9]{{*,*,*,*,*},{ ,*,*,*,*,*},{ , ,*,*,*,*,*},{ , , ,*,*,*,*,*},{ , , , ,*,*,*,*,*}};for(j0;j<9;j) {for(i0;i<5;i) {printf("%c ",a[i][j]);} printf("\n&qu…

【C++】:模板初阶和STL简介

目录 一&#xff0c;泛型编程二&#xff0c;函数模板2.1 函数模板概念2.2 函数模板格式2.3 函数模板的原理2.4 函数模板的实例化2.5 模板参数的匹配原则 三&#xff0c;类模板3.1 类模板的定义格式3.2 类模板的实例化 四&#xff0c;STL简介&#xff08;了解&#xff09;4.1 什…

python移位操作符(左移位操作符<<、右移位操作符>>)(允许开发者对整数进行位操作,乘2或除2)(左移操作、右移操作)(位掩码操作|=)

文章目录 Python 中的移位操作符详解移位操作符简介左移位操作符 (<<)语法和使用示例代码输出 右移位操作符 (>>)语法和使用示例代码输出 移位操作符的应用场景快速乘除运算&#xff1a;使用移位操作符代替传统的乘法和除法运算&#xff0c;可以提高计算速度。位掩…

参数设置错误导致的 OOM

参数设置错误导致的 OOM 前言事故分析事故原因事故复盘 前言 2024 年 5 月 10 日 14 时 19 分&#xff0c;C 公司开发人员向 A 公司开发人员反映某开放接口从 2024 年 5 月 10 日 14 时许开始无法访问和使用。该系统为某基础数据接口服务&#xff0c;基于 HTTP 协议进行通信。…

【第十二节】C++控制台版本贪吃蛇小游戏

目录 一、游戏简介 1.1 游戏概述 1.2 实现功能 1.3 开发环境 二、实现设计 2.1 C类的设计 2.2 项目结构 2.3 代码设计 三、程序运行截图 3.1 游戏界面 3.2 自定义地图 3.3 常规游戏界面 一、游戏简介 1.1 游戏概述 本游戏是一款基于C语言开发的控制台版本贪吃蛇游…

爆火的ChatTTS试用体验(附完整安装步骤和体验地址)

近日&#xff0c;一个名为 ChatTTS 文本转语音项目爆火出圈。突破了开源语音天花板&#xff0c;才开源3天斩获9k的Star量。 该模型真是强大&#xff0c;又要火爆一波&#xff0c;是最接近真人的语音特征&#xff0c;包括笑声、停顿和插入词等&#xff0c;让人感觉不到竟是语音合…

【一步一步了解Java系列】:子类继承以及代码块的初始化

看到这句话的时候证明&#xff1a;此刻你我都在努力 加油陌生人 个人主页&#xff1a;Gu Gu Study专栏&#xff1a;一步一步了解Java 喜欢的一句话&#xff1a; 常常会回顾努力的自己&#xff0c;所以要为自己的努力留下足迹 喜欢的话可以点个赞谢谢了。 作者&#xff1a;小闭 …

spring boot 3.x版本 引入 swagger2启动时报错

一&#xff0c;问题 Spring Boot 3.x版本的项目里&#xff0c;准备引入Swagger2作为接口文档&#xff0c;但是项目启动报错&#xff1a; java.lang.TypeNotPresentException: Type javax.servlet.http.HttpServletRequest not present at java.base/sun.reflect.generics.…

如何让Google收录网页?

确保网页被Google快速且持续地收录&#xff0c;页面的质量起着至关重要的作用。高质量的网页不仅更容易被搜索引擎收录&#xff0c;而且能够提高网页在搜索结果中的排名&#xff0c;想确保页面的质量&#xff0c;要保持原创&#xff0c;确保你的内容是独一无二的&#xff0c;别…