跳转至

JWT(JSON Web Token)在Java Web中的应用:从基础到最佳实践

简介

在当今的分布式和移动应用开发中,身份验证和授权是保障系统安全的关键环节。JSON Web Token(JWT)作为一种轻量级的、紧凑的、自包含的方式,用于在网络应用的各方之间安全地传输信息,已经被广泛应用。本文将深入探讨JWT在Java Web环境中的基础概念、使用方法、常见实践以及最佳实践,帮助你全面掌握并在项目中高效运用这一技术。

目录

  1. JWT基础概念
    • JWT是什么
    • JWT的结构
    • JWT的工作原理
  2. JWT在Java Web中的使用方法
    • 引入依赖
    • 生成JWT
    • 验证JWT
  3. JWT常见实践
    • 在Servlet中使用JWT
    • 在Spring Boot中使用JWT
  4. JWT最佳实践
    • 安全存储JWT
    • 设置合理的过期时间
    • 防止JWT被盗用
  5. 小结
  6. 参考资料

JWT基础概念

JWT是什么

JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于作为JSON对象在各方之间安全地传输信息。由于这些信息是经过数字签名的,因此可以被验证和信任。JWT可以包含有关实体(通常是用户)和其他数据的声明。

JWT的结构

JWT通常由三部分组成:头部(Header)、载荷(Payload)和签名(Signature),它们之间用点(.)分隔,格式为 xxxxx.yyyyy.zzzzz。 - 头部(Header):通常包含两部分信息:令牌的类型(即JWT)和使用的签名算法,如HMAC SHA256或RSA。它会被编码成一个JSON对象,然后进行Base64Url编码。 json { "alg": "HS256", "typ": "JWT" } - 载荷(Payload):是声明(claims)的集合,声明是关于实体(通常是用户)和其他数据的陈述。有三种类型的声明:注册声明(如 issexpsub 等)、公共声明和私有声明。同样会被编码成一个JSON对象,然后进行Base64Url编码。 json { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 } - 签名(Signature):用于创建签名部分,需要使用编码后的头部、编码后的载荷、一个密钥(secret)和签名算法。例如,如果使用HMAC SHA256算法,签名将按如下方式创建: HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

JWT的工作原理

  1. 用户向认证服务器提供凭证(如用户名和密码)进行身份验证。
  2. 认证服务器验证用户凭证,如果验证成功,生成一个JWT,其中包含用户的相关信息和声明。
  3. 认证服务器将生成的JWT返回给客户端。
  4. 客户端在后续的请求中,将JWT包含在请求头(通常是 Authorization 头,格式为 Bearer <token>)中发送到受保护的资源服务器。
  5. 资源服务器接收到请求后,从请求头中提取JWT,并使用与认证服务器共享的密钥或公钥对JWT进行验证。
  6. 如果JWT验证成功,资源服务器可以从JWT中获取用户信息,并授权用户访问请求的资源。

JWT在Java Web中的使用方法

引入依赖

在Maven项目中,我们可以引入 jjwt 库来处理JWT。在 pom.xml 中添加如下依赖:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.2</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>

生成JWT

以下是生成JWT的示例代码:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;

public class JwtUtil {
    private static final String SECRET_KEY = "your-secret-key";

    public static String generateToken(String username) {
        Date now = new Date();
        Date expiration = new Date(now.getTime() + 3600000); // 1小时后过期

        Claims claims = Jwts.claims().setSubject(username);
        claims.put("iat", now);
        claims.put("exp", expiration);

        return Jwts.builder()
              .setClaims(claims)
              .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
              .compact();
    }
}

验证JWT

验证JWT的示例代码如下:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;

public class JwtUtil {
    private static final String SECRET_KEY = "your-secret-key";

    public static boolean validateToken(String token) {
        try {
            Claims claims = Jwts.parserBuilder()
                  .setSigningKey(SECRET_KEY)
                  .build()
                  .parseClaimsJws(token)
                  .getBody();

            Date expiration = claims.getExpiration();
            Date now = new Date();

            return expiration.after(now);
        } catch (SignatureException e) {
            return false;
        }
    }
}

JWT常见实践

在Servlet中使用JWT

在Servlet中,我们可以在过滤器中验证JWT,以保护受限制的资源。以下是一个简单的过滤器示例:

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter("/protected/*")
public class JwtFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        String authHeader = request.getHeader("Authorization");
        if (authHeader!= null && authHeader.startsWith("Bearer ")) {
            String token = authHeader.substring(7);
            if (JwtUtil.validateToken(token)) {
                filterChain.doFilter(request, response);
                return;
            }
        }

        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    }

    // 其他方法省略
}

在Spring Boot中使用JWT

在Spring Boot项目中,我们可以通过自定义的 AuthenticationFilterAuthorizationFilter 来处理JWT。首先,创建一个配置类:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
              .cors()
              .and()
              .csrf().disable()
              .authorizeRequests()
              .antMatchers("/authenticate").permitAll()
              .anyRequest().authenticated()
              .and()
              .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

然后,创建自定义的 JwtAuthenticationFilter

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;

public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        return authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>())
        );
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        String username = ((UsernamePasswordAuthenticationToken) authResult).getPrincipal().toString();
        String token = JwtUtil.generateToken(username);
        response.addHeader("Authorization", "Bearer " + token);
    }
}

JWT最佳实践

安全存储JWT

  • 客户端:在浏览器端,JWT应存储在 HttpOnly 的Cookie中,以防止XSS攻击窃取JWT。或者使用 localStoragesessionStorage,但要注意安全风险,例如在使用 localStorage 时,要确保在页面卸载时清除JWT。
  • 服务器端:在服务器端,JWT的密钥(secret)必须严格保密,不要硬编码在代码中,建议使用环境变量或密钥管理系统来存储密钥。

设置合理的过期时间

根据应用的需求,设置合适的JWT过期时间。对于短期访问的资源,可以设置较短的过期时间,如几分钟或几小时;对于长期有效的访问,可以使用刷新令牌(Refresh Token)来延长JWT的有效期。

防止JWT被盗用

  • 使用HTTPS:确保在传输JWT时使用HTTPS协议,以加密数据,防止中间人攻击窃取JWT。
  • 防止CSRF攻击:结合CSRF防护机制(如CSRF令牌),防止跨站请求伪造攻击利用JWT进行非法操作。

小结

本文详细介绍了JWT的基础概念、在Java Web中的使用方法、常见实践以及最佳实践。通过掌握这些知识,你可以在Java Web项目中安全、高效地使用JWT进行身份验证和授权。记住,JWT的安全使用依赖于合理的配置和对安全风险的充分认识,希望本文能帮助你在项目中更好地运用这一强大的技术。

参考资料