本文是Spring Security OAuth2学习系列文章中的第三篇;主要讲解密码模式下如何使用JWT管理Token。
关于密码模式非JWT的实现示例及Spring Security OAuth2的一些基础知识,请移步本博客文章清单进行查看。
1. JWT是什么
JWT是JSON Web Token的缩写。它与标准Token不一样的地方在于,在Token中它附加存储了很多额外的信息,如Token的失效时间,Token所拥有的权限等。
在了解为什么要有JWT及什么时候使用JWT前,我们先来看下非JWT模式下资源服务器如何校验Token的有效性;
- 资源服务器收到带Token的请求;
- 资源服务器将Token发送到授权服务器/oauth/check_token接口
- 授权服务器校验Token有效性,并将Token的权限信息(如有权访问的资源清单、对资源清单能够进行的操作等)返回给资源服务器;
- 资源服务器根据返回的信息进行处理:
- Token无效,返回相应错误信息给调用方;
- Token有效,但无权限访问想要访问的资源,返回相应错误给调用方;
- Token有效,且有权限访问想要访问的资源,访问资源后返回成功给调用方;
注意到这种模式下,每次有请求时,资源服务器都要进行这样一个处理,这大大增加了资源服务器、授权服务器负担及网络开销!
那么有没有一种方式能够减少这个校验请求的次数呢?这就是JWT要解决的问题。
它的原理就是在Token中附带了权限校验需要的信息;那么在资源服务器中进行权限验证的时候就不需要访问授权服务器获取Token所对应的权限信息了。
但这又引入了另外一个问题,如何保证Token本身是有效的而不是被伪造的?JWT通过加密的方式来保证Token的有效性,可以选择多种加密方式,其中非对称方式使用的比较多:授权服务器通过私钥加密Token;然后对外暴露公钥的获取接口;资源服务器获取公钥后使用其来对Token进行解密。由于公钥基本不会变化,因此请求到的公钥资源服务器就可以缓存到本地服务中。
通过这样一系列处理,JWT校验Token时不需要再每次都请求授权服务器进行校验,相当于将校验过程从授权服务器中下沉到了资源服务器中。
通过https://jwt.io/可以解析某个Token,直接看到其中所包含的信息,如token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidGVzdFJlc291cmNlIl0sInVzZXJfbmFtZSI6InRlc3QiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXhwIjoxNTUwODU3NjA4LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiYTg5MjdmNTItMDFlNi00NjFlLWJjNjEtOGNiZjYwODFjYmZlIiwiY2xpZW50X2lkIjoidGVzdCJ9.NXlLTSkLpajmYm6R66UYRQ81n6Z3J-wE4bTDf7sMqGU
解析到的信息为:
{
"aud": [
"testResource"
],
"user_name": "test",
"scope": [
"read",
"write"
],
"exp": 1550857608,
"authorities": [
"ROLE_USER"
],
"jti": "a8927f52-01e6-461e-bc61-8cbf6081cbfe",
"client_id": "test"
}
如果想要向Token中增加更多自定义信息,可以通过TokenEnhancer接口实现。
2 实现
本节使用一个Spring Boot的示例来说明JWT的配置及使用。
2.1 MVN
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2.2 授权服务器
2.2.1 Security普通配置:
@Configuration
@EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("test").