详解Java token主流框架之JWT

 更新时间:2023年05月26日 08:27:35   作者:蜀山剑客李沐白  
JWT(JSON Web Token)是一种基于JSON格式的轻量级的、用于身份认证的开放标准,它通过在用户和服务器之间传递一个安全的、可靠的、独立的JSON对象来进行身份验证和授权,本文将详细给大家介绍Java token主流框架之JWT,需要的朋友可以参考下

1. JWT的概念和特点

JWT是一种轻量级、可扩展、可自包含的身份验证和授权机制。它是由三个部分组成:头部(Header)、载荷(Payload)和签名(Signature)。它的目的是为了在网络应用间传递声明信息,以便在某些情况下对用户进行身份验证和授权。

JWT有以下几个特点:

  • 简洁(Compact):JWT是一个紧凑且自包含的数据格式,它可以通过HTTP头或URL参数进行传输。
  • 自包含(Self-contained):JWT包含了足够的信息,使得客户端可以判断是否信任该令牌,而不需要查询服务器。
  • 可扩展(Extensible):由于JWT是基于JSON格式的,因此可以自定义Payload中的属性来适应各种业务需求。
  • 安全(Secure):JWT使用签名来验证发送方和接收方之间的身份。当使用加密算法时,还可以确保消息的机密性。

2. JWT的三个部分:Header、Payload和Signature

JWT由三个部分组成:头部、载荷和签名。

Header

JWT头部是一个JSON对象,它描述了生成和处理该JWT所需要的算法和类型信息。例如:

{
  "alg": "HS256",
  "typ": "JWT"
}

其中,"alg"表示签名算法,"typ"表示令牌类型。

Payload

JWT载荷是一个JSON对象,它包含了有关令牌和声明的信息。例如:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

其中,"sub"表示主题(Subject),"name"表示名称,"iat"表示令牌的发行时间(Issued At)。

除了这些声明外,JWT还支持自定义声明。例如:

{
  "iss": "http://example.com",
  "exp": 1516239922,
  "custom_key": "custom_value"
}

其中,"iss"表示颁发者(Issuer),"exp"表示到期时间(Expiration Time),"custom_key"是自定义声明。

Signature

JWT签名用于验证消息的发送方和消息的完整性。签名由头部、载荷和密钥组成,并使用指定的算法进行计算。例如,使用HS256算法可以使用以下方法生成签名:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

其中,"."是分隔符,"secret"是密钥。

3. JWT的工作过程

JWT的工作流程可以描述为以下三个步骤:

  • 用户通过用户名和密码进行身份验证。
  • 服务器对用户进行身份验证,并创建一个JWT令牌,并将其返回给客户端。
  • 客户端在以后的请求中使用该令牌进行身份验证,并在需要访问API时将其发送到服务器。

具体的工作过程如下:

  • 当用户成功登录到Web应用程序时,服务器将根据用户提供的凭据(例如用户名和密码)验证用户的身份,并创建一个JWT令牌。
  • 服务器将从Payload中提取一些信息(例如用户ID或名称)并将其加密到JWT令牌中。
  • JWT令牌将被加密,并在响应头或URL参数中返回给客户端。
  • 在以后的请求中,客户端将JWT令牌作为授权标头或URL参数发送给服务器。
  • 服务器将使用该令牌进行身份验证,并检查令牌是否被篡改或过期。如果令牌有效,则允许访问所需的API。

4. JWT常见的应用场景和优势

JWT主要应用于Web应用程序和RESTful API,以下是它的一些常见应用场景:

  • 身份验证(Authentication):JWT可以作为身份验证机制,代替传统的Cookie和Session方式。
  • 前后端分离(Front and Backend Separation):在前后端分离的架构中,JWT可以在前端和后端之间进行信任关系的建立和维护。
  • 单点登录(Single Sign On):在多个Web应用程序中共享JWT,可以实现单点登录的效果。
  • 信息交换(Information Exchange):JWT可以用于安全地在不同的系统之间传递信息。

JWT的优势包括:

  • 无状态(Stateless): JWT不需要在服务器上保存用户状态和会话信息,从而简化了服务器的负载和扩展性。
  • 可扩展(Extensible):JWT的载荷可以包含自定义声明,从而满足各种业务场景的需求。
  • 安全(Secure):JWT使用签名来验证发送方和接收方之间的身份。当使用加密算法时,还可以确保消息的机密性。
  • 支持跨域(Cross-origin):JWT可以通过HTTP头或URL参数进行传输,因此支持跨域访问。

5. 如何避免JWT的安全风险

使用JWT时,需要注意以下几点来避免安全风险:

  • 不要将敏感信息存储在JWT中:JWT可以被解密,因此不应该将任何敏感信息存储在JWT中。
  • 对于重要的操作,需要再次进行身份验证:JWT只是一种身份验证机制,而不是授权机制。即使用户已经通过JWT进行了身份验证,服务器仍然需要对他们进行授权和验证。
  • 使用HTTPS协议:由于JWT可能被篡改,因此需要确保使用HTTPS协议以确保消息的机密性和完整性。
  • 设置合理的过期时间:如果JWT永远不会过期,那么它就会成为一个长期有效的访问令牌,从而增加了访问令牌被攻击者盗用的风险。

6.代码案例

  • JJWT(Java JWT)

JJWT 是一个流行的 Java JWT 库,它提供了一种创建、编码和解码 JWT 的简便方法。以下是一个基于 JJWT 的示例:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JwtUtils {
    private static final long EXPIRATION_TIME = 864_000_000; // 10 days
    private static final String SECRET_KEY = "secretKey";
    public static String generateToken(String username) {
        Date expiryDate = new Date(System.currentTimeMillis() + EXPIRATION_TIME);
        return Jwts.builder()
                .setSubject(username)
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
                .compact();
    }
    public static String getUsernameFromToken(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
        return claims.getSubject();
    }
    public static boolean isTokenValid(String token) {
        try {
            Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

在这个示例中,JwtUtils 类包含了生成令牌、从令牌中获取用户名和验证令牌的三个方法。注意,在实际使用中,SECRET_KEY 应该更加复杂。

  • Spring Security

Spring Security 是一个流行的安全框架,它提供了一种轻松的方式来保护应用程序和 APIs。Spring Security 支持使用 JWT 进行身份验证和授权。以下是一个基于 Spring Security 的示例:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserService userService;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
                .antMatchers("/api/auth/**").permitAll()
                .anyRequest().authenticated()
                .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

在这个示例中,SecurityConfig 类扩展了 WebSecurityConfigurerAdapter 类,并覆盖了其中的 configure(HttpSecurity http) 方法。在这个方法中,Spring Security 配置了哪些 URL 公开,哪些需要进行身份验证,并且禁用了 CSRF 保护。配置完后,Spring Security 将 JWT 过滤器添加到 UsernamePasswordAuthenticationFilter 前面。

在实现身份验证之前,需要创建一个用户服务类并实现 UserDetailsService 接口。以下是一个示例:

@Service
public class UserService implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found"));
        return new org.springframework.security.core.userdetails.User(user.getUsername(),
                user.getPassword(), new ArrayList<>());
    }
}

在这个示例中,UserService 类实现了 UserDetailsService 接口,并覆盖其中的 loadUserByUsername(String username) 方法。该方法根据给定的用户名查找用户,如果用户不存在,则抛出 UsernameNotFoundException 异常。

最后,需要实现一个 JWT 过滤器来验证 JWT 并将身份验证信息加载到 Spring Security 的上下文中。以下是一个基于 Spring Security 的 JWT 过滤器示例:

public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private static final String AUTHORIZATION_HEADER = "Authorization";
    private static final String TOKEN_PREFIX = "Bearer ";
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        try {
            String token = parseToken(request);
            if (token != null && JwtUtils.isTokenValid(token)) {
                String username = JwtUtils.getUsernameFromToken(token);
                UserDetails userDetails = userService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken authenticationToken =
                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
        } catch (Exception e) {
            logger.error("Error while processing authentication request", e);
        }
        filterChain.doFilter(request, response);
    }
    private String parseToken(HttpServletRequest request) {
        String header = request.getHeader(AUTHORIZATION_HEADER);
        if (header != null && header.startsWith(TOKEN_PREFIX)) {
            return header.substring(TOKEN_PREFIX.length());
        }
        return null;
    }
}

在这个示例中,JWT 过滤器实现了 OncePerRequestFilter 类,并覆盖了其中的 doFilterInternal 方法。在该方法中,它从请求头中获取 JWT,验证 JWT,将身份验证信息加载到 Spring Security 的上下文中,然后放行请求。

  • Apache Shiro

Apache Shiro 是一个功能丰富的安全框架,可以用于保护 Java 应用程序和 APIs。Shiro 支持使用 JWT 进行身份验证和授权。以下是一个基于 Shiro 的示例:

@Configuration
public class ShiroConfig {
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm());
        return securityManager;
    }
    @Bean
    public JwtRealm realm() {
        JwtRealm realm = new JwtRealm();
        realm.setCredentialsMatcher(jwtCredentialsMatcher());
        return realm;
    }
    @Bean
    public JwtCredentialsMatcher jwtCredentialsMatcher() {
        return new JwtCredentialsMatcher();
    }
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        filterFactoryBean.setSecurityManager(securityManager);
        filterFactoryBean.setFilters(filters());
        filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinition());
        return filterFactoryBean;
    }
    private Map<String, String> filterChainDefinition() {
        Map<String, String> filterChainDefinition = new LinkedHashMap<>();
        filterChainDefinition.put("/api/auth/**", "anon");
        filterChainDefinition.put("/**", "jwt");
        return filterChainDefinition;
    }
    private Map<String, Filter> filters() {
        Map<String, Filter> filters = new HashMap<>();
        filters.put("jwt", new JwtFilter());
        return filters;
    }
}

在这个示例中,Shiro 配置了一个名为 securityManager 的安全管理器,其中包含一个名为 realm 的域。realm 类需要实现 org.apache.shiro.realm.Realm 接口,并覆盖其中的 supports(AuthenticationToken token)getAuthenticationInfo(AuthenticationToken token) 方法来验证 JWT。

类似于 Spring Security,需要实现一个 JWT 过滤器来验证 JWT 并将身份验证信息加载到 Shiro 的上下文中。以下是一个基于 Shiro 的 JWT 过滤器示例:

public class JwtFilter extends AuthenticatingFilter {
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        String token = getToken(request);
        if (StringUtils.isNotBlank(token) && JwtUtils.isTokenValid(token)) {
            return new JwtToken(token);
        }
        return null;
    }
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
        return false;
    }
    private String getToken(ServletRequest request) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("Authorization");
        if (StringUtils.isNotBlank(token) && token.startsWith("Bearer ")) {
            return token.substring(7);
        } else {
            return null;
        }
    }
}

在这个示例中,JWT 过滤器扩展了 AuthenticatingFilter 类,并覆盖了其中的 createToken(ServletRequest request, ServletResponse response)onAccessDenied(ServletRequest request, ServletResponse response) 方法。createToken 方法创建一个 JwtToken 对象,并将 JWT 设置为凭据。onAccessDenied 方法在未找到 JWT 时返回 UNAUTHORIZED 状态码。

以上就是详解Java token主流框架之JWT的详细内容,更多关于Java token框架JWT的资料请关注脚本之家其它相关文章!

相关文章

  • Springboot中的Validation参数校验详解

    Springboot中的Validation参数校验详解

    这篇文章主要介绍了Springboot中的Validation参数校验详解,Springboot参数校验是一种常用的验证机制,在传递参数时进行校验,以确保参数的有效性和正确性,该机制可以帮助开发者在代码实现前就避免一些常见的错误,需要的朋友可以参考下
    2023-10-10
  • Spring AOP底层源码详解

    Spring AOP底层源码详解

    这篇文章主要介绍了Spring AOP底层源码详解,帮助大家更好的理解和学习使用Spring AOP,感兴趣的朋友可以了解下
    2021-03-03
  • Java Calendar日历类的使用介绍

    Java Calendar日历类的使用介绍

    Candendar类是一个抽象类,提供了一些获取当前时间,或者指定的时间的字段和一些方法,我们可以通过一些方法与字段对他进行获取当前天或者当月的一些信息
    2022-09-09
  • spring中在xml配置中加载properties文件的步骤

    spring中在xml配置中加载properties文件的步骤

    这篇文章主要介绍了在spring中如何在xml配置中加载properties文件,本文分步骤给大家介绍在XML配置中加载properties文件的方法,需要的朋友可以参考下
    2023-07-07
  • javafx tableview鼠标触发更新属性详解

    javafx tableview鼠标触发更新属性详解

    这篇文章主要为大家详细介绍了javafx tableview鼠标触发更新属性的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-08-08
  • Java面试最容易被刷的重难点之锁的使用策略

    Java面试最容易被刷的重难点之锁的使用策略

    锁像synchronized同步块一样,是一种线程同步机制,但比Java中的synchronized同步块更复杂。因为锁(以及其它更高级的线程同步机制)是由synchronized同步块的方式实现的,所以我们还不能完全摆脱synchronized关键字
    2021-10-10
  • java自动生成接口文档完整代码示例

    java自动生成接口文档完整代码示例

    在软件开发中,编写接口文档是一项必要但繁琐的任务,为了简化这一过程,可以通过使用Swagger2和Swagger-UI来自动生成接口文档,这篇文章主要介绍了java自动生成接口文档的相关资料,需要的朋友可以参考下
    2021-07-07
  • @PathVariable、@RequestParam和@RequestBody的区别

    @PathVariable、@RequestParam和@RequestBody的区别

    本文主要介绍了@PathVariable、@RequestParam和@RequestBody的区别和使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05
  • 详解SpringBoot中的参数校验(项目实战)

    详解SpringBoot中的参数校验(项目实战)

    这篇文章主要介绍了SpringBoot中的参数校验,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • java webApp异步上传图片实现代码

    java webApp异步上传图片实现代码

    这篇文章主要为大家详细介绍了java webApp异步上传图片实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-11-11

最新评论