如何利用OAuth2协议实现分布式授权?

今天,我将和你一起分析在微服务系统中,如何利用OAuth2协议实现服务访问授权。让我们先来考虑这样一个场景。在微服务系统中,存在一个服务A。我们知道,在微服务系统中,每个服务都具有自己的业务边界和数据。所以,每个服务中的功能并不一定是全部对外开放,也就是说,并不是每一个服务都可以无条件的访问服务A。那么,如果我们站在服务A的角度看,它怎么判断一个HTTP请求具备访问自己的权限呢?这是我们需要解决的第一个问题。


更进一步,就算一个请求具有访问服务A的权限,但并不意味着它能够访问服务A中的所有功能。对于某些核心功能,需要具备较高的权限才能访问,而有些则不需要。这就是我们需要解决的第二个问题,也就是说,如何对服务访问的权限进行精细化管理?


现在,我们已经明确了本课程需要解决的问题,这个问题本质上就是一个授权问题。既然是授权问题,我们就不难想象在服务A之前应该存在一个授权层。这种授权层的构建也有很多方法,而在微服务架构中,目前最主流的技术体系就是使用OAuth2协议。我们先来对OAuth2协议做一个简单的介绍。

OAuth是Open Authorization的简称,面向分布式场景下开放和消费第三方接口,该协议解决的是授权问题而不是认证问题。为了理解OAuth 2协议,我们有必要先解释一下其中的几个专用名词。

首先,OAuth2协议中把需要访问的接口或服务统称为资源(Resource),而每个资源都有一个拥有者(Resource Owner)。这个资源拥有者所拥有的资源统一存放在资源服务器(Resource Server)中。同时,协议规定需要有一台授权服务器(Authorization Server),即专门用来处理对访问请求进行授权的服务器。

OAuth2协议的作用就是让客户端程序安全可控地获取用户的授权信息,并与资源服务器进行交互。OAuth2协议在客户端程序和资源服务器之间设置了一个授权层,所以客户端程序不能直接访问资源服务器,而是只能先登录授权层。资源拥有者会首先同意给客户端授权,客户端使用获得的授权,向认证服务器申请一个Token,Token中就包含了权限范围和有效期。然后,客户端使用Token,向资源服务器申请获取资源,资源服务器就根据Token的权限范围和有效期向客户端开放拥有者的资源。OAuth 2.0的基本运行流程如下图所示。


把这一流程应用到微服务架构的场景中,显然作为服务提供者的服务A,其所充当的角色就是资源服务器,而发出HTTP请求的服务就是客户端。事实上,各个服务本身都可以是客户端,也可以作为资源服务器,或者两者兼之。当客户端拿到Token之后,该Token就能在各个服务之间进行传递。


介绍完OAuth2协议的基本流程之后,我们需要明确它所提供的几种授权模式。OAuth2提供了4中授权模式,包括授权码模式(Authorization Code)、简化模式(Implicit)、密码模式(Resource Owner Password Credentials)和客户端模式(Client Credentials)。

这里,我们以比较简单也比较容易理解的密码模式为例进行展开。在密码模式下,用户向客户端提供用户名和密码,并将用户名和密码发给授权服务器并请求Token,然后授权服务器确认无误后,向客户端发放Token。

OAuth2协议的流程看起来虽然并不是很复杂,在实现上难度很大,需要综合考虑加密、授权、认证、敏感信息存储等一系列技术要点。幸好在Spring家族提供了Spring Security和Spring Cloud Security框架来简化在业务系统中集成OAuth2协议的实现过程。接下来,我们就使用这两个框架来完成OAuth2服务授权流程的实现。整个开发流程需要涉及两个维度,一方面我们需要构建一个独立的授权服务并完成用户和客户端信息的初始化,另一方面,每个微服务也需要完成与授权服务之间的集成。

我们先来看第一个维度,即构建独立的授权服务。授权服务的构建并不简单,需要有四个步骤,分别是构建授权服务器、初始化用户、初始化客户端和生成Token。


第一步是授权服务器的构建,需要我们创建一个独立的Spring Boot工程,并在启动类上添加@EnableAuthorizationServer注解

@SpringBootApplication 

@RestController

@EnableAuthorizationServer

public class AuthorizationApplication {

    public static void main(String[] args) {

        SpringApplication.run(AuthorizationApplication.class, args);

    }

}

这个@EnableAuthorizationServer注解的作用在于为微服务运行环境提供一个基于OAuth2协议的授权服务,该服务通过暴露一系列以/oauth为根节点的HTTP端点供OAuth2授权流程进行使用,例如用于生成Token的/oauth/token端点,用于授权的/oauth/authorize端点。

在构建了授权服务器之后,接下来就需要初始化密码模式下的用户和客户端信息。基于Spring Security框架,初始化用户操作所依赖的配置类是WebSecurityConfigurer类。但一般我们不需要直接操作这个配置类,而是使用框架所提供的适配器类WebSecurityConfigurerAdapter来简化该配置类的使用方式。我们可以继承WebSecurityConfigurerAdapter类并且覆写其中的configure()的方法来完成配置工作。

@Configuration

public class MyWebSecurityConfigurer extends WebSecurityConfigurerAdapter {

@Override

    protected void configure(AuthenticationManagerBuilder builder) throws Exception {

        builder.inMemoryAuthentication()

                .withUser("user1").password("password1")

.roles("USER")

                .and()

                .withUser("user2").password("password2")

.roles("ADMIN");

 }

}

这里,我们构建了"user1"和"user2"这两个用户,其密码分别是"password1"和"password2",在角色上也分别代表着普通用户USER以及管理员ADMIN。

初始化客户端也是依赖于一个配置类AuthorizationServerConfigurer。同样,我们在日常开发中,通常也是通过覆写框架提供的AuthorizationServerConfigurerAdapter类的configure()方法来简化开发过程

@Configuration

public class MyAuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

    @Override

    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        clients.inMemory()

                .withClient("myclient")

                .secret("mysecret")

                .authorizedGrantTypes("password", "refresh_token").scopes("webclient")

;

    }

}

注意到这里需要指定客户端名称、密码以及授权类型,其中的授权类型可以指定OAuth2协议所提供的四种类型中的任意一种,显然这里指定的是密码授权模式。而"refresh_token"则用于刷新操作,以便获取该授权模式下所产生的新的令牌。至于最后的"scope"配置项则用来限制客户端的访问范围,这里指定为Web端访问。

当授权服务器启动完毕,我们就可以使用http://localhost:8080/oauth/token端点来获取Token。为了演示生成Token的过程和结果,本课程使用Postman来模拟HTTP请求。在Postman中,我们首先在请求头"Authorization"中指定认证类型为“Basic Auth”,然后设置客户端名称和密码分别为“myclient”和“mysecret”。


接下来,我们在请求的"Body"部分中指定针对密码授权模式的专用配置信息,包括授权类型grant_type、授权范围scope、用户名username和密码password,这里依次填入已初始化的用户信息即可。


我们通过Postman发起这个请求,会得到http://localhost:8080/oauth/token端点的返回结果,除了作为请求参数的scope之外,我们看到在消息体中包含了access_token、token_type、refresh_token和expires_in属性。

{

    "access_token": "b7c2c7e0-0223-40e2-911d-eff82d125b80",

    "token_type": "bearer",

    "refresh_token": "40ee99d5-90f6-43ce-920f-383a619fc806",

    "expires_in": 43199,

    "scope": "webclient"

}

其中access_token 就是OAuth2 token,当访问每个受保护的服务时,用户都需要携带该Token以便进行验证;针对token_type,在OAuth2协议中存在很多种Token类型可供选择,包括bearer类型、mac类型等,这里返回的是最常见的一种类型,即bearer类型;refresh_token的作用在于当access_token 过期之后,用于重新下发一个新的access_token;expires_in属性用于指定access_token的有效时间,当超过这个有效时间时,access_token将会自动失效。至此,我们已经成功获取了可用于访问各个服务的Token信息。接下来,我们将演示如何使用该Token。

我们知道在OAuth2协议中,诸如服务A这样的单个微服务的定位就是资源服务器,Spring Security框架为此提供了专门的@EnableResourceServer注解。通过在Spring Boot的Bootstrap类上添加@EnableResourceServer注解,相当于就是声明了该服务中的所有内容都是受保护的资源:

@SpringBootApplication

@EnableResourceServer

public class TestApplication {

    public static void main(String[] args) {

        SpringApplication.run(TestApplication.class, args);

    }

}

在添加了@EnableResourceServer注解之后,微服务会对所有的HTTP请求进行验证以确定消息头中是否包含Token信息,如果没有Token信息,则会直接限制访问;如果有Token信息,就会访问授权服务器并进行Token的验证。

同时,作为资源服务器,每个微服务对于自身资源的保护粒度并不是固定的,而是可以根据需求进行控制。在日常开发过程中,我们可以使用三种不同的控制粒度,分别是只要用户层级、用户+角色层级以及用户+角色+请求方法层级。

为了实现各个层级的控制,我们的做法同样是继承一个ResourceServerConfigurerAdapter类并覆写它的configure方法,注意到这个方法的入参是一个HttpSecurity对象:

@Configuration

public class MyResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override

    public void configure(HttpSecurity httpSecurity) throws Exception{

        httpSecurity.authorizeRequests()

             .anyRequest()

             .authenticated();

    }

}

现在,如果我们访问该服务的任何HTTP端点,系统都会抛出一段错误信息“Full authentication is required to access this resource”。解决办法就是在HTTP请求中设置“Authorization”请求头并传入Token信息。注意,因为我们使用的是bearer类型的Token,所以需要在access_token的具体值之前加上“bearer”前缀。


当然,如果我们随意输入一个无效的Token,系统会抛出“invalid_token”错误。

对于某些安全性要求比较高的资源,我们不应该开放资源访问入口给所有的认证用户,而是需要限定访问资源的角色,比方说限定只有角色为“ADMIN”的管理员才能访问该服务中的资源。要想达到这种效果,实现方式也比较简单,就是在HttpSecurity 中通过antMatchers()和hasRole()方法指定想要限制的资源和角色。

@Configuration

public class MyResourceServerConfiguration extends 

ResourceServerConfigurerAdapter{

    @Override

public void configure(HttpSecurity httpSecurity) throws Exception {

        httpSecurity.authorizeRequests()

                .antMatchers("/myresource/**")

                .hasRole("ADMIN")

                .anyRequest()

                .authenticated();

    }

}

在这个示例中,我们规定"/myresource/"端点下的所有资源只有ADMIN角色才能访问。联想到前面我们初始化用户时的示例,如果这时候使用角色为“USER”的“user1”用户来访问这个端点,那么将得到“access_denied”错误。只有基于角色为“ADMIN”的“user2”用户所生成的Token才能访问这一端点中的资源。

更进一步,我们还可以针对某个端点的某个具体HTTP方法进行控制。例如,如果"/myresource/"端点下的资源进行更新的风险很高,那么就可以在HttpSecurity的antMatchers()中添加HttpMethod.UPDATE限定。

@Configuration

public class MyResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override

    public void configure(HttpSecurity httpSecurity) throws Exception{

        httpSecurity.authorizeRequests()

                .antMatchers(HttpMethod.UPDATE, "/myresource/**")

                .hasRole("ADMIN")

                .anyRequest()

                .authenticated();

    }

}

至此,关于使用OAuth2协议来对服务访问进行授权管理的实现过程就介绍完毕了。最后,作为总结,我们来梳理一下与OAuth2协议和服务访问授权相关的各个知识点。我们首先介绍了微服务系统中服务访问授权的需求和必要性。然后,我们引出了OAuth2协议这一在处理微服务访问授权是非常常用的一款安全性协议,介绍了OAuth2协议本身的工作流程、所具备的授权模式以及与微服务架构的集成方式。然后,我们基于Spring框架来完成独立授权服务的构建以及与微服务的集成,我们对这其中的实现步骤都做了详细展开。

在使用Spring Cloud等框架开发微服务系统时,我们可以使用OAuth2协议对服务访问进行授权控制。总体而言,服务访问授权是一个比较复杂的过程,实现上也有很多可以扩展的点。例如,在初始化用户和客户端的场景中,课程中演示的都是基于内存存储的数据持久化方案,我们在开发过程中可以根据需要采用关系型数据库等更为灵活和扩展的实现方案。

  • 20
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第1章 面向服务的体系架构(SOA) 1   本章主要介绍和解决以下问题,这些也是全书的基础:   HTTP协议的工作方式与HTTP网络协议栈的结构。   如何实现基于HTTP协议和TCP协议的RPC调用,它们之间有何差别,分别适应何种场景。   如何实现服务的动态注册和路由,以及软负载均衡的实现。   1.1 基于TCP协议的RPC 3   1.1.1 RPC名词解释 3   1.1.2 对象的序列化 4   1.1.3 基于TCP协议实现RPC 6   1.2 基于HTTP协议的RPC 9   1.2.1 HTTP协议栈 9   1.2.2 HTTP请求与响应 15   1.2.3 通过HttpClient发送HTTP请求 16   1.2.4 使用HTTP协议的优势 17   1.2.5 JSON和XML 18   1.2.6 RESTful和RPC 20   1.2.7 基于HTTP协议的RPC的实现 22   1.3 服务的路由和负载均衡 30   1.3.1 服务化的演变 30   1.3.2 负载均衡算法 33   1.3.3 动态配置规则 39   1.3.4 ZooKeeper介绍与环境搭建 40   1.3.5 ZooKeeper API使用简介 43   1.3.6 zkClient的使用 47   1.3.7 路由和负载均衡的实现 50   1.4 HTTP服务网关 54   第2章 分布式系统基础设施 58   本章主要介绍和解决如下问题:   分布式缓存memcache的使用及分布式策略,包括Hash算法的选择。   常见的分布式系统存储解决方案,包括MySQL的分布式扩展、HBase的API及使用场景、Redis的使用等。   如何使用分布式消息系统ActiveMQ来降低系统之间的耦合度,以及进行应用间的通信。   垂直化的搜索引擎在分布式系统中的使用,包括搜索引擎的基本原理、Lucene详细的使用介绍,以及基于Lucene的开源搜索引擎工具Solr的使用。   2.1 分布式缓存 60   2.1.1 memcache简介及安装 60   2.1.2 memcache API与分布式 64   2.1.3 分布式session 69   2.2 持久化存储 71   2.2.1 MySQL扩展 72   2.2.2 HBase 80   2.2.3 Redis 91   2.3 消息系统 95   2.3.1 ActiveMQ & JMS 96   2.4 垂直化搜索引擎 104   2.4.1 Lucene简介 105   2.4.2 Lucene的使用 108   2.4.3 Solr 119   2.5 其他基础设施 125   第3章 互联网安全架构 126   本章主要介绍和解决如下问题:   常见的Web攻击手段和防御方法,如XSS、CRSF、SQL注入等。   常见的一些安全算法,如数字摘要、对称加密、非对称加密、数字签名、数字证书等。   如何采用摘要认证方式防止信息篡改、通过数字签名验证通信双方的合法性,以及通过HTTPS协议保障通信过程中数据不被第三方监听和截获。   在开放平台体系下,OAuth协议如何保障ISV对数据的访问是经过授权的合法行为。   3.1 常见的Web攻击手段 128   3.1.1 XSS攻击 128   3.1.2 CRSF攻击 130   3.1.3 SQL注入攻击 133   3.1.4 文件上传漏洞 139   3.1.5 DDoS攻击 146   3.1.6 其他攻击手段 149   3.2 常用的安全算法 149   3.2.1 数字摘要 149   3.2.2 对称加密算法 155   3.2.3 非对称加密算法 158   3.2.4 数字签名 162   3.2.5 数字证书 166   3.3 摘要认证 185   3.3.1 为什么需要认证 185   3.3.2 摘要认证的原理 187   3.3.3 摘要认证的实现 188   3.4 签名认证 192   3.4.1 签名认证的原理 192   3.4.2 签名认证的实现 193   3.5 HTTPS协议 200   3.5.1 HTTPS协议原理 200   3.5.2 SSL/TLS 201   3.5.3 部署HTTPS Web 208   3.6 OAuth协议 215   3.6.1 OAuth的介绍 215   3.6.2 OAuth授权过程 216   第4章 系统稳定性 218   本章主要介绍和解决如下问题:   常用的在线日志分析命令的使用和日志分析脚本的编写,如cat、grep、wc、less等命令的使用,以及awk、shell脚本的编写。   如何进

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值