微服务架构 | 7.1 基于 OAuth2 的安全认证

本文涉及的产品
云原生网关 MSE Higress,422元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: OAuth2 是一个基于令牌的安全验证和授权框架。他允许用户使用第三方验证服务进行验证。 如果用户成功进行了验证, 则会出示一个令牌,该令牌必须与每个请求一起发送。然后,验证服务可以对令牌进行确认;

前言

《Spring Microservices in Action》
《Spring Cloud Alibaba 微服务原理与实战》
《B站 尚硅谷 SpringCloud 框架开发教程 周阳》

OAuth2 是一个基于令牌的安全验证和授权框架。他允许用户使用第三方验证服务进行验证。 如果用户成功进行了验证, 则会出示一个令牌,该令牌必须与每个请求一起发送。然后,验证服务可以对令牌进行确认;


1. OAuth2 基础知识

1.1 安全性的 4 个组成部分

  • 受保护资源:Resource Server,开发人员想要保护的资源(如一个微服务),需要确保只有已通过验证并且具有适当授权的用户才能访问它;
  • 资源所有者:Resource Owner,资源所有者定义哪些应用程序可以调用其服务,哪些用户可以访问该服务,以及他们可以使用该服务完成哪些事情。 资源所有者注册的每个应用程序都将获得一个应用程序名称,该应用程序名称与应用程序密钥一起标识应用程序。 应用程序名称和密钥的组合是在验证 OAuth2 令牌时传递的凭据的一部分;
  • 应用程序:Client,这是代表用户调用服务的应用程序。毕竟,用户很少直接调用服务 。相反,他们依赖应用程序为他们工作。
  • OAuth2 验证服务器:Authorization Server,OAuth2 验证服务器是应用程序和正在使用的服务之间的中间人。 OAuth2 验证服务器允许用户对自己进行验证,而不必将用户凭据传递给由应用程序代表用户调用的每个服务;

1.2 OAuth2 的工作原理

  • 第三方客户端资源所有者(用户)申请认证请求;
  • 【关键】用户同意请求,返回一个许可;
  • 客户端根据许可向认证服务器申请认证令牌 Token;
  • 客户端根据认证令牌向资源服务器申请相关资源;

Oauth执行流程

1.3 OAuth2 规范的 4 种类型的授权

  • 密码( password ) ;
  • 客户端凭据( client credential ) ;;
  • 授权码( authorization code) ;
  • 隐式( imp licit );

1.4 OAuth2 的优势

  • 允许开发人员轻松与第三方云服务提供商集成,并使用这些服务进行用户验证和授权,而无须不断地将用户的凭据传递给第三方服务;

1.5 OAuth2 核心原理

  • 先有一个 OAuth2 认证服务器,用来创建和管理 OAuth2 访问令牌;
  • 接着在受保护资源主程序类上添加一个注解:@EnableResourceServer,该注解会强制执行一个过滤器,该过滤器会拦截对服务的所有传入调用,检查传入调用的 HTTP 首部中是否存在 OAuth2 访问令牌,然后调用 security.oauth2.resource.userInfoUri 中定义的回调 URL 告诉客户端与 OAuth2 认证服务器交互,查看令牌是否有效;
  • 一旦获悉令牌是有效的,@EnableResourceServer 注解也会应用任何访问控制规则,以控制什么人可以访问服务;

1.6 JSON Web Token


2. 建立 OAuth2 服务器

  • 验证服务将验证用户凭据并颁发令牌;
  • 每当用户尝试访问由,如正服务保护的服务时,验证服务将确认 OAuth2 令牌是否已由其颁发并且尚未过期;

2.1 引入 pom.xml 依赖文件

<!--security 通用安全库-->
<dependency> 
    <groupid>org.springframework.cloud</groupid> 
    <artifactid>spring-cloud-security</artifactid> 
</dependency> 
<!--oauth2.0-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

2.2 主程序类上添加注解

  • @EnableAuthorizationServer:该服务将作为 OAuth2 服务;
  • @EnableResourceServer:表示该服务是受保护资源;(该注解在 3.3 详解)

2.3 添加受保护对象的端点

在 controller 包下;
  • 该端点将映射到 /auth/user 端点,当受保护的服务调用 /auth/user 时,将会确认 OAuth2 访问令牌,并检索发文手背欧虎服务所分配的角色;
/**
 * 用户信息校验
 * 由受保护服务调用,确认 OAuth2 访问令牌,并检索访问受保护服务的用户所分配的角色
 * @param OAuth2Authentication 信息
 * @return 用户信息
 */
@RequestMapping(value = { "/user" }, produces = "application/json")
public Map<String, Object> user(OAuth2Authentication user) {
    Map<String, Object> userInfo = new HashMap<>();
    userInfo.put("user", user.getUserAuthentication().getPrincipal());
    userInfo.put("authorities", AuthorityUtils.authorityListToSet(user.getUserAuthentication().getAuthorities()));
    return userInfo;
}

2.4 定义哪些应用程序可以使用服务

在 config 包下;
  • ClientDetailsServiceConfigurer 支持两种类型的储存:内存存储和JDBC存储,如下分点所示:

2.4.1 使用 JDBC 存储

  • OAuth2Config 类
@Configuration
//继承 AuthorizationServerConfigurerAdapter 类
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private DataSource dataSource;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    //定义哪些客户端将注册到服务
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //JDBC存储:
        JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        clientDetailsService.setSelectClientDetailsSql(SecurityConstants.DEFAULT_SELECT_STATEMENT); //设置我们的自定义的sql查找语句
        clientDetailsService.setFindClientDetailsSql(SecurityConstants.DEFAULT_FIND_STATEMENT); //设置我们的自定义的sql查找语句
        clients.withClientDetails(clientDetailsService); //从 jdbc 查出数据来存储
    }
    
    @Override
    //使用 Spring 提供的默认验证管理器和用户详细信息服务
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
      endpoints
        .authenticationManager(authenticationManager)
        .userDetailsService(userDetailsService);
    }
}
  • SecurityConstants 类:里面存放上述提到的 SQL 查询语句;
public interface SecurityConstants {
    /**
     * sys_oauth_client_details 表的字段,不包括client_id、client_secret
     */
    String CLIENT_FIELDS = "client_id, client_secret, resource_ids, scope, "
            + "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
            + "refresh_token_validity, additional_information, autoapprove";

    /**
     *JdbcClientDetailsService 查询语句
     */
    String BASE_FIND_STATEMENT = "select " + CLIENT_FIELDS + " from sys_oauth_client_details";

    /**
     * 默认的查询语句
     */
    String DEFAULT_FIND_STATEMENT = BASE_FIND_STATEMENT + " order by client_id";

    /**
     * 按条件client_id 查询
     */
    String DEFAULT_SELECT_STATEMENT = BASE_FIND_STATEMENT + " where client_id = ?";
}

2.4.2 使用内存储存

@Configuration
//继承 AuthorizationServerConfigurerAdapter 类
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    //定义哪些客户端将注册到服务
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("eagleeye")  //名称
                .secret("thisissecret")  //密钥
                .authorizedGrantTypes("refresh_token", "password", "client_credentials")  //授权类型列表
                .scopes("webclient", "mobileclient");  //获取访问令牌时可以操作的范围
    }

    @Override
    //使用 Spring 提供的默认验证管理器和用户详细信息服务
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
      endpoints
        .authenticationManager(authenticationManager)
        .userDetailsService(userDetailsService);
    }
}

2.5 为应用程序定义用户 ID、密码和角色

在 config 包下:
  • 可以从内存数据存储、支持 JDBC 的关系数据库或 LDAP 服务器中存储和检索用户信息;
@Configuration
@EnableWebSecurity
//扩展核心 Spring Security 的 WebSecurityConfigurerAdapter
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    //用来处理验证
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    //处理返回用户信息
    @Override
    @Bean
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return super.userDetailsServiceBean();
    }
    
    //configure() 方法定义用户、密码与角色
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("john.carnell").password("password1").roles("USER")
                .and()
                .withUser("william.woodward").password("password2").roles("USER", "ADMIN");
    }
}
  • 上述例子中 john.carnell 用户拥有 USER 用户;
  • william.woodward 拥有 ADMIN 用户;

2.6 通过发送 POST 请求验证用户

  • 发送:POST http://localhost:8901/auth/oauth/token
  • 并在 POST 的请求体里带上应用程序名称、密钥、用户 ID 和密码,可以模拟用户获取 OAuth2 令牌;


3. 使用 OAuth2 建立并保护服务资源

  • 创建和管理 OAuth2 访问令牌是 OAuth2 服务器的职责;
  • 定义哪些用户角色有权执行哪些操作在单个服务级别上的;

3.1 引入 pom.xml 依赖文件

<!--security 通用安全库-->
<dependency> 
    <groupid>org.springframework.cloud</groupid> 
    <artifactid>spring-cloud-security</artifactid> 
</dependency> 
<!--oauth2.0-->
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
</dependency>

3.2 添加 bootstrap.yml 配置文件

security:
  oauth2:
   resource:
      userInfoUri: http://localhost:8901/auth/user
  • 这里添加回调 URL,客户端访问受保护服务时,受保护服务将调用 /auth/user 端点,向 OAuth2 服务器检查访问令牌是否生效;

3.3 在主程序类上添加注解

  • @EnableResourceServer:表示该服务是受保护资源;
  • 该注解会强制执行一个过滤器,该过滤器会拦截对服务的所有传入调用,检查传入调用的 HTTP 首部中是否存在 OAuth2 访问令牌,然后调用 security.oauth2.resource.userInfoUri 中定义的回调 URL 来查看令牌是否有效;
  • 一旦获悉令牌是有效的,@EnableResourceServer 注解也会应用任何访问控制规则,以控制什么人可以访问服务;
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //断路器
@EnableResourceServer //表示受保护资源
public class Application {
    //注入一个过滤器,会拦截对服务的所有传入调用
    @Bean
    public Filter userContextFilter() {
        UserContextFilter userContextFilter = new UserContextFilter();
        return userContextFilter;
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

3.4 定义访问控制规则

在 config 包或 security 包下;
  • 要定义访问控制规则,需要扩展 ResourceServerConfigurerAdapter 类井覆盖 configure() 方法;
  • 有多种定义方法,这里给出常见的两种定义示例:

    3.4.1 通过验证用户保护服务

    • 即:只由已通过身份验证的用户访问;
//必须使用该注解,且需要扩展 ResourceServerConfigurerAdapter 类
@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    //访问规则在 configure() 方法中定义,并且通过传入方法的 HttpSecurity 对象配置
    @Override
    public void configure(HttpSecurity http) throws Exception{
        http.authorizeRequests().anyRequest().authenticated();
    }
}
  • anyRequest().authenticated() 表示需要由已通过验证的用户访问;

3.4.2 通过特定角色保护服务

  • 限制只有 ADMIN 用户才能调用该服务的 DELETE 方法;
@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception{
        http
        .authorizeRequests()
          .antMatchers(HttpMethod.DELETE, "/v1/xxxservices/**")  //运行部开发人员限制对受保护的 URL 和 HTTP DELETE 动词的调用
          .hasRole("ADMIN")  //允许访问的角色列表
          .anyRequest()
          .authenticated();
    }
}
  • anyRequest().authenticated() 表示仍需要由已通过验证的用户访问;
  • 结合本篇《2.5 为应用程序定义用户 ID、密码和角色》的示例,这里使用 john.carnell USER 用户访问资源将被拒绝,而使用 william.woodward ADMIN 用户访问资源将被通过;


4. 在上下游服务中传播 OAuth2 访问令牌

传播 OAuth2 访问令牌

  • 用户已经向 OAuth2 服务器进行了验证,调用 EagleEye Web 客户端;
  • EagleEye Web 应用程序( OAuth2 服务器)将通过 HTTP 首都 Authorization 添加 OAuth2 访问令牌;
  • Zuul 将查找许可证服务端点,然后将调用转发到其中一个许可证服务的服务器;
  • 服务网关需要从传入的调用中复制 HTTP 首部 Authorization
  • 受保护服务使用 OAuth2 服务器确认令牌;

4.1 配置服务网关的黑名单

在 Zuul 的 application.yml 的配置文件里;
  • 因为在整个验证流程中,我们需要将 HTTP 首部 Authorization 传递上下游进行权限认证;
  • 但在默认情况下,Zuul 不会将敏感的 HTTP 首部(如 Cookie、Set-Cokkie 和 Authorization)转发到下游服务;
  • 需要配置 Zuul 的黑名单放行 Authorization;

    zuul:
      sensitiveHeaders: Cookie , Set-Cookie
  • 上述配置表示拦截 Cookie , Set-Cookie 传递下游,而 Authorization 会放行;

4.2 修改上游服务业务代码

  • 业务代码需要保证将 HTTP 首部 Authorization 注入服务的上下游;

4.2.1 下游服务

  • 这里的下游服务就是受保护的服务;
  • 其构建方法同本篇的《3. 使用 OAuth2 建立并保护服务资源》

4.2.2 在上游服务中公开 OAuth2RestTemplate 类

可以在主程序类上,也可以在主程序所在包及其子包里创建类;
  • 使该类可以被自动装配到调用另一个受 OAuth2 保护的服务;

    @Bean
    public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext, OAuth2ProtectedResourceDetails details) {
        return new OAuth2RestTemplate(details, oauth2ClientContext);
    }

4.2.3 在上游服务中用 OAuth2RestTemplate 来传播 OAuth2 访问令牌

  • 自动装配 OAuth2RestTemplate;
@Component
public class OrganizationRestTemplateClient {
    //OAuth2RestTemplate 是标准的 RestTemplate 的增强式替代品,可处理 OAuth2 访问令牌
    @Autowired
    OAuth2RestTemplate restTemplate;

    public Organization getOrganization(String organizationId){
        //调用组织服务的方式与标准的 RestTemplate 完全相同
        ResponseEntity<Organization> restExchange =
                restTemplate.exchange(
                        "http://zuulserver:5555/api/organization/v1/organizations/{organizationId}",
                        HttpMethod.GET,
                        null, Organization.class, organizationId);
        return restExchange.getBody();
    }
}

相关实践学习
消息队列+Serverless+Tablestore:实现高弹性的电商订单系统
基于消息队列以及函数计算,快速部署一个高弹性的商品订单系统,能够应对抢购场景下的高并发情况。
云安全基础课 - 访问控制概述
课程大纲 课程目标和内容介绍视频时长 访问控制概述视频时长 身份标识和认证技术视频时长 授权机制视频时长 访问控制的常见攻击视频时长
相关文章
|
3天前
|
缓存 负载均衡 JavaScript
探索微服务架构下的API网关模式
【10月更文挑战第37天】在微服务架构的海洋中,API网关犹如一座灯塔,指引着服务的航向。它不仅是客户端请求的集散地,更是后端微服务的守门人。本文将深入探讨API网关的设计哲学、核心功能以及它在微服务生态中扮演的角色,同时通过实际代码示例,揭示如何实现一个高效、可靠的API网关。
|
6天前
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
32 6
|
6天前
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
19 1
|
13天前
|
弹性计算 Kubernetes Cloud Native
云原生架构下的微服务设计原则与实践####
本文深入探讨了在云原生环境中,微服务架构的设计原则、关键技术及实践案例。通过剖析传统单体架构面临的挑战,引出微服务作为解决方案的优势,并详细阐述了微服务设计的几大核心原则:单一职责、独立部署、弹性伸缩和服务自治。文章还介绍了容器化技术、Kubernetes等云原生工具如何助力微服务的高效实施,并通过一个实际项目案例,展示了从服务拆分到持续集成/持续部署(CI/CD)流程的完整实现路径,为读者提供了宝贵的实践经验和启发。 ####
|
2天前
|
Dubbo Java 应用服务中间件
服务架构的演进:从单体到微服务的探索之旅
随着企业业务的不断拓展和复杂度的提升,对软件系统架构的要求也日益严苛。传统的架构模式在应对现代业务场景时逐渐暴露出诸多局限性,于是服务架构开启了持续演变之路。从单体架构的简易便捷,到分布式架构的模块化解耦,再到微服务架构的精细化管理,企业对技术的选择变得至关重要,尤其是 Spring Cloud 和 Dubbo 等微服务技术的对比和应用,直接影响着项目的成败。 本篇文章会从服务架构的演进开始分析,探索从单体项目到微服务项目的演变过程。然后也会对目前常见的微服务技术进行对比,找到目前市面上所常用的技术给大家进行讲解。
11 1
服务架构的演进:从单体到微服务的探索之旅
|
1天前
|
Cloud Native 安全 API
云原生架构下的微服务治理策略与实践####
—透过云原生的棱镜,探索微服务架构下的挑战与应对之道 本文旨在探讨云原生环境下,微服务架构所面临的关键挑战及有效的治理策略。随着云计算技术的深入发展,越来越多的企业选择采用云原生架构来构建和部署其应用程序,以期获得更高的灵活性、可扩展性和效率。然而,微服务架构的复杂性也带来了服务发现、负载均衡、故障恢复等一系列治理难题。本文将深入分析这些问题,并提出一套基于云原生技术栈的微服务治理框架,包括服务网格的应用、API网关的集成、以及动态配置管理等关键方面,旨在为企业实现高效、稳定的微服务架构提供参考路径。 ####
14 5
|
4天前
|
监控 API 微服务
后端技术演进:从单体架构到微服务的转变
随着互联网应用的快速增长和用户需求的不断演化,传统单体架构已难以满足现代软件开发的需求。本文深入探讨了后端技术在面对复杂系统挑战时的演进路径,重点分析了从单体架构向微服务架构转变的过程、原因及优势。通过对比分析,揭示了微服务架构如何提高系统的可扩展性、灵活性和维护效率,同时指出了实施微服务时面临的挑战和最佳实践。
17 7
|
2天前
|
Kubernetes 负载均衡 Cloud Native
云原生架构下的微服务治理策略
随着云原生技术的不断成熟,微服务架构已成为现代应用开发的主流选择。本文探讨了在云原生环境下实施微服务治理的策略和方法,重点分析了服务发现、负载均衡、故障恢复和配置管理等关键技术点,以及如何利用Kubernetes等容器编排工具来优化微服务的部署和管理。文章旨在为开发者提供一套实用的微服务治理框架,帮助其在复杂的云环境中构建高效、可靠的分布式系统。
14 5
|
2天前
|
负载均衡 监控 Cloud Native
云原生架构下的微服务治理策略与实践####
在数字化转型浪潮中,企业纷纷拥抱云计算,而云原生架构作为其核心技术支撑,正引领着一场深刻的技术变革。本文聚焦于云原生环境下微服务架构的治理策略与实践,探讨如何通过精细化的服务管理、动态的流量调度、高效的故障恢复机制以及持续的监控优化,构建弹性、可靠且易于维护的分布式系统。我们将深入剖析微服务治理的核心要素,结合具体案例,揭示其在提升系统稳定性、扩展性和敏捷性方面的关键作用,为读者提供一套切实可行的云原生微服务治理指南。 ####
|
5天前
|
消息中间件 供应链 架构师
微服务如何实现低耦合高内聚?架构师都在用的技巧!
本文介绍了微服务的拆分方法,重点讲解了“高内聚”和“低耦合”两个核心设计原则。高内聚强调每个微服务应专注于单一职责,减少代码修改范围,提高系统稳定性。低耦合则通过接口和消息队列实现服务间的解耦,确保各服务独立运作,提升系统的灵活性和可维护性。通过领域建模和事件通知机制,可以有效实现微服务的高效拆分和管理。
23 7