前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JWT在Spring Boot中的最佳实践:构建坚不可摧的安全堡垒

JWT在Spring Boot中的最佳实践:构建坚不可摧的安全堡垒

原创
作者头像
Front_Yue
发布2024-04-16 20:35:26
3620
发布2024-04-16 20:35:26
举报

前言

大家好,我是腾讯云开发者社区的 Front_Yue,本篇文章将介绍什么是JWT以及在JWT在Spring Boot项目中的最佳实践。

在现今的Web应用中,安全性是至关重要的。为了验证用户的身份并保护应用的数据,我们通常使用认证和授权机制。JSON Web Token(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。这些信息可以被验证和信任,因为它们是数字签名的。JWT可以使用HMAC算法或者是RSA或ECDSA的公钥/私钥对进行签名。

在Spring Boot应用中,JWT经常被用作无状态的认证方式,使得客户端可以在每次请求时都带上JWT,从而进行身份验证。

正文内容

一、JWT的结构

JWT通常由三部分组成,它们之间用.分隔,如下:

代码语言:java
复制
xxxxx.yyyyy.zzzzz
1. Header(头部)

通常包含两部分信息:

  • 令牌的类型,这里是JWT
  • 使用的签名算法,如HMAC SHA256或RSA

例如:

代码语言:json
复制
{
  "alg": "HS256",
  "typ": "JWT"
}

这个JSON对象被Base64Url编码后形成JWT的第一部分。

2. Payload(负载)

包含声明。声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型:注册的声明、公共的声明和私有的声明。

例如:

代码语言:json
复制
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

这个JSON对象也被Base64Url编码后形成JWT的第二部分。

3. Signature(签名)

是对上述两部分内容的签名,以防止内容被篡改。

这个部分是对前两部分的签名,需要指定一个密钥(secret)。这个密钥只有服务器才知道,并且应该保密。服务器在创建token的时候使用这个密钥对header和payload进行签名,生成第三部分。客户端在请求时带上这个JWT,服务器使用相同的密钥进行验证。

二、Spring Boot中使用JWT

在Spring Boot中,你可以通过以下步骤集成JWT:

1. 添加依赖

首先,在pom.xml中添加JWT相关的依赖,如jjwt

代码语言:xml
复制
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
2. 创建JWT工具类

我们需要在项目中创建一个Java工具类用来生成和验证JWT:

代码语言:java
复制
public class JwtUtils {

    private static final String SECRET = "your_secret_key"; // 密钥
    private static final long JWT_EXPIRATION = 604800000; // 一周的有效期
    // 生成JWT令牌
    public static String generateToken(String username) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + JWT_EXPIRATION);

        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(now)
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .compact();
    }
    // 验证JWT令牌
    public static Claims validateToken(String token) {
        try {
            return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException e) {
            // JWT过期
            e.printStackTrace();
        } catch (UnsupportedJwtException e) {
            // 不支持的JWT
            e.printStackTrace();
        } catch (MalformedJwtException e) {
            // JWT格式错误
            e.printStackTrace();
        } catch (SignatureException e) {
            // JWT签名不一致
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // JWT为空或格式错误
            e.printStackTrace();
        }
        return null;
    }
}
3. 创建认证过滤器

在项目中,我们需要创建一个过滤器,用于拦截客户端发送的请求,服务端需要验证JWT解析是否正确。

代码语言:java
复制
@Component
public class JwtAuthenticationFilter extends Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化操作,可选
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String tokenHeader = httpRequest.getHeader("Authorization");

        if (tokenHeader == null || !tokenHeader.startsWith("Bearer ")) {
            chain.doFilter(request, response);
            return;
        }

        // 获取token并验证
        String token = tokenHeader.substring(7);
        Claims claims = JwtUtils.validateToken(token);

        if (claims == null) {
            // 验证失败,处理未认证的情况
            // 例如:返回401未授权状态码
        } else {
            // 验证成功,设置用户认证信息
            UserDetails userDetails = // 根据claims中的信息获取UserDetails
            UsernamePasswordAuthenticationToken authentication =
                    new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        // 销毁操作,可选
    }
}

在上面的代码中,我们创建了一个JwtAuthenticationFilter,它会在每次请求之前运行,检查客服端请求头中是否包含有效的JWT。如果包含,它会从JWT中提取用户信息,并使用SecurityContextHolder来设置当前认证的用户。

4. 创建用户登录接口

当用户登录时,可以使用JwtUtils来生成JWT,并将其返回给客户端。客户端应该将这个JWT保存在本地,请确保你已经设置了JWT的生成和验证逻辑,包括创建JWT的工具类(JwtUtils)和用于存储和验证JWT中信息的密钥,下面是我创建的一个登录接口案例,仅供参考。

代码语言:java
复制
@Api(tags = "用户列表管理")
@RestController
public class SysUserController extends BaseController {
    @ApiOperation("账号密码登录")
    @GetMapping("/login")
    public AjaxResult login(@RequestParam String userName, @RequestParam String password) {
        if (userName != null && password != null) {
            SysUser sysUser = new SysUser();
            sysUser.setUserName(userName);
            sysUser.setPassword(newPassword);
            List<SysUser> user = sysUserService.list(sysUser);
            if (user.size() == 1) {
                return success(setUserInfo(user.get(0)));
            } else {
                return error("用户名或密码错误");
            }
        } else {
            return error("用户名或密码不能为空");
        }
    }


    public Map<String, Object> setUserInfo(SysUser userInfo) {
        String jwt = JwtUtil.createToken(userInfo.getUserId(), userInfo.getUserName(), userInfo.getOpenid()); //jwt包含了当前登录的员工信息
        loginInfo.setUserId(userInfo.getUserId());
        Map<String, Object> map = new HashMap<>();
        map.put("userinfo", userInfo);
        map.put("token", jwt);
        return map;
    }
}

三、 客户端存储和使用JWT

客户端(如前端应用)在登录成功后,接收到JWT后,应该将其保存在localStoragesessionStorage或cookies中。在后续的请求中,客户端应该通过HTTP请求头(如Authorization)将JWT发送给服务器进行验证。

1. 储存JWT令牌
代码语言:js
复制
const handleLogin = async () => {
  if (user.validCode == validCode.value) {
    const res: any = await login(user);
    if (res.code == 200) {
      sessionStorage.setItem("token", res.data.token);
      store.setUserInfo(res.data.userinfo);
    }
  }
}  
2. 使用JWT令牌
代码语言:js
复制
// 请求拦截器
service.interceptors.request.use(config => {
    // 每次发送请求之前判断vuex中是否存在token        
    // 如果存在,则统一在http请求的header都加上token,这样后台根据token判断你的登录情况
    // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断 
    config.headers['Authorization'] = 'Bearer ' + sessionStorage.getItem('token');
    return config;
}, error => {
    Promise.reject(error);
})

四、 刷新令牌

为了提高安全性,你可以考虑实现刷新令牌(refresh token)的机制。长期令牌(access token)通常会有较短的过期时间,而刷新令牌(refresh token)的过期时间会更长。当access token过期时,客户端可以使用refresh token来获取新的access token,而不需要用户重新登录。

五、JWT过期处理

当客户端的JWT令牌过期时,我们通过客户端发送的请求将被拒绝。你需要设计一种机制来处理这种情况,比如提示用户重新登录或自动使用refresh token来获取新的access token。

总结

使用JWT进行用户认证和授权提供了灵活性和可扩展性,使得前后端分离的应用更容易管理用户会话。通过正确配置JWT工具类,我们可以轻松地在Spring Boot应用中实现JWT认证。确保你的JWT密钥安全存储,并经常更换以防止潜在的安全风险。

最后,感谢腾讯云开发者社区小伙伴的陪伴,如果你喜欢我的博客内容,认可我的观点和经验分享,请点赞、收藏和评论,这将是对我最大的鼓励和支持。同时,也欢迎大家提出宝贵的意见和建议,让我能够更好地改进和完善我的博客。谢谢!

我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 正文内容
    • 一、JWT的结构
      • 1. Header(头部)
      • 2. Payload(负载)
      • 3. Signature(签名)
    • 二、Spring Boot中使用JWT
      • 1. 添加依赖
      • 2. 创建JWT工具类
      • 3. 创建认证过滤器
      • 4. 创建用户登录接口
    • 三、 客户端存储和使用JWT
      • 1. 储存JWT令牌
      • 2. 使用JWT令牌
    • 四、 刷新令牌
      • 五、JWT过期处理
      • 总结
      相关产品与服务
      云开发 CloudBase
      云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档