文章目录
使用JWT来存储token的时候,主要有两种方式(两种加密方式):对称加密、非对称加密。
对称加密
对称加密的方式比较简单。现在在项目中加入jwt的对称加密方式。和之前授权服务之redis存储token一样,这个功能也是基于授权服务。
授权服务项目配置
在授权服务项目中,进行以下改造:(有个前提是,我们可以通过配置,来决定我们系统是使用jdbc存储,redis存储,还是jwt)。
配置文件
将之前使用的redis改为使用jwt对称加密:
#token存储方式
#token的存储方式,可选值为:redis,jdbc,jwt_sy,jwt_asy;默认为jdbc
fyk.authorization.token-store=jwt_sy
修改授权服务配置类:OAuth2AuthorizationConfig
先修改getTokenStore()方法:(加入了jwt对称加密存储方式)
@Bean
public TokenStore getTokenStore() {
if (JWT_SY_STORE.equalsIgnoreCase(tokenStore)) {// jwt对称加密存储方式
return new JwtTokenStore(accessTokenConverter());
} else if (REDIS_STORE.equalsIgnoreCase(tokenStore)) {// redis存储方式
RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
redisTokenStore.setAuthenticationKeyGenerator(
authentication -> "FYK" + UUID.randomUUID().toString().replace("-", ""));
return redisTokenStore;
} else {// jdbc存储方式
JdbcTokenStore jdbcTokenStore = new JdbcTokenStore(dataSource);
jdbcTokenStore.setAuthenticationKeyGenerator(
authentication -> "FYK" + UUID.randomUUID().toString().replace("-", ""));
return jdbcTokenStore;
}
}
新增accessTokenConverter()方法:由于演示,这里的签名密匙就没有单独提取出来了。
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
// 设置设置JWT签名密钥
converter.setSigningKey("fyk123");
return converter;
}
最后,对configure(AuthorizationServerEndpointsConfigurer endpoints)方法进行改造:
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// @formatter:off
endpoints
.tokenStore(getTokenStore())
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
// 设置客户端可以使用get和post方式提交
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
if(JWT_SY_STORE.equalsIgnoreCase(tokenStore)) {
// token生成方式
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter()));
endpoints.tokenEnhancer(tokenEnhancerChain);
}
// @formatter:on
}
这里,加入了判断,如果是jwt的对称加密方式,则要设置token的生成方式。
到此,授权服务项目的改造就算是完成了。
资源服务项目配置
从实现方式 这个文章中,我们已经将资源服务的配置类提取为公用的了。所以,对于资源服务项目的配置就是在这个公用配置中改造。
配置文件
将之前的资源服务中,关于oauth的配置都屏蔽掉,然后加入以下两个配置:
#token的存储方式,只有授权服务使用了jwt存储的时候,才配置这个值,这个值的作用的决定是否加载jwt相关的bean
spring.profiles.active=jwt
#token的存储方式,只有授权服务使用了jwt存储的时候,才配置这个值
fyk.authorization.token-store=jwt_sy
修改资源服务配置类:OAuth2ResourceConfig
现在是基于资源服务项目进行改造,在OAuth2ResourceConfig类中,加入以下代码:
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
if (JWT_SY_STORE.equalsIgnoreCase(tokenStore)) {// 如果是jwt,对称加密
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(this.tokenStore());
resources.tokenServices(defaultTokenServices);
} else {
super.configure(resources);
}
}
/**
* 资源服务中,token的存储方式(只有jwt方式的时候,才需要配置)
* @author FYK
* @return
*/
@Bean
@Profile("jwt")
public TokenStore tokenStore() {
return new JwtTokenStore(this.accessTokenConverter());
}
/**
* jwt中,token的编码
* @author FYK
* @return
*/
@Bean
@Profile("jwt")
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
if (JWT_SY_STORE.equalsIgnoreCase(tokenStore)) {// 如果是jwt,对称加密
converter.setSigningKey("fyk123");
}
return converter;
}
好了,到此,jwt对称加密方式就已经是完成了。启动项目,发现从授权服务中获取到token之后,资源服务就用这个token去访问保护的资源了,在这个过程中,不用再每次都去授权服务中去校验这个token是否有效。
非对称加密
生成私钥
在使用jwt非对称加密的时候,需要使用jks文件作为Token加密的密匙。
本文使用java的keytool生成jks文件(首先要安装好JDK环境)。
首先CMD打开终端窗口,要生成在哪个目录,就进入哪个目录,然后执行:
keytool -genkeypair -alias fyk-jwt -validity 365 -keyalg RSA -dname "CN=fyk,OU=xxx,O=yyy,L=cd,S=sc,C=CH" -keypass fyk123 -keystore fyk-jwt.jks -storepass fyk123
说明:
- genkeypair:生成密钥对;
- alias:别名;
- validity:有效时间,单位:天;
- keyalg:密钥算法名称;
- dname:指定证书拥有者信息:例如: “CN=名字与姓氏,OU=组织单位名称,O=组织名称,L=城市或区域名称,ST=州或省份名称,C=单位的两字母国家代码”
- keypass:密钥口令;
- keystore:密钥库名称;
- storepass:密钥库口令;
该命令执行之后,就会生成一个私钥,该文件就是在执行终端命令的文件目录中,名字为fyk-jwt.jks。复制该文件到工程下的src\main\resources目录中。
生成公钥
在上一步中fyk-jwt.jks生成的目录下,执行命令:
keytool -list -rfc --keystore fyk-jwt.jks | openssl x509 -inform pem -pubkey
执行命令后,输入密码,则会出现如下结果:
然后复制出该结果,放在公共代码中(与类OAuth2ResourceConfig平级),取名为:public.cert:
注意:这里不要复制错了,而起一定要加上-----BEGIN PUBLIC KEY-----和-----END PUBLIC KEY-----。
做好了以上两步之后,下面就对代码的改造。另外,需要说明的一点是,这两个文件,放入项目中后,也许maven在编译的时候,会导致他们乱码,如果是这样,可以在pom文件中加入如下插件:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<!-- 编译项目的时候,避免xxx.jks文件乱码 -->
<nonFilteredFileExtension>jks</nonFilteredFileExtension>
<!-- 编译项目的时候,避免xxx.cert文件乱码 -->
<nonFilteredFileExtension>cert</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
授权服务项目配置
在授权服务项目中,进行以下改造,同对称加密一样,也是使用也是再保留原有的功能的情况下,动态配置使用token存储方式。
配置文件
将之前使用的对称加密方式jwt_sy改为使用jwt非对称加密:jwt_asy
#token存储方式
#token的存储方式,可选值为:redis,jdbc,jwt_sy,jwt_asy;默认为jdbc
fyk.authorization.token-store=jwt_asy
修改授权服务配置类:OAuth2AuthorizationConfig
首先,对于getTokenStore()方法,只需要在原基础上,加上一个条件if (JWT_SY_STORE.equalsIgnoreCase(tokenStore) || JWT_ASY_STORE.equalsIgnoreCase(tokenStore)):
@Bean
public TokenStore getTokenStore() {
if (JWT_SY_STORE.equalsIgnoreCase(tokenStore) || JWT_ASY_STORE.equalsIgnoreCase(tokenStore)) {// jwt对称加密存储方式
return new JwtTokenStore(accessTokenConverter());
} else if (REDIS_STORE.equalsIgnoreCase(tokenStore)) {// redis存储方式
......
} else {// jdbc存储方式
......
}
}
然后改造accessTokenConverter()方法,增加非对称加密生成token的方式:
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
if (JWT_SY_STORE.equalsIgnoreCase(tokenStore)) {// jwt对称加密
// 设置设置JWT签名密钥
converter.setSigningKey("fyk123");
} else if (JWT_ASY_STORE.equalsIgnoreCase(tokenStore)) {// jwt非对称加密
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("fyk-jwt.jks"),
"fyk123".toCharArray());
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("fyk-jwt"));
}
return converter;
}
最后,改造configure(AuthorizationServerEndpointsConfigurer endpoints)方法,只需要在原基础上,加上一个条件if(JWT_SY_STORE.equalsIgnoreCase(tokenStore) || JWT_ASY_STORE.equalsIgnoreCase(tokenStore)):
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// @formatter:off
......
if(JWT_SY_STORE.equalsIgnoreCase(tokenStore) || JWT_ASY_STORE.equalsIgnoreCase(tokenStore)) {
......
}
// @formatter:on
}
至此,授权服务的配置改造完成。
资源服务项目配置
同上,jwt的非对称方式和对称方式修改的地方差不多,就是在原有的基础上,增加一些判断或者修改一下配置
配置文件
在jwt对称方式的基础上,将一下配置修改一下:即将jwt_sy换为jwt_asy。
#token的存储方式,只有授权服务使用了jwt存储的时候,才配置这个值,这个值的作用的决定是否加载jwt相关的bean
spring.profiles.active=jwt
#token的存储方式,只有授权服务使用了jwt存储的时候,才配置这个值
fyk.authorization.token-store=jwt_asy
修改资源服务配置类:OAuth2ResourceConfig
首先,需要对方法accessTokenConverter()进行改造:(也就是加入jwt非对称方式的解密方式)
@Bean
@Profile("jwt")
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
if (JWT_SY_STORE.equalsIgnoreCase(tokenStore)) {// 如果是jwt,对称加密
converter.setSigningKey("fyk123");
} else if (JWT_ASY_STORE.equalsIgnoreCase(tokenStore)) {// 如果是jwt,非对称加密{
Resource resource = new ClassPathResource("public.cert", getClass());
String pulblicKey;
try {
pulblicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
} catch (Exception e) {
throw new RuntimeException(e);
}
converter.setVerifierKey(pulblicKey);
}
return converter;
}
然后在方法configure(ResourceServerSecurityConfigurer resources)中加入判断:也就是说,只有jwt方式,才进行tokenServices的设置,其他方式,按照默认的来。
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
if (JWT_SY_STORE.equalsIgnoreCase(tokenStore) || JWT_ASY_STORE.equalsIgnoreCase(tokenStore)) {// 如果是jwt加密(对称和非对称)
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(this.tokenStore());
resources.tokenServices(defaultTokenServices);
} else {
super.configure(resources);
}
}
至此,jwt非对称加密方式就改造完成了。
总结
到目前为止,securit+oauth2的整合中,支持了jdbc存储token,redis存储token,jwt存储token(对称加密和非对称加密)。这些在项目就通过配置就可以切换了。
最后有几个问题:
- 使用jwt的时候,如何退出登录?
- 授权服务生成jwt的时候,如果附加一些信息(如登录用户信息),然后资源服务中又如何解析这些信息?详见:JWT方式下获取登录人信息