mesher授权系统

China☆狼群 提交于 2019-12-26 13:56:56

背景

Servicecomb-mesher 是 Apache servicecomb 的服务网格项目。mesher以调用链的形式处理请求,可以根据配置自由裁剪处理函数。在控制面mesher天然能够接入apache servicecomb微服务体系。mesher 实际上在应用层作为一个代理,拦截并代替业务服务发起和接收请求,mesher 和业务服务各司其职,业务服务只要保证业务功能正常,mesher负责服务治理层面,构建可靠的通信链路。因此必须构筑开放的生态系统,以支撑业务的进一步发展。通过三方授权登录,将平台的服务各能力开发给第三方,并将第三方的服务和能力接入平台,繁荣共生,共同发展。

授权系统

授权框架

目前实现统一身份认证和授权的技术较多,总体上可归为:

  • 传统的 Cookie + Session 解决方案,有状态会话模式;
  • 基于令牌/票据的解决方案,无状态交互模式。

分布式 Session

分布式 Session 是使用悠久的成熟解决方案,但因有状态会话模式与微服务中所倡导的API导向无状态通信相互违背,对于共享式存储,技术上存在安全隐患,对于微服务而言,适用性较低。

OAuth2.0

OAuth2.0 是业界成熟的授权登录解决方案, 它是一个授权框架而不是一个认证框架,它提供了4种授权模式,能够适应多种场景,作为基于令牌的安全系统,可以广泛用于需要统一身份认证和授权的场景。

关于OAuth2的介绍和使用教程,这篇文章可以帮助你理解什么是OAuth2

OAuth2 的参与实体主要有:
** 资源所有者(resource owner)、资源服务器(resource server)、客户端(client)、 授权服务器(authorization server)。**

 +--------+                               +---------------+
 |        |--(A)- Authorization Request ->|   Resource    |
 |        |                               |     Owner     |
 |        |<-(B)-- Authorization Grant ---|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(C)-- Authorization Grant -->| Authorization |
 | Client |                               |     Server    |
 |        |<-(D)----- Access Token -------|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(E)----- Access Token ------>|    Resource   |
 |        |                               |     Server    |
 |        |<-(F)--- Protected Resource ---|               |
 +--------+                               +---------------+
              图1: 授权流程图
  • 资源所有者(resource owner):对资源具有授权能力的人。

  • 资源服务器(resource server):它存储资源,并处理对资源的访问请求。如Github资源服务器。

  • 客户端(client): 代表第三方应用,它获得资源所有者的授权后便可以去访问资源所有者的资源。

  • 授权服务器(authorization server): 授权服务器,它认证资源所有者的身份,为资源所有者提供授权审批流程,并最终颁发授权令牌(Access Token)。

关于更多 OAuth2.0 的介绍[1],请参考OAuth 2.0 官网

JWT

JSON Web Token(JWT)是一种基于开放标准RFC 7519设计,使用HMAC或RSA算法签名的无状态授权令牌;
由于令牌本身包含了授权所需要的所有信息,因此JWT非常适合分布式的单点服务授权认证。

Basic Auth(HTTP Auth)

Basic Auth简单点说明就是每次请求API时都提供用户的username和password。【base64encode(username+":"+password)】

优点:

  • 使用非常简单,开发和调试工作简单,
  • 没有复杂的页面跳转逻辑和交互过程,更利于发起方控制;

缺点:

  • 安全性低,每次都需要传递用户名和密码,用户名和密码很大程度上存在被监听盗取的可能;
  • 同时应用本地还需要保存用户名和密码,在应用本身的安全性来说,也存在很大问题;
  • 开放平台服务商出于自身安全性的考虑(第三方可以得到该服务商用户的账号密码,对于服务商来说是一种安全隐患),未来也会限制此认证方式

业界生态

Kong

  1. Kong 简介

    Kong(https://github.com/Kong/kong)是一个云原生,高效,可扩展的分布式 API 网关 [2]。

    Kong 的插件机制是其高可扩展性的根源,这也是 Kong 最优雅的一个设计。Kong 可以很方便地为路由和服务提供各种插件,网关所需要的基本特性,Kong 都如数支持:

    认证 : 支持常用的 JWT, Basic Auth, OAuth2.0 等协议

  2. Kong 中如何使用插件

  • 微服务架构中,网关应当承担所有服务共同需要的那部分功能,那么Kong 如何使用插件,如何实现授权的呢?插件(Plugins)又是装在哪?实际上大多数插件都是装在 service 或者 route 之上。

  • Kong的插件使用非常灵活,用户只需要对核心接口进行限流控制,只需要对部分接口进行权限控制,这时候,对特定的 service 和 route 进行定向的配置即可。

Kong的OAuth授权插件实现

plugins:
- name: oauth2
  service: {service}
  config: 
    scopes:
    - email
    - phone
    - address
    mandatory_scope: true
    enable_authorization_code: true

Shiro

Apache Shiro是Java的一个安全框架,功能强大。它为开发人员提供一个直观而全面的认证,授权,加密及会话管理的解决方案。

Shiro授权中的关键对象:

主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。

  • 主体:即访问应用的用户,shiro中使用Subject代表该用户;

  • 资源:应用中用户可以访问的任何东西,比如访问JSP页面、查看/编辑某些数据、访问某个业务方法等;

  • 权限:表示在应用中用户有没有操作某个资源的权力,能不能访问某个资源;

  • 角色:可以理解成权限的集合,一般情况下会赋予用户角色而不是权限,这样用户可以拥有一组权限,赋予权限时比较方便。

Shiro内部架构[3],如下图所示:
shiro

  • Subject:主体,代表了当前“用户”,这里用户并不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;

  • SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;

  • Realm:可以有1个或多个Realm,Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

其详细介绍可参考Apache Shiro官网

方案分析

分布式 Session已经不使用于微服务鉴权,basic auth存在安全隐患,且需要维护数据库,因此使用业界流行的oauth2是比较好的选择。

Kong的缺点:安装过程中依赖第三方软件较多,安装过程复杂。

Shiro的缺点:认证系统过于庞大,配置文件复杂

设计思路

  1. mesher作为可定制化的服务网格,针对开发者提供开发接口,可以根据自身业务需求而设计自己的授权系统;而针对普通用户,只需完成参数配置,即可完成授权过程。
  2. 以handler链的形式注入mesher中,用户无需理解handler链的内部结构的复杂性,而实现认证鉴权。
  3. 目前oauth2授权框架使用最广泛,也是最流行的授权方式之一,因此mesher中将使用oauth2授权。

关键原理

oauth2授权包含四种授权模式,其中安全性较高使用最广泛的为授权码模式,其中用grant_type=authorization_code 表示授权类型为授权码模式,用于获取access tokens和 refresh tokens。由于这是一个基于重定向的流程,客户端必须有能力和资源拥有者的user-agent(通常为WEB浏览器)进行交互,并且有能力接收来自授权服务器的重定向请求。

授权码模式流程图如下:

 +----------+
 | Resource |
 |   Owner  |
 |          |
 +----------+
      ^
      |
     (B)
 +----|-----+          Client Identifier      +---------------+
 |         -+----(A)-- & Redirection URI ---->|               |
 |  User-   |                                 | Authorization |
 |  Agent  -+----(B)-- User authenticates --->|     Server    |
 |          |                                 |               |
 |         -+----(C)-- Authorization Code ---<|               |
 +-|----|---+                                 +---------------+
   |    |                                         ^      v
  (A)  (C)                                        |      |
   |    |                                         |      |
   ^    v                                         |      |
 +---------+                                      |      |
 |         |>---(D)-- Authorization Code ---------'      |
 |  Client |          & Redirection URI                  |
 |         |                                             |
 |         |<---(E)----- Access Token -------------------'
 +---------+       (w/ Optional Refresh Token)

(A)client通过将资源拥有者重定向到授权服务器来初始化这个流程。client需要在请求中包含client identifier, requested scope, local state, and a redirection URI,这些内容在client被赋予(或者被拒绝)权限后也会被发送回来。

(B)授权服务器通过user-agent来对资源拥有者进行身份验证,并确定资源所有者是否授予或拒绝客户端的访问请求。

(C)假设资源所有者授予访问权限,授权服务器使用先前提供的重定向URI(在请求中或在客户端注册期间)将用户代理重定向回客户端。重定向URI包含授权代码和客户端先前提供的任一本地状态。

(D)客户端通过包含在上一步骤中接收的授权代码来请求来自授权服务器的令牌端点的访问令牌。发出请求时,客户端使用授权服务器进行身份验证。客户端通过包含用于获取验证授权码的重定向URI进行 验证。

(E)授权服务器对客户端进行身份验证,验证授权代码,并确保收到的重定向URI与步骤(C)中用于重定向客户端的URI相匹配

关键代码解读

  1. handler 链注入
    mesher提供高级别通用中间件抽象层,免于用户学习handler chain内部的复杂性,让用户只需关注与自身业务的开发。
// AuthName is a constant
const AuthName = "oauth2"
func (oa *Handler) Handle(chain *handler.Chain, inv *invocation.Invocation, cb invocation.ResponseCallBack) {
...
chain.Next(inv, func(r *invocation.Response) error {
		return cb(r)
	})
}
//Handler is is a oauth2 pre process raw data in handler
type Handler struct {
}

// Name returns router string
func (oa *Handler) Name() string {
	return AuthName
}

// NewOAuth2 returns new auth handler
func NewOAuth2() handler.Handler {
	return &Handler{}
}

func init() {
	err := handler.RegisterHandler(AuthName, NewOAuth2)
	if err != nil {
		openlogging.Error("register handler error: " + err.Error())
		return
	}
}
  1. 设置授权类型和配置文件
    mesher中需设置oauth2的授权类型,如authorization code,然后设置配置参数
    Use(&OAuth2{
		GrantType: "authorization_code",       // Registration grand type
		                                       // The default is the authorization code model
		UseConfig: &oauth2.Config{
			ClientID:     "",                  // (required, string) your client_ID
			ClientSecret: "",                  // (required, string) your client_Secret
			Scopes:       []string{""},        // (optional, string) scope specifies requested permissions
			RedirectURL:  "",                  // (required, string) redirect url to mesher
			Endpoint: oauth2.Endpoint{         // (required, string) your auth server endpoint
				AuthURL:  "",
				TokenURL: "",
			},
		},
	})
  1. 从请求端获取token
    oauth2授权最终的目的就是获取token,从而完成授权。首先从URL中获取code,然后用code获取token。token过期时间本例中设置为30分钟。
code := req.FormValue("code")
	if code == "" {
		WriteBackErr(ErrInvalidCode, http.StatusUnauthorized, cb)
		return
}
		
func getToken(code string, cb invocation.ResponseCallBack) (accessToken string, err error) {
	if auth.UseConfig != nil {
		config := auth.UseConfig
		token, err := config.Exchange(context.Background(), code)
		if err != nil {
			openlogging.Error("get token failed, errors: " + err.Error())
			WriteBackErr(ErrInvalidCode, http.StatusUnauthorized, cb)
			return "", err
		}

		// set the expiry token in 30 minutes
		token.Expiry = time.Now().Add(30 * 60 * time.Second)
		if time.Now().After(token.Expiry) {
			return "", ErrExpiredToken
		}
		accessToken = token.AccessToken
		return accessToken, nil
	}
	return "", nil
}

使用和实践

以访问github授权为例:

  1. 访问这个网站,填写登记信息。

  2. mesher 为可定制化的服务网格,提供授权码模式的用例,并定义了授权所需参数的结构体,需在mesher中补全代码。如填写以下信息:

Use(&OAuth2{
		GrantType: "authorization_code",
		Authenticate: func(at string, req *http.Request) error {
			return nil
		},
		UseConfig: &oauth2.Config{
			ClientID:     "",           // (required, string) your client_ID
			ClientSecret: "",           // (required, string) your client_Secret
			Scopes:       []string{""}, // (optional, string) scope specifies requested permissions
			RedirectURL:  "",           // (required, string) URL to redirect users going through the OAuth2 flow
			Endpoint: oauth2.Endpoint{ // (required, string) your auth server endpoint
				AuthURL:  "https://github.com/login/oauth/authorize",
			    TokenURL: "https://github.com/login/oauth/access_token",
			},
		},
	})
  1. 在网关模式下启动服务。
  2. 在浏览器中访问OAuth2 服务器的认证接口:
https://github.com/login/oauth/authorize?response_type=code&client_id=your_client_id&redirect_uri=http://localhost:30101
response_type=code : 代表期望的请求响应类型为authorization code
client_id=test: client_id为你需要使用的客户端id
redirect_uri=http://localhost:30101: redirect_uri是成功获取token之后,重定向的地址
  1. 访问认证接口成功之后,浏览器会跳转到OAuth2配置的登录页面。浏览器将会在重定向的地址上返回一个code。如下:

在这里插入图片描述
输入github账户的用户名密码,然后页面跳转到授权页面:在这里插入图片描述
然后点击页面上的授权按钮,然后浏览器会返回mesher处理获取的code

http://localhost:30101/?code=xxxxx&state=random
code=xxxxx : 本例中code就是github授权服务器返回的
state=random: state
  1. 使用 accessToken进行认证,用户的访问令牌允许GitHub代表用户向API发出请求。
Authorization: token accessToken
GET https://api.github.com/user

认证
Body中显示的内容与第三步设置的授权范围有关,使用访问令牌返回API信息。

  1. 根据得到的API信息,访问授权给用户登录的第三方网站或实现其它功能。如根据得到的用户名或者用户id实现网站注册登录功能 。例如:在本地另外起一个服务监听端口8000,在Authenticate接口中将用户名发送到本地端口,实现使用用户名登录网站功能。

参考链接

[1] 理解OAuth 2.0
[2] Kong的插件使用
[2] Apache Shiro 介绍

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!