Spring Cloud Oauth2 + Security 填坑记

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Cloud Oauth2 + Security 填坑记相关的知识,希望对你有一定的参考价值。

参考技术A 教程: Spring Cloud下基于OAUTH2认证授权的实现
Spring cloud微服务实战——基于OAUTH2.0统一认证授权的微服务基础架构
Spring Cloud OAuth2(一) 搭建授权服务

https://gitee.com/xingfly/Spring-CloudJiYuZuulDeTongYiShouQuanRenZheng
https://gitee.com/log4j/pig

搜索到一些文章,就照着搭,但是由于本人使用Spring Cloud 是Finchley.RELEASE版本,而教程或者demo通常是Dalston.SR3版本的。由于版本差异出现了一些问题。

Dalston.SR3版本spring-boot-starter-data-redis默认实现是Jedis,security oauth使用Redis存储token的实现类RedisTokenStore中使用Pipeline的功能。Jedis对于单节点可以支持Pipeline,但是集群则没有支持Pipeline,最终导致服务无法使用。

要解决这个问题,要么自己实现Jedis对Pipeline的支持,这比较复杂。
另一种就是使用lettuce代替Jedis,luttuce对于集群使用Pipeline有很好的支持。而且Dalston.SR3版本spring-boot-starter-data-redis也有相关的配置类,我们只需要引入luttuce的依赖:

注意luttuce的版本与spring的对应关系,错误的版本也导致无法启动。查找的时候发现不同的lettuce,groupId有不同的,连里面的包名也不同。

然后配置redisConnectionFactory:

各微服务单独做token校验时遇到了一些token传递的问题

发起请求时,需要添加Authorization请求头设置token完成校验,直接访问微服务没有问题,但是经过zuul网关转发时发现返回401。
排查发现在经过zuul转发到微服务时丢失了Authorization请求头,查找资料才知道zuul默认会过滤一些敏感header。
如果需要将token信息传递给微服务,则需要配置zuul关闭默认过滤:

这里冒号之后是空,表示没有需要过滤的header。

Feign本质上是一个新的请求,与进入这个微服务的请求并不是同一个。如果我们需要token传递给下游微服务,需要自己取出token设置给新请求。
先定义一个Feign的请求拦截器:

这里完成了取出token,设置的操作。注意 @Component 注解,然后将这个拦截器配置给FeignClient:

这样在构造Feign请求时会执行拦截器的操作,完成token的传递。

当使用hystrix时,你会发现上面Feign的拦截器中并不能获取到token。问题原因来自hystrix隔离策略,默认是线程隔离,也就是说Feign的请求执行在一个新的线程中。而SecurityContextHolder获取token对象是通过ThreadLocal存储的,也即是说与线程绑定,因此无法在新线程中获取token。

解决方法是将hystrix隔离策略(strategy)修改为:SEMAPHORE。

自定义oauth2验证失败出参

hasRole hasAuthority 区别

Spring Security OAuth2 授权失败(401) 问题整理

spring cloud gateway oauth 整合

https://gitee.com/owenwangwen/open-capacity-platform/tree/master/new-api-gateway

package com.open.capacity.client.filter;

import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;

import reactor.core.publisher.Mono;

@Component
public class AccessFilter implements GlobalFilter ,Ordered{

    // url匹配器
    private AntPathMatcher pathMatcher = new AntPathMatcher();

    @Resource
    private RedisTemplate<String, Object> redisTemplate ;

    @Override
    public int getOrder() {
        // TODO Auto-generated method stub
        return -500;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // TODO Auto-generated method stub

        String accessToken = extractToken(exchange.getRequest());

        if(pathMatcher.match("/**/v2/api-docs/**",exchange.getRequest().getPath().value())){
            return chain.filter(exchange);
        }

        if(!pathMatcher.match("/api-auth/**",exchange.getRequest().getPath().value())){
            if (accessToken == null) {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }else{
                try {
                    Map<String, Object> params =  (Map<String, Object>) redisTemplate.opsForValue().get("token:" + accessToken) ;
                    if(params.isEmpty()){
                        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                        return exchange.getResponse().setComplete();
                    }
                } catch (Exception e) {
                    exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                    return exchange.getResponse().setComplete();
                } 
            }
        }

        return chain.filter(exchange);
    }

    protected String extractToken(ServerHttpRequest request) {
        List<String> strings = request.getHeaders().get("Authorization");
        String authToken = null;
        if (strings != null) {
            authToken = strings.get(0).substring("Bearer".length()).trim();
        }

        if (StringUtils.isBlank(authToken)) {
            strings = request.getQueryParams().get("access_token");
            if (strings != null) {
                authToken = strings.get(0);
            }
        }

        return authToken;
    }

}

以上是关于Spring Cloud Oauth2 + Security 填坑记的主要内容,如果未能解决你的问题,请参考以下文章

保障微服务 Spring Cloud 安全 Oauth2

调用spring oauth2授权服务器时Spring Cloud Gateway卡住

Spring Cloud Security Oauth2集成

Spring Cloud Gateway OAuth2 with Spring Security OAuth2 Authorization Server = loop

如何覆盖 Spring Cloud OAuth2 客户端自动配置?

Spring Cloud ?????????????????? OAuth2.0 ??????????????????