【SpringBoot】SpringBoot整合JWT

目录

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

JWT的核心应用

Authorization (授权)

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

Information Exchange (信息交换)

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

JWT的组成部分

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

java 复制代码
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令牌被篡改。
java 复制代码
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.依赖

xml 复制代码
	<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,还需要引入以下依赖

    xml 复制代码
            <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

java 复制代码
@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.输出结果

java 复制代码
生成的密钥是:pI9g7kkh0IgqHC27U7FYgAQtquy9PGPINCUvko2Qyyo=
token = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMmY0MzMyYy01MmRhLTQ0MDktODJjZS1hODBkZjNmMDIwYjMiLCJzdWIiOiJhbGwiLCJpYXQiOjE3MTcxMTc3MTMsImV4cCI6MTcxNzExOTUxMywidXNlcm5hbWUiOiJ6aGFuZ3NhbiIsInVzZXJJZCI6IjEwMDEifQ.wfrMHoLZubZksALfad5BAG7oNUXbMwrXxHhgRTAtOtI

解密

在线token解密

地址:https://tooltt.com/jwt-decode/

代码实现

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

java 复制代码
    @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);
    }
输出结果
java 复制代码
头信息:{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

xml 复制代码
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>

2.配置

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

3.封装工具类

java 复制代码
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.其他类

  • 实体类

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

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

    java 复制代码
    @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;
        }
    }
  • 统一返回模板

    java 复制代码
    @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.拦截器

java 复制代码
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.拦截器配置

java 复制代码
/**
 * 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类

java 复制代码
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】
相关推荐
你的人类朋友几秒前
CommonJS模块化规范
javascript·后端·node.js
小码编匠24 分钟前
C# 实现西门子S7系列 PLC 数据管理工具
后端·c#·.net
Postkarte不想说话28 分钟前
Ubuntu24.04搭建TrinityCore魔兽世界
后端
数据智能老司机29 分钟前
CockroachDB权威指南——SQL调优
数据库·分布式·架构
Weison29 分钟前
Apache Doris Trash与Recover机制
后端
数据智能老司机30 分钟前
CockroachDB权威指南——应用设计与实现
数据库·分布式·架构
XuanXu33 分钟前
Java AQS原理以及应用
java
数据智能老司机43 分钟前
CockroachDB权威指南——CockroachDB 模式设计
数据库·分布式·架构
codelang2 小时前
Cline + MCP 开发实战
前端·后端
风象南3 小时前
SpringBoot中6种自定义starter开发方法
java·spring boot·后端