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

2024-04-16 20:35:26 浏览数 (1)

前言

大家好,我是腾讯云开发者社区的 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腾讯技术创作特训营最新征文,快来和我瓜分大奖!

0 人点赞