软件工程应用与实践(13)——JWT

Posted 叶卡捷琳堡

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了软件工程应用与实践(13)——JWT相关的知识,希望对你有一定的参考价值。

2021SC@SDUSC

一、内容概述

在老年健康管理系统中,权限验证是个很重要的内容。在本项目中,使用了JWT的方式进行认证服务。经过小组成员讨论交流后,决定由我进行JWT内容源码的分析。

本篇博客我会首先从JWT的配置,引入开始,接着详细说明与JWT相关的工具类,最后对Controller接口调用的的相关方法进行分析。

本项目中将对以下几个类进行源码分析

在开始分析代码之前,我们需要首先了解JWT是什么。

JWT的全称是Json web token,是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

通过上面这张解释JWT原理的图,我们可以知道,JWT在应用时,需要前后端的交互,发送相应的请求及响应。

JWT在前端中的应用较为广泛,而在本篇博客中,我主要关注项目后端使用java的jwt对登录和管理权限等进行验证的源码。

二、源码分析

2.1 JWT配置

在本项目中,由于需要多次使用JWT中的某些属性,因此在对应的.yml文件中配置JWT的相关信息。在这个配置中,规定了expire为token过期时间,并且指定了RSA加密算法的私钥。

# 自定义Jwt认证服务
jwt:
  token-header: Authorization
  expire: 14400
  rsa-secret: xxxxxxxxxxx

这些写在配置文件中的属性,之后会在其他类中使用@Value注解引入,在随后讲解其他类时我们会用到。

在这里,加密使用了RSA算法,这里略微提一下RSA加密算法,RSA算法是非对称加密算法,使用公钥加密,私钥解密。

在KeyConfiguration类中,我们可以看到注入了这个属性。

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

@Configuration
@Data
public class KeyConfiguration 
    @Value("$jwt.rsa-secret")
    private String userSecret;
    private byte[] userPubKey;
    private byte[] userPriKey;

2.2 JwtAuthenticationRequest类

JwtAuthenticationRequest类中封装了Jwt权限请求的相关方法,首先我们看到类引入的包,以及类的定义

import java.io.Serializable;

public class JwtAuthenticationRequest implements Serializable


可以看到,这个类引入了Serializable接口,实现了Serializable接口,在下面的属性中,我们可以看到,这个类指定了一个序列号serialVersionUID

//指定序列号
private static final long serialVersionUID = -8442365848912354778L;

把对象转换为字节序列的过程称为对象的序列化,把字节序列恢复为对象的过程称为对象的反序列化。

通过查阅资料,我们知道,对象的序列化主要有两种用途

  • 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中
  • 在网络上传送对象的字节序列。

我个人认为,在项目中,序列化的作用是,便于在网络上传输对象的字节序列。

接下来我们看到这个类的剩下几个属性

//用户名
private String username;
//密码
private String password;
//有效码
private String verCode;
//唯一标识符
private String uuid;

值得一提的是UUID,UUID是全局唯一标识符,是在一定的范围内(从特定的名字空间到全球)唯一的机器生成的标识符。

接下来我们看到构造方法和一些对属性的set和get方法。构造方法有两个,一个是空的构造方法,另一个是可传入全部参数的构造方法。

public JwtAuthenticationRequest(String username, String password, String verCode, String uuid) 
    this.username = username;
    this.password = password;
    this.verCode = verCode;
    this.uuid = uuid;


public JwtAuthenticationRequest() 


public String getPassword() 
    return password;


public void setPassword(String password) 
    this.password = password;


public String getUsername() 
    return username;


public void setUsername(String username) 
    this.username = username;


public String getVerCode() 
    return verCode;


public void setVerCode(String verCode) 
    this.verCode = verCode;


public String getUuid() 
    return uuid;


public void setUuid(String uuid) 
    this.uuid = uuid;

2.3 JwtAuthenticationResponse类

这个类与上面的JWT请求类类似,只不过这个类用于处理JWT的响应数据.

可以看到,这个类与上面的类同样实现了Serializable接口,并自定义了一个serialVersionUID。这个类中的属性只有一个String类型的token,这个token用于前后端交互时的验证。

import java.io.Serializable;

public class JwtAuthenticationResponse implements Serializable 
    private static final long serialVersionUID = 1250234508123483573L;

    private final String token;

    public JwtAuthenticationResponse(String token) 
        this.token = token;
    

    public String getToken() 
        return this.token;
    

2.4 JwtTokenUtil类

这个类的主要作用是,对JWT验证时的token做一些简单的处理

在这个类中,首先通过属性注入的方式获取了token的失效时间,为int类型。之后使用generateToken方法获取对应的Token,getInfoFromToken用于获取传入token的具体信息。在这个类中还调用了其他几个类的方法,比如KeyConfiguration类,IJWTInfo类,JWTHelper类。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class JwtTokenUtil 

    @Value("$jwt.expire")
    private int expire;
    @Autowired
    private KeyConfiguration keyConfiguration;

    public String generateToken(IJWTInfo jwtInfo) throws Exception 
        return JWTHelper.generateToken(jwtInfo, keyConfiguration.getUserPriKey(),expire);
    

    public IJWTInfo getInfoFromToken(String token) throws Exception 
        return JWTHelper.getInfoFromToken(token, keyConfiguration.getUserPubKey());
    

在keyConfiguration类中,有三个变量,主要保存了RSA加密所用的公钥和私钥。用于对Token进行加密。

@Configuration
@Data
public class KeyConfiguration 
    @Value("$jwt.rsa-secret")
    private String userSecret;
    private byte[] userPubKey;
    private byte[] userPriKey;

2.5 UserAuthRestInterceptor类

本类的目的是对用户的请求进行拦截,并判断权限,输出对应的日志

首先我们看到这个类的引入

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

从这个类的引入可以看出,本类主要利用了日志类,HttpServletRequst类进行拦截和相应的操作

接下来我们关注对应的属性和对应的方法

  • 在属性中,自动注入了我们上面分析过的JwtTokenUtil类,并且使用preHendle函数,对用户的请求进行了拦截
  • 通过request.getHeader,获取请求头,通过传入参数获取token,如果token为空,则从cookie中获取对应的token(使用for-each循环)
  • 在afterCompletion函数中,调用super.afterCompletion函数,处理完拦截后继续传递下去(避免被持续拦截,卡在拦截器中)
public class UserAuthRestInterceptor extends HandlerInterceptorAdapter 
    private Logger logger = LoggerFactory.getLogger(UserAuthRestInterceptor.class);
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private UserConfiguration userConfiguration;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception 
        // 配置该注解,说明不进行用户拦截
        String token = request.getHeader(userConfiguration.getUserTokenHeader());
        if (StringUtils.isEmpty(token)) 
            if (request.getCookies() != null) 
                for (Cookie cookie : request.getCookies()) 
                    if (cookie.getName().equals(userConfiguration.getUserTokenHeader())) 
                        token = cookie.getValue();
                    
                
            
        
        IJWTInfo infoFromToken = jwtTokenUtil.getInfoFromToken(token);
        BaseContextHandler.setUsername(infoFromToken.getUniqueName());
        BaseContextHandler.setName(infoFromToken.getName());
        BaseContextHandler.setUserID(infoFromToken.getId());
        return super.preHandle(request, response, handler);
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception 
        BaseContextHandler.remove();
        super.afterCompletion(request, response, handler, ex);
    

三、总结

在本次的代码分析中,我主要针对本项目中JWT相关的内容进行了相关分析。总的来说,通过本次分析,我获得了不少知识,并在这个过程中,通过小组讨论与查阅资料,解决了很多问题。希望在未来的项目实训中,我也能吸收这个项目的优点,争取用在项目中。

以上是关于软件工程应用与实践(13)——JWT的主要内容,如果未能解决你的问题,请参考以下文章

山东大学软件工程应用与实践——Spark(13)代码分析

nodejs--JWT 在前后端分离中的应用与实践

软件工程应用与实践(14)——工具类分析

软件工程应用与实践(14)——工具类分析

JWT( JSON Web Token )的 实践,以及与 Session 对比

同时从移动设备和 Web 刷新 JWT 令牌的最佳实践