SpringCloud微服务——基于security+oauth2的安全保护(五):授权服务之JWT


使用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(对称加密和非对称加密)。这些在项目就通过配置就可以切换了。
最后有几个问题:

  1. 使用jwt的时候,如何退出登录?
  2. 授权服务生成jwt的时候,如果附加一些信息(如登录用户信息),然后资源服务中又如何解析这些信息?详见:JWT方式下获取登录人信息
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值