如何在微服务架构中实现基于角色的安全

sri*_*ain 7 spring-security spring-boot microservices spring-cloud netflix-zuul

我有一个带有 4 个微服务、eureka 服务器和一个集中式 API 网关的 spring-boot 应用程序。

所有外部流量都通过我的 API 网关到达我的微服务。

我的 API 网关 (Zuul) 正在验证和验证 JWT 令牌。

JWT 令牌由我的微服务之一在用户登录后生成(用户微服务),该令牌包含用户 ID 及其角色/权限。

现在,我想对网关以外的微服务中存在的方法实施基于角色的安全性。

我尝试使用,@PreAuthorize但它在网关之外不起作用(显然,为了使其工作,我必须SecurityContextHolder在我的微服务中设置一个 Spring Security 身份验证对象并用权限填充它)。

那么是否有任何解决方案可以实现这种安全性?

在微服务架构中设置安全性的最佳设计是什么?

API 网关级别的身份验证和微服务级别的授权?

在 API 网关级别验证 JWT 后,我是否需要在微服务中使用 spring security 或仅传递角色(将它们附加到请求中),例如创建我自己的注释并使用 Spring AOP 处理授权?

Kav*_*lai 6

目前问题很广泛,因为微服务的流量性质尚不清楚。

  • 假设所有外部流量都通过您的 API 网关到达您的微服务

    • 您不需要在 API 网关中验证 JWT 两次,然后在内部微服务中再次验证。如果 JWT 无效,请求将永远不会到达您的微服务
    • 然后 API 网关传播角色。在您的微服务中,您可以使用标头中传递的角色初始化 spring 安全上下文。它将允许您使用@PreAuthorize
  • 假设外部流量可以通过您的 API 网关以及直接到达您的微服务。

    • 现在您需要在 API 网关和微服务中验证它

更新

  • 我不了解 Zuul API 网关。这只是解决以下问题:

我尝试使用@PreAuthorize,但它无法在网关之外工作(显然为了使其工作,我必须在我的微服务的SecurityContextHolder中设置一个Spring Security身份验证对象并用权限填充它)。

    public class PreAuthenticatedUserRoleHeaderFilter 
        extends GenericFilterBean {

    public void doFilter(ServletRequest servletRequest, 
                         ServletResponse servletResponse,
                         FilterChain chain) 
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String rolesString = //extract the roles
        String userName = // extract the username

        List<GrantedAuthority> authorities 
           = AuthorityUtils.commaSeparatedStringToAuthorityList(rolesString);


        PreAuthenticatedAuthenticationToken authentication 
                = new PreAuthenticatedAuthenticationToken(
                                    userName, null, authorities);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(servletRequest, servletResponse);
    }
}
Run Code Online (Sandbox Code Playgroud)
    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, 
        jsr250Enabled = true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        PreAuthenticatedUserRoleHeaderFilter authFilter
                = new PreAuthenticatedUserRoleHeaderFilter();
        http.
                antMatcher("/**")
                .csrf()
                .disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .addFilterBefore(authFilter, 
                                 BasicAuthenticationFilter.class)
                .authorizeRequests()
                .anyRequest()
                .authenticated();
      }

    }
Run Code Online (Sandbox Code Playgroud)


doc*_*ore 6

Spring5 微服务中,您将能够找到开发微服务架构的基础,其中包含您正在寻找的几个必备条件:

关于安全性,我开发了两种不同的微服务:

  • Spring Oauth2 与 Jwt
  • Spring Jwt多应用程序安全服务,用于访问和刷新 Jwt 令牌,具有多种自定义功能,例如:定义每个令牌的内容、使用 JWS 或 JWE 等

最重要的 API 都使用 Swagger 进行了详细记录,正如您在此处看到的那样,并且所有记录的 API 都可以使用唯一的网关 Url 进行访问。

对于每个微服务的所有类别,都开发了Junit 测试。


安全

此时,我做出了几个决定:

1、网关不是验证安全的微服务吗?

因为使用网关作为“防火墙”是一种不太灵活的方法。我想决定哪些微服务需要安全性,并且每个微服务都应该在内部管理角色可以访问每个端点。总之,每个微服务都必须使用授权/身份验证,但不需要知道该功能是如何完成的。

2.具体的微服务来处理安全问题

正如我告诉你的,我开发了两种不同的方法,因为我想“玩”不同的选项/方法。最重要的优点是封装,如果“明天”我决定通过任何其他选项更改 Jwt,我只需要修改这些选项,使用它们的微服务将保留相同的代码(我很快就会向您解释如何集成)已完成)


安全集成示例

我将解释安全功能如何集成在:

1.每个管理用户和角色的应用程序都将在安全微服务中包含一个与下一个类似的文件夹用于定义其模型、存储库以获取所需信息等

2.此处定义安全微服务的全局端点。正如您所看到的,它们基本上与 2 个 Dto 一起工作:

主要优点是,只有安全微服务知道有关如何完成该功能的详细信息,使用它的其他微服务将收到包含所需信息的众所周知的 Dto。

3.Pizza-service中,安全集成主要定义在接下来的 3 个类中:

  • SecurityContextRepository从标头获取授权令牌并将其发送到SecurityManager.
  • SecurityManager使用提供的“授权令牌”进行调用security-jwt-service(它不知道它是 Jwt 还是任何其他东西)并接收一个众所周知的信息UsernameAuthoritiesDto(将其转换为 Spring 类的对象UsernamePasswordAuthenticationToken
  • WebSecurityConfiguration全局安全配置。

现在,您可以在端点中包含所需的基于角色的安全性:


最后的考虑因素

  1. pizza-serviceorder-service 是使用 Webflux 开发的,您可以在此处看到基于 MVC 微服务的等效集成(在本例中我使用了“其他安全服务”,但很容易对其进行调整)。

  2. 为了提高安全性并遵循“Oauth 方法”,请求也security-jwt-service需要包含基本身份验证。正如您在课堂上看到的SecurityManager

    private String buildAuthorizationHeader(String username, String password) {
      String auth = username + ":" + password;
      byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes());
      return "Basic " + new String(encodedAuth);
    }
    
    Run Code Online (Sandbox Code Playgroud)

数据库中用于存储该信息的表与用于管理每个应用程序的安全配置的表相同:security.jwt_client_details


小智 3

如果您的 API 网关也是创建 JWT 令牌并使用私钥签名并使用 API 网关中的公钥对您进行身份验证的人,那么您就是指定该 JWT 令牌结构的人,并且您应该能够将角色编码为该 JWT(例如,它可以是范围参数,但所有可能的范围通常可供所有用户访问)。然后,您可以将 spring boot 配置为自动从该 JWT 解析组角色(设置 SecurityContextHolder 角色的权限),并且@PreAuthorize无需任何修改即可使用注释。

如果您的 API 网关仅使用该服务器的公钥针对第 3 方授权服务器(签名和构建 JWT 的服务器)验证 JWT 令牌,则您必须实现一些自定义机制以进行基于角色的访问。我想到的一个是实现二级 Oauth2 身份验证,该身份验证仅用于使用某种“内部”JWT 的微服务和 API 网关之间的请求。例如见下图:

API网关

由于您通过 API 网关代码定义了内部 JWT 的结构,因此您可以设置自定义属性,例如角色:(管理员、用户等)。例如,可以通过从第三方授权服务器的外部 JWT 提供的用户名、ID、电子邮件来解决此问题。因此,您需要在 API 网关代码中保留一些映射,例如:

(userId: 12563) => Admin group
(userId: 45451) => User group
Run Code Online (Sandbox Code Playgroud)

由于您的微服务使用 JWT 进行身份验证,因此您可以使用spring boot resource server它来设置身份验证并将其配置为从自定义结构化内部 JWT 自动解析组(您在 SecurityContextHolder 中提到的对象)。这样,您可以简单地@PreAuthorize在微服务内使用注释,因此您不必创建自定义注释。请注意,这只是为了解决我在第一种情况下指定的第二种情况,您已经控制了 JWT 令牌。