教程:Spring Cloud下基于OAUTH2認證授權的實現
Spring cloud微服務實戰——基于OAUTH2.0統一認證授權的微服務基礎架構
Spring Cloud OAuth2(一) 搭建授權服務
demo:
https://gitee.com/xingfly/Spring-CloudJiYuZuulDeTongYiShouQuanRenZheng
https://gitee.com/log4j/pig
搜索到一些文章,就照著搭,但是由于本人使用Spring Cloud 是Finchley.RELEASE版本,而教程或者demo通常是Dalston.SR3版本的。由于版本差異出現了一些問題。
版本問題
Dalston.SR3版本Redis集群Pipeline問題
Dalston.SR3版本spring-boot-starter-data-redis默認實現是Jedis,security oauth使用Redis存儲token的實現類RedisTokenStore中使用Pipeline的功能。Jedis對于單節點可以支持Pipeline,但是集群則沒有支持Pipeline,最終導致服務無法使用。
UnsupportedOperationException, Pipeline is currently not supported for JedisClusterConnection.
要解決這個問題,要么自己實現Jedis對Pipeline的支持,這比較復雜。
另一種就是使用lettuce代替Jedis,luttuce對于集群使用Pipeline有很好的支持。而且Dalston.SR3版本spring-boot-starter-data-redis也有相關的配置類,我們只需要引入luttuce的依賴:
<dependency>
<groupId>biz.paluch.redis</groupId>
<artifactId>lettuce</artifactId>
<version>4.4.2.Final</version>
</dependency>
注意luttuce的版本與spring的對應關系,錯誤的版本也導致無法啟動。查找的時候發現不同的lettuce,groupId有不同的,連里面的包名也不同。
然后配置redisConnectionFactory:
@Primary
@Bean
public LettuceConnectionFactory redisConnectionFactory(RedisProperties redisProperties) {
RedisProperties.Cluster cluster = redisProperties.getCluster();
if (cluster != null) {
RedisClusterConfiguration configuration = new RedisClusterConfiguration(cluster.getNodes());
return new LettuceConnectionFactory(configuration);
} else {
LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
connectionFactory.setPassword(redisProperties.getPassword());
return connectionFactory;
}
}
@Bean
public TokenStore tokenStore(RedisConnectionFactory connectionFactory) {
RedisTokenStore redisTokenStore = new RedisTokenStore(connectionFactory);
redisTokenStore.setPrefix(Const.REDIS_PREFIX);
return redisTokenStore;
}
Finchley.RELEASE版本 配置ClientDetailsServiceConfigurer時,secret需要使用passwordEncoder加密
.secret(passwordEncoder.encode("android"))
坑
各微服務單獨做token校驗時遇到了一些token傳遞的問題
zuul網關過濾Authorization請求頭
發起請求時,需要添加Authorization請求頭設置token完成校驗,直接訪問微服務沒有問題,但是經過zuul網關轉發時發現返回401。
排查發現在經過zuul轉發到微服務時丟失了Authorization請求頭,查找資料才知道zuul默認會過濾一些敏感header。
如果需要將token信息傳遞給微服務,則需要配置zuul關閉默認過濾:
zuul:
# 默認會過濾authorization、set-cookie、cookie、host、connection、content-length、content-encoding、server、transfer-encoding、x-application-context
sensitive-headers:
這里冒號之后是空,表示沒有需要過濾的header。
使用Feign進行服務消費時,沒有將Authorization請求頭傳遞給下游服務
Feign本質上是一個新的請求,與進入這個微服務的請求并不是同一個。如果我們需要token傳遞給下游微服務,需要自己取出token設置給新請求。
先定義一個Feign的請求攔截器:
@Component
public class FeignOauth2RequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
log.info("authentication:" + authentication);
if (authentication != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails auth2AuthenticationDetails = (OAuth2AuthenticationDetails) authentication.getDetails();
String token = String.format("%s %s", auth2AuthenticationDetails.getTokenType(), auth2AuthenticationDetails.getTokenValue());
log.info("feign header token:" + token);
requestTemplate.header(HttpHeaderConst.AUTHORIZATION, token);
}
}
}
這里完成了取出token,設置的操作。注意@Component
注解,然后將這個攔截器配置給FeignClient:
@Component
@FeignClient(value = ServiceNameConst.SERVICE_USER, fallback = AccessApiImpl.class,
configuration = FeignOauth2RequestInterceptor.class
)
public interface AccessApi {
這樣在構造Feign請求時會執行攔截器的操作,完成token的傳遞。
SecurityContextHolder無法獲取到token
當使用hystrix時,你會發現上面Feign的攔截器中并不能獲取到token。問題原因來自hystrix隔離策略,默認是線程隔離,也就是說Feign的請求執行在一個新的線程中。而SecurityContextHolder獲取token對象是通過ThreadLocal存儲的,也即是說與線程綁定,因此無法在新線程中獲取token。
解決方法是將hystrix隔離策略(strategy)修改為:SEMAPHORE。
參考
Spring Cloud fegin 在oauth2 中無法傳遞授權信息
feign調用session丟失解決方案
關于springcloud 使用oauth2 和 feign 的坑