SpringBoot如何集成Token

 更新时间:2025年01月06日 15:00:35   作者:爱JAVA的少年闰土  
文章介绍了如何使用jjwt插件实现Token的生成和校验,该插件可以直接与SpringBoot集成,Token由三部分组成,分别是header、payload和signature,通过在请求头中传递Token,后端可以验证其合法性,从而提高安全性

SpringBoot集成Token

简介

在项目开发中,Token 是常见且重要的一个功能,目前对于 Token 设计有很多成熟的方案;

比如使用 Redis 存储管理 Token,不过这种方式需要而额外集成 Redis 服务,虽然 Redis 查询效率很高,但是对于普通项目来说,还是增加了开发难度;

本章将介绍一个简单易用的 Token 插件:jjwt,该插件直接与 SpringBoot 集成即可,其原理是:在服务端加密生成一个三段式加密字符串,前端每次请求都要将该 Token 传递给服务端(建议放在 Header 中),后台通过解析该 Token 字符串,判断其是否合法,超时等

基本原理

从这个架构图中可以看出 JWT 主体分为 3 个部分:user,application server,authentication server;

非常常见的一个架构,首先用户需要 通过登录等手段向 authentication server 发送一个认证请求,authentication会返回给用户一个 JWT (这个JWT 的具体内容格式是啥后面会说,先理解成一个简单的字符串好了)

此后用户向application server发送的所有请求都要捎带上这个 JWT,然后application server 会验证这个 JWT 的合法性,验证通过则说明用户请求时来自合法守信的客户端

JWT 结构

这个 JWT 的格式,就是一个由三部分组成的字符串:header.payload.signatue;

其中 header 主要包含了加密算法等信息;

payload 则主要包含后端服务器放入的自定义信息,如:登录用户的信息;signature 就是使用算法生成的能够实现身份认证的字符串

实现步骤

1. 在项目的 pom.xml 配置文件中添加如下依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.7.0</version>
</dependency>

2. 添加一个公共类 Token,提供生成 Token,校验等基本方法

/**
 * @ClassName Token
 * @Author Andy
 * @Date 2020/7/14 14:32
 * @Description 用于生成, 解析 token 的工具类
 **/
public class Token {
    // 密钥
    private static SecretKeySpec key = "";

    // 对密钥加密
    static {
        key = new SecretKeySpec(Constant.SECRET_KEY.getBytes(), SignatureAlgorithm.HS512.getJcaName());
    }

    // 生成 token
    public static String createToken(String subject, Map < String, Object > claims, Date expireDate) {
        JwtBuilder builder = Jwts.builder()
            .setClaims(claims) // payload 私有申明,存放一些个人信息,必须放在第一个
            .setIssuer(Constant.AUTHOR) // token 的签发人
            .setIssuedAt(new Date()) // token 的签发时间
            .setSubject(subject) // token 的所有人, 一般放用户的 id 之类的
            .setExpiration(expireDate) // 过期时间
            .signWith(SignatureAlgorithm.HS512, key); // token 的签名算法
        return builder.compact();
    }

    // 生成 token
    public static String createToken(String subject, Date expireDate) {
        return createToken(subject, new HashMap < > (), expireDate);
    }

    // 解析 token
    public static Claims parseToken(String token) {
        try {
            return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            throw new CommonException(Exceptions.TOKEN_PARSE_ERROR);
        }
    }

    // 将 token 标记为过期
    public static void markTokenExpired(String token) {
        Date expireDate = CommonUtil.currentTimeAddSeconds(1);
        Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody().setExpiration(expireDate);
    }

    // 判断 token 是否过期
    public static boolean checkTokenExpired(String token) {
        Claims claims = parseToken(token);
        return !CommonUtil.checkExpired(claims.getExpiration());
    }

    // 获取 token 的 subject 信息, 登录成功保存的时用户的主键
    public static String getSubject(HttpServletRequest request) {
        return parseToken(getToken(request)).getSubject();
    }
   
    // 根据 token 获取 subject 信息
    public static String getSubject(String token) {
        return parseToken(token).getSubject();
    }

    // 根据 HttpServletRequest 获取 token
    public static String getToken(HttpServletRequest request) {
        return request.getHeader(Constant.HEADER_TOKEN);
    }

    // 获取 token 的签发人
    public static String getIssuer(String token) {
        return parseToken(token).getIssuer();
    }
}

这里使用到其它关联类的方法或属性如下

  • Constant.class
// token 签发者
public static final String AUTHOR = "duzimei";
// 获取 header 中 token 标识
public static final String HEADER_TOKEN = "token";
// token 密钥加密字符串(取自 《肖申克的救赎》经典台词)
public static final String SECRET_KEY = "Fear can hold you prisoner.Hope can set you free";
// 设置 token 的过期时间为 1 个小时(单位秒)
public static final Integer EXPIRE_DATE = 3600;
  • CommonUtil.class
// 在当前时间基础上加 seconds 秒
public static Date currentTimeAddSeconds(int seconds) {
    Calendar now = Calendar.getInstance();
    now.add(Calendar.SECOND, seconds);
    return now.getTime();
}

// 判断于当前日期是否过期
public static boolean checkExpired(Date expiration) {
    return expiration.after(new Date());
}

异常定义请忽略,根据自己项目而实现

3. 定义一个 UserController.class,添加登录方法,当用户登录成功后,返回一个 Token

@RestController
@RequestMapping("/user")
public class User {    
    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public Map<Integer, String> login(@RequestBody JSONObject params) {
        Map<Integer, String> result = new HashMap<>();
        String userAccount = params.getString("account");
        String password = params.getString("password");
        User tempUser = userService.getUserByAccount(userAccount);
        if(null == tempUser) {
            result.put(-1, "用户不存在");
        } else if(!password.equals(tempUser.getPassword())) {
            result.put("-2", "密码不正确");
        } else {
            // 用户登录成功, 添加 token 返回给客户端
            // 1. 设置 token 过期时间
            Date expireDate = CommonUtil.currentTimeAddSeconds(Contant.EXPIRE_DATE);
            // 2. 这里我们把用户的 id 信息放到 subject 中
            String token = Token.createToken(tempUser.getUserId(), expireDate); 
            result.put(0, token);
        }
        return result;
    }
}

4. 前端当用户登录成功后,向后台发送其它请求的时候,将 Token 放到 Header 中一起传送给后台

$.ajax({
    headers: {
        "token": 从后台获取到的token
    },
    url: "http://xxx:8080/user/info",
    method: "GET",
    data: null,
    dataType: "json",
    contentType: "application/json",
    success: function(data) {
        console.log(data);
    },
    error: function(x, s, e) {
        console.log("异常信息: " + x.responseText);
    }
})

5.解析Token

在后台对应方法中,就可以通过解析 Token 获取用户 id,判断请求是否合法;

像下面这种请求,前端根本不用传递用户的 id 到后台,后台通过解析 Token,获取 Token 的 subject 属性就能拿到用户 id,在一定程度上提高了安全性

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/info")
    public Map<Integer, Object> getUserInfo(HttpServletRequest request) {
        Map<Integer, Object> result = new HashMap<>();
        String token = Token.getToken(request);
        // 对 token 进行校验
        if(StringUtils.isEmpty(token)) { // 如果没有获取到 Token
            result.put(-1, "没有获取到 Token, 请求被阻止"); 
        } else if(!Constant.AUTHOR.equals(Token.getIssuer(token))) { // 如果 Token 的签发人不正确
            result.put(-2, "非法的 Token, 请求被阻止");
        } else if(Token.checkTokenExpired(token)) { // 如果 Token 已过期
            result.put(-3, "过期的 Token, 请求被阻止");
        } else {
            String userId = Token.getSubject(token);
            User user = userService.getUserById(userId);
            result.put(0, user);
        }
        return result;
    }
}

这里只是介绍的关于 JWT 实现 Token 的简单用法,在实际开发过程中,建议使用 Spring 的 AOP 技术实现对 Token 的校验;

我们这里只是实现了一个最简单的 Token,这 Token 中还可以放入其它信息,由于是单向加密的,所以数据传输非常安全;

更进一步的实现,应根据实际开发具体实现

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 在Eclipse中部署Spring Boot/Spring Cloud应用到阿里云

    在Eclipse中部署Spring Boot/Spring Cloud应用到阿里云

    这篇文章主要介绍了在Eclipse中部署Spring Boot/Spring Cloud应用到阿里云,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-12-12
  • 举例讲解Java设计模式编程中模板方法模式的运用实例

    举例讲解Java设计模式编程中模板方法模式的运用实例

    这篇文章主要介绍了Java设计模式编程中模板方法模式的运用实例,模板方法模式强调基于继承的代码复用,需要的朋友可以参考下
    2016-05-05
  • SpringBoot2.1 RESTful API项目脚手架(种子)项目

    SpringBoot2.1 RESTful API项目脚手架(种子)项目

    这篇文章主要介绍了SpringBoot2.1 RESTful API项目脚手架(种子)项目,用于搭建RESTful API工程的脚手架,只需三分钟你就可以开始编写业务代码,不再烦恼于构建项目与风格统一,感兴趣的小伙伴们可以参考一下
    2018-12-12
  • 浅谈SpringBoot实现自动装配的方法原理

    浅谈SpringBoot实现自动装配的方法原理

    SpringBoot的自动装配是它的一大特点,可以大大提高开发效率,减少重复性代码的编写。本文将详细讲解SpringBoot如何实现自动装配,需要的朋友可以参考下
    2023-05-05
  • Java中集合LinkedList的原理与使用方法

    Java中集合LinkedList的原理与使用方法

    这篇文章主要给大家介绍了关于Java中集合LinkedList的原理与使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-06-06
  • Spring\SpringBoot配置连接数据库的方法

    Spring\SpringBoot配置连接数据库的方法

    最近在学习SpringBoot,第一步就是要配置数据库,本文详细的介绍了Spring\SpringBoot配置连接数据库的方法,有需要的朋友们下面随着小编来一起学习学习吧
    2021-06-06
  • HashMap底层数据结构详细解析

    HashMap底层数据结构详细解析

    这篇文章主要介绍了HashMap底层数据结构详细解析,HashMap作为开发中常用的数据结构,也是面试中经常被问的知识点,因此作为开发者应该尽可能多的理解其底层的数据结构,需要的朋友可以参考下
    2023-11-11
  • Java基于线程实现带有滚动效果的Label标签实例

    Java基于线程实现带有滚动效果的Label标签实例

    这篇文章主要介绍了Java基于线程实现带有滚动效果的Label标签,实例分析了java线程的使用技巧及label标签的实现方法,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-07-07
  • Spring整合CXF webservice restful实例详解

    Spring整合CXF webservice restful实例详解

    这篇文章主要为大家详细介绍了Spring整合CXF webservice restful的实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-08-08
  • 高效Java尺寸压缩技巧,节省资源成本

    高效Java尺寸压缩技巧,节省资源成本

    如果你想了解如何优化Java应用程序的尺寸,节省存储空间并提升性能,那么你来对地方了,本指南将教你简单实用的技巧和最佳实践,帮助你轻松减小Java应用程序的体积,让你的代码更高效、更精简,让我们一起开始吧,让Java应用程序变得更小巧而强大!
    2023-12-12

最新评论