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

Posted

技术标签:

【中文标题】如何在微服务架构中实现基于角色的安全性【英文标题】:how to implement role-based security in microservices architecture [closed] 【发布时间】:2020-05-04 06:45:32 【问题描述】:

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

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

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

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

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

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

那么有什么解决方案可以实现这种安全性吗?

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

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

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

【问题讨论】:

【参考方案1】:

在Spring5 microservices 中,您将能够找到一个基础来开发具有您正在寻找的几个必要条件的微服务架构:

Registry server 使用 Eureka。 Gateway server 与 Zuul。

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

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

最重要的是使用 Swagger 进行了详细记录,如您所见 here,并且所有记录的 API 都可以使用唯一的网关 Url 访问。

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


安全

此时,我做了几个决定:

1.网关不是验证安全性的微服务。

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

2.特定的微服务来处理安全

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


安全集成示例

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

Pizza serviceeasy 微服务作为架构的一部分开发。 Spring Jwt

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

2. 安全微服务的全局端点定义为here。如您所见,它们基本上与 2 个 Dto 一起工作:

AuthenticationInformationDto UsernameAuthoritiesDto

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

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

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

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

Controller example Custom PreAuthorize annotation

最后的考虑

    pizza-service 是使用 Webflux 开发的,您可以在 order-service here 中看到基于 MVC 微服务的等效集成(在这种情况下,我使用了“其他安全服务”,但很容易适应它)。

    为了提高安全性并遵循“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);
    
    

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

【讨论】:

【参考方案2】:

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

假设所有外部流量都通过您的 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);
    

    @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();
      

    

【讨论】:

【参考方案3】:

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

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

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

(userId: 12563) => Admin group
(userId: 45451) => User group

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

【讨论】:

【参考方案4】:

现在避免了基于角色的授权,并且首选基于范围的授权,例如 service1.read、service2.full_access 范围。您可以:将基于角色的授权移动到每个服务并远离身份服务器,转换为基于范围的授权,将基于角色的授权作业移动到相应的服务,而不是在身份服务器上中继。

当您的角色/权限发生更改时,您可以用户引用令牌流并使令牌无效,this will help explaining it

【讨论】:

以上是关于如何在微服务架构中实现基于角色的安全性的主要内容,如果未能解决你的问题,请参考以下文章

大佬分享:API网关在微服务架构中的应用

阿里大佬分享API网关在微服务架构中的应用

阿里大神分享API网关在微服务架构中的应用!

基于事件驱动架构构建微服务第14部分:查询API

如何在微服务架构中进行身份验证和授权

在微服务应用程序中实现 websocket