欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Spring Security】SpringCloudGateway 整合Spring Security Oauth2

程序员文章站 2022-06-13 15:42:04
...


【Spring Security】SpringCloudGateway 整合Spring Security Oauth2

前言

更新到这里呢,终于满足了读者两年的好奇心。从上年年底探索Spring Cloud GateWay,众所周知,网关最大的用途在于限流、和路由,还有一个功能就是做鉴权,一直在我心中的疑惑,这个Spring Security OAuth2是和网关怎么关联的呢,最近终于找出了答案。上图:
【Spring Security】SpringCloudGateway 整合Spring Security Oauth2
首先,介绍下流程

  1. 客户端请求认证服务进行认证。
  2. 认证服务认证通过向浏览器cookie写入token(身份令牌),认证服务请求用户中心查询用户信息,认证服务请求Spring Security申请令牌,认证服务将token(身份令牌)和jwt令牌存储至redis中,认证服务向cookie写入 token(身份令牌)。
  3. 前端携带token请求认证服务获取jwt令牌,前端获取到jwt令牌并存储在sessionStorage,前端从jwt令牌中解析中用户信息并显示在页面。
  4. 前端携带cookie中的token身份令牌及jwt令牌访问资源服务,前端请求资源服务需要携带两个token,一个是cookie中的身份令牌,一个是http header中的jwt令牌,前端请求资源服务前在http header上添加jwt请求资源。
  5. 网关校验token的合法性,用户请求必须携带token身份令牌和jwt令牌
    网关校验redis中token是否合法,已过期则要求用户重新登录
  6. 资源服务校验jwt的合法性并完成授权
    资源服务校验jwt令牌,完成授权,拥有权限的方法正常执行,没有权限的方法将拒绝访问。

身份校验

需求分析

  1. 从cookie查询用户身份令牌是否存在,不存在则拒绝访问
  2. 从http header查询jwt令牌是否存在,不存在则拒绝访问
  3. 从Redis查询user_token令牌是否过期,过期则拒绝访问

分享整体代码结构

【Spring Security】SpringCloudGateway 整合Spring Security Oauth2
已经上传到github上面, https://github.com/fafeidou/fast-cloud-nacos.git,欢迎fork。

配置application.yml,没有配置redis,使用的默认配置

spring:
  application:
    name: security-api-gateway
  cloud:
    gateway:
      discovery:
        locator:
          lower-case-service-id: true  #gateway可以通过开启以下配置来打开根据服务的serviceId来匹配路由,默认是false大写
          enabled: true # 是否可以通过其他服务的serviceId来转发到具体的服务实例。默认为false
      routes:
      - id: security-auth
        uri: lb://security-auth # lb://serviceId
        predicates:
        - Path=/service-hi/auth/**
        filters:
        - StripPrefix=1
      - id: security-provider
        uri: lb://security-provider # lb://serviceId
        predicates:
        - Path=/security-provider/**
        filters:
        - StripPrefix=1
      - id: security-consumer
        uri: lb://security-consumer # lb://serviceId
        predicates:
        - Path=/security-consumer/**
        filters:
        - StripPrefix=1
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 # 使用nacos作为注册中心

创建全局处理器·TokenGlobalFilter实现GlobalFilter接口

public class TokenGlobalFilter implements GlobalFilter {
    @Autowired
    AuthService authService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String tokenFromCookie = authService.getTokenFromCookie(request);
        if (StringUtils.isEmpty(tokenFromCookie)) {
            ExceptionCast.cast(CommonCode.UNAUTHENTICATED);
        }
        //从header中取jwt
        String jwtFromHeader = authService.getJwtFromHeader(request);
        if (StringUtils.isEmpty(jwtFromHeader)) {
            //拒绝访问
            ExceptionCast.cast(CommonCode.UNAUTHENTICATED);
        }
        //从redis取出jwt的过期时间
        long expire = authService.getExpire(tokenFromCookie);
        if (expire < 0) {
            //拒绝访问
            ExceptionCast.cast(CommonCode.FORBIDDEN);
        }
        return chain.filter(exchange);
    }
}

添加gataway全局异常处理器

spring cloud gateway 是基于webflux的,用之前的controllerAdvice,已经不生效了,这里需要我们自己去实现,这里有两个类JsonExceptionHandlerErrorHandlerConfiguration,服务之间的调用controllerAdvice是生效的。这里只给出代码片段。具体代码请看github。

public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {

    public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
                                ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }

    /**
     * 获取异常属性
     */
    @Override
    protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        int code = 500;
        Throwable error = super.getError(request);
        if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) {
            code = 404;
        }
        if (error instanceof fast.cloud.nacos.common.model.exception.CustomException) {
            CustomException customException = (CustomException) error;
            return response(code, customException.getResultCode().message());
        }
        return response(code, this.buildMessage(request, error));
    }

    /**
     * 指定响应处理方法为JSON处理的方法
     *
     * @param errorAttributes
     */
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }

    /**
     * 根据code获取对应的HttpStatus
     *
     * @param errorAttributes
     */
    @Override
    protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
        int statusCode = (int) errorAttributes.get("code");
        return HttpStatus.valueOf(statusCode);
    }

    /**
     * 构建异常信息
     *
     * @param request
     * @param ex
     * @return
     */
    private String buildMessage(ServerRequest request, Throwable ex) {
        StringBuilder message = new StringBuilder("Failed to handle request [");
        message.append(request.methodName());
        message.append(" ");
        message.append(request.uri());
        message.append("]");
        if (ex != null) {
            message.append(": ");
            message.append(ex.getMessage());
        }
        return message.toString();
    }

    /**
     * 构建返回的JSON数据格式
     *
     * @param status       状态码
     * @param errorMessage 异常信息
     * @return
     */
    public static Map<String, Object> response(int status, String errorMessage) {
        Map<String, Object> map = new HashMap<>();
        map.put("code", status);
        map.put("message", errorMessage);
        map.put("data", null);
        return map;
    }

}

测试

配置host

127.0.0.1 batman.com

启动nacos,api-gateway,auth,provider服务,使用postman测试

查看nacos已经注册这几个服务:

【Spring Security】SpringCloudGateway 整合Spring Security Oauth2

获取令牌。

POST 请求: http://batman.com:40400/auth/userlogin
username和password都是batman

【Spring Security】SpringCloudGateway 整合Spring Security Oauth2
这个时候身份令牌已经写入cookie,jwt令牌写入redis。

【Spring Security】SpringCloudGateway 整合Spring Security Oauth2
【Spring Security】SpringCloudGateway 整合Spring Security Oauth2

获取jwt令牌
GET http://batman.com:40400/auth/userjwt
【Spring Security】SpringCloudGateway 整合Spring Security Oauth2

调用provider接口
GET 请求 http://batman.com:18085/security-provider/security/hello
【Spring Security】SpringCloudGateway 整合Spring Security Oauth2

错误实例

token传到有误

【Spring Security】SpringCloudGateway 整合Spring Security Oauth2
由于篇幅问题,这篇就先写到这里,下次会分享下,微服务之间调用如何传递token的问题以及怎样实现授权。