分布式系统认证解决方案SpringSecurityOAuth2.0(四)整合网关认证授权

文章

分布式系统认证解决方案SpringSecurityOAuth2.0(一)认证授权
分布式系统认证解决方案SpringSecurityOAuth2.0(二)分布式系统认证流程分析与实现
分布式系统认证解决方案SpringSecurityOAuth2.0(三)资源服务器使用Redis令牌、JWT令牌认证及RSA非对称加密算法
分布式系统认证解决方案SpringSecurityOAuth2.0(四)整合网关认证授权

一、简介

网关整合OAuth2.0有两种思路:
第一种是网关只做请求转发;
第二种是认证服务器生成JWT令牌,请求统一在网关层验证,判断权限等操作,在转发至其他微服务。

API网关在认证授权体系中主要负责两件事:

  1. 作为OAuth2.0的资源服务器角色,实现接入方的权限拦截。
  2. 令牌解析并转发当前登录用户信息(明文)给微服务。

微服务拿到明文token(明文token中包含登录用户的身份和权限信息)后也需要做两件事:

  1. 用户授权拦截(校验用户是否有权限访问该资源)。
  2. 将用户信息存储进当前线程的上下文(有利于后续业务逻辑随时回去当前用户信息)。

二、第一种:网关只做请求转发

这里我们使用SpringCloudGateWay作为网关服务。

搭建Gateway网关服务可以参考之间的文章:
SpringCloud微服务API网关Gateway的使用和配置(一)路由转发、断言谓词
SpringCloud微服务API网关Gateway的使用和配置(二)过滤器
SpringCloud微服务API网关Gateway的使用和配置(三)动态路由

路由配置

在网关服务的application.yml配置文件添加认证服务的转发:

          # oauth2认证授权服务
        - id: oauth2
          uri: lb://springboot-security-oauth2
          predicates:
            - Path=/oauth2/**
        # 订单服务
        - id: order
          uri: lb://springcloud-alibaba-order
          predicates:
            - Path=/order/**

网关服务的所有以/oauth2开头的请求都会转发到Oauth2认证服务。

安全配置

/**
 * @author :LiuShihao
 * @date :Created in 2021/8/18 11:49 上午
 * @desc :
 * 从Spring Cloud Gateway文档中:
 * Spring Cloud Gateway需要Spring Boot和Spring Webflux提供的Netty运行时。它不能在传统的Servlet容器中工作,也不能在构建为WAR时工作。
 * 扩展WebSecurityConfigurerAdapter是为了基于servlet的应用程序。
 * 您应该为 reactive 应用程序配置Spring Security。
 */
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class WebSecurityConfig  {

    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http.csrf().disable();
        http
                .authorizeExchange()
                .anyExchange().permitAll()
                .and()
                .formLogin()
        ;
        return http.build();
    }
}

订单服务资源

 @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
    @GetMapping("/test")
    public ResultObject decreaseAccount(){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        Object principal = authentication.getPrincipal();
        System.out.println("principal:"+principal);
        authentication.getAuthorities().stream().forEach(x->{
            System.out.println("authorization:"+x.getAuthority());
        });
//        String username = principal.toString();
        String username = authentication.getName();

        return new ResultObject(true, StatusCode.OK, SystemConstants.QUERY_SUCCESS,username+"访问Order服务资源");
    }

转发路由

在这里插入图片描述

请求网关服务申请令牌,网关服务转发到认证服务:

在这里插入图片描述
请求网关服务校验令牌,网关服务转发到认证服务:
在这里插入图片描述
不携带令牌访问网关服务转发至Order服务:

在这里插入图片描述

带上令牌访问网关服务转发至Order服务:
在这里插入图片描述

令牌的校验在微服务端进行。

三、第二种:网关解析令牌

依赖

   <!--SpringSecurity 权限-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!-- spring-security-jwt -->
   <dependency>
       <groupId>org.springframework.security</groupId>
       <artifactId>spring-security-jwt</artifactId>
       <version>1.1.0.RELEASE</version>
   </dependency>

路由配置

application.yml配置同上

安全配置

WebSecurityConfig同上

网关服务创建拦截器校验令牌

拦截校验流程:

  1. 首先拦截所有请求进行认证,
  2. 如果请求是/oauth请求认证服务申请令牌的,则放行。
  3. 校验请求头信息Authorization信息。
  4. 解析JWT令牌。
  5. 将解析后的用户认证信息添加到请求头中(可选)。
package com.lsh.gateway.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Date;
import java.util.List;

/**
 * @author :LiuShihao
 * @date :Created in 2021/8/25 1:40 下午
 * @desc :
 */
@Slf4j
@Component
public class AuthorizationFilter implements GlobalFilter, Ordered {

    /**存放公钥的文件名 */
    public String first_public = "first_public.txt";

    public String second_public = "second_public.txt";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("----------------------Gateway Global Filter Start---------------------------------");
        ServerHttpRequest request = exchange.getRequest();
        String uri = request.getURI().toString();
        log.info("URI: "+uri+" ; TIME : "+new Date().toString());
        if (uri.contains("/oauth/")){
            // 请求认证服务   放行
            log.info("----------------------Gateway Global Filter 请求OAuth认证服务:放行!---------------------------------");
            return chain.filter(exchange);
        }

        HttpHeaders headers = request.getHeaders();
        //获取请求头中的认证信息
        List<String> authorization = headers.get("Authorization");
        if (authorization == null || authorization.isEmpty() || authorization.size()==0 || authorization.get(0).length()<8){
            //没有认证信息 不允许访问,禁止访问
            ServerHttpResponse response = exchange.getResponse();
            //返回401 未授权
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            log.info("----------------------Gateway Global Filter End: 没有令牌!----------------------------");
            return exchange.getResponse().setComplete();
        }
        // Bearer token
        String access_token = authorization.get(0);
        //注意:此处的JWT令牌是从access_token值第7位开始取值(不包括第7位)
        String jwtToken = access_token.substring(7);
        //公钥(读取public.txt的公钥信息)
        Resource resource = new ClassPathResource(first_public);
        String publicKey = null;
        try {
            publicKey =  inputStream2String(resource.getInputStream());

        } catch (final IOException e) {
            throw new RuntimeException(e);
        }
        String claims =null;
        try{
            //解析 token 令牌
            Jwt jwt = JwtHelper.decodeAndVerify(jwtToken, new RsaVerifier(publicKey));
            //获取Jwt原始内容 载荷
            claims = jwt.getClaims();
            System.out.println(claims);
        }catch (Exception e){
            log.error("令牌解析失败:"+e.getMessage());
            ServerHttpResponse response = exchange.getResponse();
            //返回401 未授权
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            log.info("----------------------Gateway Global Filter End:令牌解析失败!----------------------------");
            return exchange.getResponse().setComplete();
        }

        //添加请求头 信息
        ServerHttpRequest newRequest = request.mutate().header("username", claims).build();

        ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();


        Mono<Void> filter = chain.filter(newExchange);
        log.info("----------------------Gateway Global Filter 令牌解析成功:放行!---------------------------------");
        return filter;
    }

    /**
     * 过滤器有一个优先级的问题,这个值越小,优先级越高
     * @return
     */
    @Override
    public int getOrder() {
        return -1;
    }

    public String inputStream2String(InputStream is) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(is));
        StringBuffer buffer = new StringBuffer();
        String line = "";
        while ((line = in.readLine()) != null) {
            buffer.append(line);
        }
        return buffer.toString();
    }

}

JWT令牌使用了RSA非对称加密算法,所以以上代码涉及到了公钥信息。

订单服务创建拦截器查看请求头令牌

/**
 * @author :LiuShihao
 * @date :Created in 2021/7/19 2:21 下午
 * @desc :拦截器 打印请求路径
 */
@Component
public class URIInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("URIInterceptor - RequestURL:"+request.getRequestURL());
        String jsonToken = request.getHeader("username");
        if (jsonToken != null && !"".equals(jsonToken)){
            JSONObject object = JSON.parseObject(jsonToken);
            System.out.println("URIInterceptor:username:"+object.toString());
        }
        return true;
    }
}

测试

请求订单服务:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

源码

代码已上传仓库
https://gitee.com/L1692312138/spring-cloud-alibaba

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Liu_Shihao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值