使用 JWT
在前面的案例中,我们一直都是使用的不透明令牌(Opaque Token),在实际开发中,JWT 令牌自前使用较多,因此本节我们来看一下如何在 OAuth2 中使用 JWT。
JWT
JWT 全称为 Json Web Token,它是一种 JSON 风格的轻量级授权和身份认证规范,可实现无状态、分布式的 Web 应用授权。
JWT 作为一种规范,并没有和某一种语言绑定在一起,开发者可以使用任何语言来实现 JWT。Java 中 JWT 相关的开源库也比较多,例如 jjwt、nimbus-jose-jwt 等。
JWT 数据格式
OAuth2 中使用 JWT
Spring Security 官方推荐使用 nimbus-jose-jwt 来生成和解析 JWT 令牌,该库同时支持对称加密和非对称加密两种方式处理 JWT,本小节使用目前通用的非对称加密(RSA)来处理 JWT。
非对称加密有两种使用场景:
-
加密场景:公钥负责加密,私钥负责解密。
-
签名场景:私钥负责签名,公钥负责验证。
我们在 JWT 中使用的非对称加密属于签名场景。如果要使用 JWT,我们首先需要创建一个证书文件,这里使用 Java 自带的 keytool 工具来生成 jks 证书文件,该工具在 JDK 的 bin 目录下,生成过程如图 15-21 所示。

生成证书命令中,我们设置了生成证书的别名是 jwt,生成的证书文件是 jwt.jks。接下来输入密码以及其他信息即可,命令执行完成后,会在当前目录下生成一个 jwt.jks 文件,将该文件拷贝到 auth_server 项目的 resources 目录下,如图 15-22 所示。

接下来在 auth_server 项目中添加 JWT 依赖,代码如下(nimbus-jose-jwt 在资源服务器中有提供,所以只需要在授权服务器中添加即可):
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
</dependency>
接下来进行 JWT 配置,首先对密钥进行配置,代码如下:
class KeyConfig {
private static final String KEY_STORE_FILE = "jwt.jks";
private static final String KEY_STORE_PASSWORD = "123456";
private static final String KEY_ALIAS = "jwt";
private static KeyStoreKeyFactory KEY_STORE_KEY_FACTORY = new KeyStoreKeyFactory(
new ClassPathResource(KEY_STORE_FILE), KEY_STORE_PASSWORD.toCharArray());
static RSAPublicKey getVerifierKey() {
return (RSAPublicKey) getKeyPair().getPublic();
}
static RSAPrivateKey getSignerKey() {
return (RSAPrivateKey) getKeyPair().getPrivate();
}
private static KeyPair getKeyPair() {
return KEY_STORE_KEY_FACTORY.getKeyPair(KEY_ALIAS);
}
}
KEY_STORE_FILE 就是生成的证书文件名,KEY_STORE_PASSWORD 则是生成证书时输入的密码,KEY_ALIAS 指证书别名,然后再通过 getVerifierKey 和 getSignerKey 两个方法分别返回公钥和私钥。
接下来配置 TokenStore,代码如下:
@Configuration
public class AccessTokenConfig {
@Bean
TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
RsaSigner signer = new RsaSigner(KeyConfig.getSignerKey());
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigner(signer);
converter.setVerifier(new RsaVerifier(KeyConfig.getVerifierKey()));
return converter;
}
@Bean
public JWKSet jwkSet() {
RSAKey.Builder builder = new RSAKey.Builder(KeyConfig.getVerifierKey())
.keyUse(KeyUse.SIGNATURE)
.algorithm(JWSAlgorithm.RS256);
return new JWKSet(builder.build());
}
}
此时提供的 TokenStore 实例是 JwtTokenStore,创建该实例时需要一个 JwtAccessTokenConverter 对象,该对象是一个令牌生成工具。JwtAccessTokenConverter 对象在创建时,配置一下签名以及验证者即可。最后还需要提供一包含公钥的 JWSet 对象,该对象接下来要暴露给资源服务器。
接下来配置 AuthorizationServer,主要在 AuthorizationServerTokenServices 实例中进行配置,代码如下:
@EnableAuthorizationServer
@Configuration
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Autowired
TokenStore tokenStore;
@Autowired
JwtAccessTokenConverter jwtAccessTokenConverter;
@Bean
AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices services = new DefaultTokenServices();
services.setClientDetailsService(clientDetailsService);
services.setSupportRefreshToken(true);
services.setTokenStore(tokenStore);
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));
services.setTokenEnhancer(tokenEnhancerChain);
return services;
}
//省略其他
}
主要是在 DefaultTokenServices 中配置 TokenEnhancer,将之前的 JwtAccessTokenConverter 注入进来即可。
最后我们还需要提供一个公钥接口,资源服务器将从该接口中获取到公钥,进而完成对 JWT 的校验:
@GetMapping(value = "/oauth2/keys")
public String keys() {
return jwkSet.toString();
}
至此,我们的 auth_server 就改造完成了,接下来对 res_server 进行改造。
当采用 JWT 之后,资源服务器就不需要每次拿到令牌后都去调用授权服务器校验令牌,资源服务器只需要调用授权服务器接口获取到公钥即可。有了公钥,资源服务器就可以自已校验 JWT 令牌了。所以,对资源服务器的改动很简单,代码如下:
@Configuration
public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt()
.jwkSetUri("http://auth.javaboy.org:8881/oauth2/keys");
}
}
开启 JWT 并设置获取 JwkSet 的地址即可。
配置完成后,分别启动 auth_server 和 res_server,测试客户端依然使用 15.5.2.3 小节搭建的客户端,具体的测试过程这里就不再赘述了。