JSON Web Token (JWT)是一个开放标准,它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。JWT主要有两种使用场景。
JWT和银行存款的票据比较相似,在获取token后,后续服务器和客户端的交互都可以通过这个token完成。JWT分为三部分:Header(头部)、Payload(负载)、Signature(签名)。Header部分包括:alg和typ,alg是算法名称,typ统一都是JWT。Payload包括签发人、主题、过期时间等信息,具体如下所示。Signature是数字签名,防止数据被篡改。
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
在使用JWT的过程中,需要注意JWT的以下这些特点:
上面介绍了JWT的一些概念信息,下面就通过实际例子来演示JWT的使用,Demo地址。Demo里面包含了jwt认证授权服务器和资源服务器两部分,资源服务器的代码和之前的例子相同,只是多了在application.properties中设置jwt签名的key,所以重点看看jwt认证授权服务器即可。内容主要包含两部分,jwt的配置,这个基本都是固定配置,具体内容如下所示:
另外还有授权服务器部分的配置,和前面博客介绍的内容相同。通过这两项配置,生成的token就是jwt了,即包含了自认证的token。
启动jwt服务和资源服务后,因为授权服务配置了password和authorization_code两种授权模式,这里就尝试password模式,可以看到返回的response body中包含token和refresh_token.解析返回的jwt,可以看到有header,payload和signature三部分。从这里也可以看到,自包含令牌即授权服务器颁发的令牌包含关于用户或者客户的元数据和声明(Claims),通过检查签名,期望的颁发着(issuer),期望的接收人aud(audience),或者scope,资源服务器可以在本地校验令牌。通常的实现为签名的JSON Web Token(JWT)。
可以看到如果要支持生成JWT,实际很简单,只需要在授权认证服务器上进行一些jwt的固定配置即可。接下来看看一个完善的前后端分离的项目,如何通过jwt完成认证和授权,Demo地址。Demo启动成功后,可以通过swagger发送请求,先调用regiser接口,这样在数据库中就存入了user信息,存入数据库的用户密码信息已经进行了加密处理。
接着调用登陆认证接口,获取token信息,可以看到,token信息用“.”号区分,将token进行解析,也可以看到token被解析为header,payload,signature三部分。解析出来的user信息也就是jwtUser。
这样带着token访问其他接口,例如获取用户信息接口则能成功,否则会返回401的错误。上面展示了实际的效果,接下来看看代码上是如何实现的。与授权认证相关的有3个核心Class。SecurityConfiguration配置类继承了Spring Security的WebSecurityConfigurerAdapter类,这里设置了哪些请求可以不经过认证,哪些请求必须经过认证。将自定义的JwtAuthorizationFilter过滤器添加到Spring Security机制中。而JwtAuthorizationFilter是用户请求授权过滤器,用于从用户请求中获取token信息,并对其进行验证,同时加载与token相关联的用户身份认证信息,并添加到Spring Security上下文中。JwtConfigurer是固定写法。
上一篇博客中,生产Jwt时借助OAuth2来生成的授权的,这里的Demo代码则是通过JWT的算法来生成token,具体代码如下所示,包括验证token等也都封装在JwtUtil类中。
剩下的一些类如Repository、Entity主要是为了对数据库进行增删改查,因为user信息和user_role等信息是存入数据库的。另外,还有DTO(数据传输对象),Exception(异常类定义),Constant(一些常量定义),UserService和UserRoleService主要是调用Repository的一些方法对数据进行处理,AuthService调用其他类的方法组合了authLogin方法。最后是Configure里面的一些配置,除了配置Swagger外,还配置了springframework.data的审计功能。配置审计功能后,相关的数据库表就会自动添加上createBy,createDate等信息。以下是审计的配置类
定义审计Entity,普通Entity继承AbstractAuditingEntity即可自动进行审计功能。
总结而言,对于JWT的使用,最关键的还是生成Token,使用Token,使用Token就包括验证Token的正确性、存储Token,filter配置等。上面的例子中数据库表中只存放了username/password等信息,token是没有进行存放的,那如果要用redis来存放token应该如何实现呢?上面的demo代码中refresh-token分支上就有相关的实现,接下来看看如何通过redis来刷新token。首先在pom文件中引入redis的依赖,且在application.properties中添加redis相关配置。
org.springframework.boot
spring-boot-starter-data-redis
provided
接着配置RedisTemplate,实现JwtRedisCacheService,主要包含getValue和setValue方法。
在AuthService中,当生成jwt后,会先存入redis中,然后再写入安全上下文中。
在JwtAutherizationFilter中判断token的有效性,如果token过期,那么会读取缓存的token进行对比,如果缓存中的token和过期token一致,那么会刷新token,并把刷新的token写入redis以及写入接口的responseHeader中。
需要注意一点:这里刷新的token是直接调用JwtUtil重新生成的token,并不是借助refresh_token来获取新的token。
可以看到如果要将token等信息存入redis中并不难,主要是调用set,get方法即可。以上就是对JWT使用的基本介绍。