认证鉴权与API权限控制在微服务架构中的设计与实现(一)

作者: [Aoho’s Blog]

引言: 本文系《认证鉴权与API权限控制在微服务架构中的设计与实现》系列的第一篇,本系列预计四篇文章讲解微服务下的认证鉴权与API权限控制的实现。

1. 背景

最近在做权限相关服务的开发,在系统微服务化后,原有的单体应用是基于session的安全权限方式,不能满足现有的微服务架构的认证与鉴权需求。微服务架构下,一个应用会被拆分成若干个微应用,每个微应用都需要对访问进行鉴权,每个微应用都需要明确当前访问用户以及其权限。尤其当访问来源不只是浏览器,还包括其他服务的调用时,单体应用架构下的鉴权方式就不是特别合适了。在微服务架构下,要考虑外部应用接入的场景、用户–服务的鉴权、服务–服务的鉴权等多种鉴权场景。

最近在做权限相关服务的开发,在系统微服务化后,原有的单体应用是基于session的安全权限方式,不能满足现有的微服务架构的认证与鉴权需求。微服务架构下,一个应用会被拆分成若干个微应用,每个微应用都需要对访问进行鉴权,每个微应用都需要明确当前访问用户以及其权限。尤其当访问来源不只是浏览器,还包括其他服务的调用时,单体应用架构下的鉴权方式就不是特别合适了。在微服务架构下,要考虑外部应用接入的场景、用户–服务的鉴权、服务–服务的鉴权等多种鉴权场景。
比如用户A访问User Service,A如果未登录,则首先需要登录,请求获取授权token。获取token之后,A将携带着token去请求访问某个文件,这样就需要对A的身份进行校验,并且A可以访问该文件。

最近在做权限相关服务的开发,在系统微服务化后,原有的单体应用是基于session的安全权限方式,不能满足现有的微服务架构的认证与鉴权需求。微服务架构下,一个应用会被拆分成若干个微应用,每个微应用都需要对访问进行鉴权,每个微应用都需要明确当前访问用户以及其权限。尤其当访问来源不只是浏览器,还包括其他服务的调用时,单体应用架构下的鉴权方式就不是特别合适了。在微服务架构下,要考虑外部应用接入的场景、用户–服务的鉴权、服务–服务的鉴权等多种鉴权场景。
比如用户A访问User Service,A如果未登录,则首先需要登录,请求获取授权token。获取token之后,A将携带着token去请求访问某个文件,这样就需要对A的身份进行校验,并且A可以访问该文件。
为了适应架构的变化、需求的变化,auth权限模块被单独出来作为一个基础的微服务系统,为其他业务service提供服务。

2. 系统架构的变更

单体应用架构到分布式架构,简化的权限部分变化如下面两图所示。

单体应用架构到分布式架构,简化的权限部分变化如下面两图所示。
(1)单体应用简化版架构图:

单体应用架构到分布式架构,简化的权限部分变化如下面两图所示。
(1)单体应用简化版架构图:
[

单体架构

(2)分布式应用简化版架构图:

分布式架构

分布式架构,特别是微服务架构的优点是可以清晰的划分出业务逻辑来,让每个微服务承担职责单一的功能,毕竟越简单的东西越稳定。

但是,微服务也带来了很多的问题。比如完成一个业务操作,需要跨很多个微服务的调用,那么如何用权限系统去控制用户对不同微服务的调用,对我们来说是个挑战。当业务微服务的调用接入权限系统后,不能拖累它们的吞吐量,当权限系统出现问题后,不能阻塞它们的业务调用进度,当然更不能改变业务逻辑。新的业务微服务快速接入权限系统相对容易把控,那么对于公司已有的微服务,如何能不改动它们的架构方式的前提下,快速接入,对我们来说,也是一大挑战。

3. 技术方案

这主要包括两方面需求:其一是认证与鉴权,对于请求的用户身份的授权以及合法性鉴权;其二是API级别的操作权限控制,这个在第一点之后,当鉴定完用户身份合法之后,对于该用户的某个具体请求是否具有该操作执行权限进行校验。

3.1 认证与鉴权

对于第一个需求,笔者调查了一些实现方案:

  1. 分布式Session方案

    分布式Session方案
    分布式会话方案原理主要是将关于用户认证的信息存储在共享存储中,且通常由用户会话作为 key 来实现的简单分布式哈希映射。当用户访问微服务时,用户数据可以从共享存储中获取。在某些场景下,这种方案很不错,用户登录状态是不透明的。同时也是一个高可用且可扩展的解决方案。这种方案的缺点在于共享存储需要一定保护机制,因此需要通过安全链接来访问,这时解决方案的实现就通常具有相当高的复杂性了。

  2. 基于OAuth2 Token方案

    基于OAuth2 Token方案
    随着 Restful API、微服务的兴起,基于Token的认证现在已经越来越普遍。Token和Session ID 不同,并非只是一个 key。Token 一般会包含用户的相关信息,通过验证 Token 就可以完成身份校验。用户输入登录信息,发送到身份认证服务进行认证。AuthorizationServer验证登录信息是否正确,返回用户基础信息、权限范围、有效时间等信息,客户端存储接口。用户将 Token 放在 HTTP 请求头中,发起相关 API 调用。被调用的微服务,验证Token。ResourceServer返回相关资源和数据。

这边选用了第二种方案,基于OAuth2 Token认证的好处如下:

  • 服务端无状态:Token 机制在服务端不需要存储 session 信息,因为 Token 自身包含了所有用户的相关信息。
  • 性能较好,因为在验证 Token 时不用再去访问数据库或者远程服务进行权限校验,自然可以提升不少性能。
  • 现在很多应用都是同时面向移动端和web端,OAuth2 Token机制可以支持移动设备。
  • OAuth2与Spring Security结合使用,有提供很多开箱即用的功能,大多特性都可以通过配置灵活的变更。
  • 最后一点,也很重要,Spring Security OAuth2的文档写得较为详细。

oauth2根据使用场景不同,分成了4种模式:

  • 授权码模式(authorization code)
  • 简化模式(implicit)
  • 密码模式(resource owner password credentials)
  • 客户端模式(client credentials)

对于上述oauth2四种模式不熟的同学,可以自行百度oauth2,阮一峰的文章有解释。常使用的是password模式和client模式。

3.2 操作权限控制

对于第二个需求,笔者主要看了Spring Security和Shiro。

  1. Shiro

    Shiro
    Shiro是一个强大而灵活的开源安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。Shiro很容易入手,上手快控制粒度可糙可细。自由度高,Shiro既能配合Spring使用也可以单独使用。

  2. Spring Security

    Spring Security
    Spring社区生态很强大。除了不能脱离Spring,Spring Security具有Shiro所有的功能。而且Spring Security对Oauth、OpenID也有支持,Shiro则需要自己手动实现。Spring Security的权限细粒度更高。但是Spring Security太过复杂。

看了下网上的评论,貌似一边倒向Shiro。大部分人提出的Spring Security问题就是比较复杂难懂,文档太长。不管是Shiro还是Spring Security,其实现都是基于过滤器,对于自定义实现过滤器,我想对于很多开发者并不是很难,但是这需要团队花费时间与封装可用的jar包出来,对于后期维护和升级,以及功能的扩展。很多中小型公司并不一定具有这样的时间和人力投入这件事。笔者综合评估了下复杂性与所要实现的权限需求,以及上一个需求调研的结果,既然Spring Security功能足够强大且稳定,最终选择了Spring Security。

4. 系统架构

4.1 组件

Auth系统的最终使用组件如下:

OAuth2.0 JWT Token
Spring Security
Spring boot

4.2 步骤

主要步骤为:

  • 配置资源服务器和认证服务器
  • 配置Spring Security

上述步骤比较笼统,对于前面小节提到的需求,属于Auth系统的主要内容,笔者后面会另写文章对应讲解。

4.3 endpoint

提供的endpoint:

/oauth/token?grant_type=password #请求授权token

/oauth/token?grant_type=refresh_token #刷新token

/oauth/check_token #校验token

/logout #注销token及权限相关信息

4.4 maven依赖

主要的jar包,pom.xml文件如下:

<dependency>
          <groupId>com.auth0</groupId>
          <artifactId>java-jwt</artifactId>
          <version>2.2.0</version>
      </dependency>

      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-security</artifactId>
          <version>1.2.1-SNAPSHOT</version>
      </dependency>

      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-oauth2</artifactId>
          <version>1.2.1-SNAPSHOT</version>
      </dependency>

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
          <exclusions>
              <exclusion>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-tomcat</artifactId>
              </exclusion>
          </exclusions>
      </dependency>

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-jersey</artifactId>
          <version>1.5.3.RELEASE</version>
      </dependency>

4.5 AuthorizationServer配置文件

AuthorizationServer配置主要是覆写如下的三个方法,分别针对endpoints、clients、security配置。

@Override
   public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
      security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
   }

   @Override
   public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
   //配置客户端认证
       clients.withClientDetails(clientDetailsService(dataSource));
   }

   @Override
   public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
   //配置token的数据源、自定义的tokenServices等信息
       endpoints.authenticationManager(authenticationManager)
               .tokenStore(tokenStore(dataSource))
               .tokenServices(authorizationServerTokenServices())
               .accessTokenConverter(accessTokenConverter())
               .exceptionTranslator(webResponseExceptionTranslator);
   }

4.6 ResourceServer配置

资源服务器的配置,覆写了默认的配置。为了支持logout,这边自定义了一个CustomLogoutHandler并且将logoutSuccessHandler指定为返回http状态的HttpStatusReturningLogoutSuccessHandler

@Override
   public void configure(HttpSecurity http) throws Exception {
       http.csrf().disable()
               .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
               .and()
               .requestMatchers().antMatchers("/**")
               .and().authorizeRequests()
               .antMatchers("/**").permitAll()
               .anyRequest().authenticated()
               .and().logout()
               .logoutUrl("/logout")
               .clearAuthentication(true)
               .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())
               .addLogoutHandler(customLogoutHandler());

4.7 执行endpoint

  1. 首先执行获取授权的endpoint。
method: post
url: http://localhost:12000/oauth/token?grant_type=password
header:
{
    Authorization: Basic ZnJvbnRlbmQ6ZnJvbnRlbmQ=,
    Content-Type: application/x-www-form-urlencoded
}
body:
{
    username: keets,
    password: ***
}

上述构造了一个post请求,具体请求写得很详细。username和password是客户端提供给服务器进行校验用户身份信息。header里面的Authorization是存放的clientId和clientSecret经过编码的字符串。

上述构造了一个post请求,具体请求写得很详细。username和password是客户端提供给服务器进行校验用户身份信息。header里面的Authorization是存放的clientId和clientSecret经过编码的字符串。
返回结果如下:

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsImV4cCI6MTUwODQ0Nzc1NiwidXNlcl9uYW1lIjoia2VldHMiLCJqdGkiOiJiYWQ3MmIxOS1kOWYzLTQ5MDItYWZmYS0wNDMwZTdkYjc5ZWQiLCJjbGllbnRfaWQiOiJmcm9udGVuZCIsInNjb3BlIjpbImFsbCJdfQ.5ZNVN8TLavgpWy8KZQKArcbj7ItJLLaY1zBRaAgMjdo",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsInVzZXJfbmFtZSI6ImtlZXRzIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImJhZDcyYjE5LWQ5ZjMtNDkwMi1hZmZhLTA0MzBlN2RiNzllZCIsImV4cCI6MTUxMDk5NjU1NiwianRpIjoiYWE0MWY1MjctODE3YS00N2UyLWFhOTgtZjNlMDZmNmY0NTZlIiwiY2xpZW50X2lkIjoiZnJvbnRlbmQifQ.mICT1-lxOAqOU9M-Ud7wZBb4tTux6OQWouQJ2nn1DeE",
    "expires_in": 43195,
    "scope": "all",
    "X-KEETS-UserId": "d6448c24-3c4c-4b80-8372-c2d61868f8c6",
    "jti": "bad72b19-d9f3-4902-affa-0430e7db79ed",
    "X-KEETS-ClientId": "frontend"
}

可以看到在用户名密码通过校验后,客户端收到了授权服务器的response,主要包括access* token、refresh* token。并且表明token的类型为bearer,过期时间expires_in。笔者在jwt token中加入了自定义的info为UserId和ClientId。

2.鉴权的endpoint

method: post
url: http://localhost:12000/oauth/check_token
header:
{
    Authorization: Basic ZnJvbnRlbmQ6ZnJvbnRlbmQ=,
    Content-Type: application/x-www-form-urlencoded
}
body:
{
    token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsImV4cCI6MTUwODQ0Nzc1NiwidXNlcl9uYW1lIjoia2VldHMiLCJqdGkiOiJiYWQ3MmIxOS1kOWYzLTQ5MDItYWZmYS0wNDMwZTdkYjc5ZWQiLCJjbGllbnRfaWQiOiJmcm9udGVuZCIsInNjb3BlIjpbImFsbCJdfQ.5ZNVN8TLavgpWy8KZQKArcbj7ItJLLaY1zBRaAgMjdo
}

上面即为check_token请求的详细信息。需要注意的是,笔者将刚刚授权的token放在了body里面,这边可以有多种方法,此处不扩展。

{
    "X-KEETS-UserId": "d6448c24-3c4c-4b80-8372-c2d61868f8c6",
    "user_name": "keets",
    "scope": [
        "all"
    ],
    "active": true,
    "exp": 1508447756,
    "X-KEETS-ClientId": "frontend",
    "jti": "bad72b19-d9f3-4902-affa-0430e7db79ed",
    "client_id": "frontend"
}

校验token合法后,返回的response如上所示。在response中也是展示了相应的token中的基本信息。

3.刷新token

3.刷新token
由于token的时效一般不会很长,而refresh* token一般周期会很长,为了不影响用户的体验,可以使用refresh* token去动态的刷新token。

method: post
url: http://localhost:12000/oauth/token?grant_type=refresh_token&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsInVzZXJfbmFtZSI6ImtlZXRzIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImJhZDcyYjE5LWQ5ZjMtNDkwMi1hZmZhLTA0MzBlN2RiNzllZCIsImV4cCI6MTUxMDk5NjU1NiwianRpIjoiYWE0MWY1MjctODE3YS00N2UyLWFhOTgtZjNlMDZmNmY0NTZlIiwiY2xpZW50X2lkIjoiZnJvbnRlbmQifQ.mICT1-lxOAqOU9M-Ud7wZBb4tTux6OQWouQJ2nn1DeE
header:
{
    Authorization: Basic ZnJvbnRlbmQ6ZnJvbnRlbmQ=
}

其response和/oauth/token得到正常的相应是一样的,此处不再列出。

4.注销token

method: get
url: http://localhost:9000/logout
header:
{
    Authorization: bearereyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDgzMzkwNTQsIlgtU0lNVS1Vc2VySWQiOiIwOGFhMTYxYi1lYjI3LTQ2NjAtYjA1MC1lMDc5YTJiODBhODMiLCJ1c2VyX25hbWUiOiJrZWV0cyIsImp0aSI6IjJhNTQ4NjY2LTRjNzEtNGEzNi1hZmY0LTMwZTI1Mjc0ZjQxZSIsImNsaWVudF9pZCI6ImZyb250ZW5kIiwic2NvcGUiOlsibWVua29yIl19.rA-U2iXnjH0AdPaGuvSEJH3bTth6AT3oQrGsKIams30
}

注销成功则会返回200,注销端点主要是将token和SecurityContextHolder进行清空。

5. 总结

本文是《认证鉴权与API权限控制在微服务架构中的设计与实现》系列文章的总述,从遇到的问题着手,介绍了项目的背景。通过调研现有的技术,并结合当前项目的实际,确定了技术选型。最后对于系统的最终的实现进行展示。后面将从实现的细节,讲解本系统的实现。敬请期待后续文章。


参考

  1. 理解OAuth 2.0
  2. 微服务API级权限的技术架构
  3. 微服务架构下的安全认证与鉴权

我的官网

我的官网http://guan2ye.com
我的CSDN地址http://blog.csdn.net/chenjianandiyi
我的简书地址http://www.jianshu.com/u/9b5d1921ce34
我的githubhttps://github.com/javanan
我的码云地址https://gitee.com/jamen/
阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld

阿里云教程系列网站http://aliyun.guan2ye.com

我的开源项目spring boot 搭建的一个企业级快速开发脚手架

时间: 2024-09-02 23:26:34

认证鉴权与API权限控制在微服务架构中的设计与实现(一)的相关文章

微服务架构中API网关的角色

[编者的话]本文主要讲述了Mashape的首席技术执行官Palladino对API网关的详细介绍,以及API网关在微服务中所起的作用,同时介绍了Mashape的一款开源API网关Kong. 本文讲的是微服务架构中API网关的角色API网关提供商Mashape的首席技术执行官Marco Palladino预测,尽管它们在命名方面存在差异,但新出现的服务网格并不完全不同于API网关,两者之间的相似性会随着时间的推移而不断增长. Palladino指出,实际上这两种技术提供的功能很相似.API网关,比

HelloFresh迁移至新的API网关,实现微服务架构

HelloFresh最近以零停机的方式将应用迁移到了一个新的API网关,其技术总监ítalo Lelis de Vietro在一篇文章中分享了他们所面临的挑战以及迁移的过程. 在这次迁移之前,HelloFresh已有的API是单体架构的.为了迁移至微服务架构并让微服务的创建更加简单,同时还要与他们的基础设施进行集成,他们构建了一个新的API网关,这个网关会涵盖已有的和新的服务.他们的基础设施已经有了一些组件,包括服务发现.基于Ansible的自动化以及广泛存在的日志和监控,这些组件都会让微服务更

微服务系统中的认证策略

软件安全本身就是个很复杂的问题,由于微服务系统中的每个服务都要处理安全问题,所以在微服务场景下会更复杂.David Borsos在最近的伦敦微服务大会上作了相关内容的演讲,并评估了四种面向微服务系统的身份验证方案. 在传统的单体架构中,单个服务保存所有的用户数据,可以校验用户,并在认证成功后创建HTTP会话.在微服务架构中,用户是在和服务集合交互,每个服务都有可能需要知道请求的用户是谁.一种朴素的解决方案是在微服务系统中应用与单体系统中相同的模式,但是问题就在于如何让所有的服务访问用户的数据.解

微服务架构实战:Swagger规范RESTful API

REST的引入 本文讲的是微服务架构实战:Swagger规范RESTful API,随着微服务架构的广泛流行,REST风格受到越来越多的关注.我们先来看一下REST在WIKI的定义及五大关键点: 什么是统一接口? REST要求,必须通过统一的接口来对资源执行各种操作.对于每个资源只能执行一组有限的操作.以HTTP/1.1协议为例,此协议定义了一个操作资源的统一接口,主要包括以下内容: 7个HTTP方法:GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS HTTP头信息(

Red Hat: API层是微服务架构成功的关键

Microservices作为一项在云中部署应用和服务的新技术已成为当下最新的热门话题.但大部分围绕microservices的争论都集中在容器或其他技术是否能很好的实施微服务,而红帽说API应该是重点. 企业和服务提供商正在寻找更好的方法将应用程序部署在云环境中,microservices被认为是未来的方向.通过将应用和服务分解成更小的.松散耦合的组件,它们可以更加容易升级和扩展,理论上是这样. 最近一场关于"容器技术和虚拟机是否是实现微服务的最佳技术"的辩论在加州硅谷的OpenSt

Spring Boot 之 RESRful API 权限控制

一.为何用RESTful API 1.1 RESTful是什么? RESTful(Representational State Transfer)架构风格,是一个Web自身的架构风格,底层主要基于HTTP协议(ps:提出者就是HTTP协议的作者),是分布式应用架构的伟大实践理论.RESTful架构是无状态的,表现为请求-响应的形式,有别于基于Bower的SessionId不同.   1.2理解REST有五点: 1.资源  2.资源的表述  3.状态的转移  4.统一接口  5.超文本驱动 需要理

微服务、SOA和API对比与分析

本文讲的是微服务.SOA和API对比与分析[编者的话]对比微服务架构和面向服务的架构(SOA)是一个敏感的话题,常常引起激烈的争论.本文将介绍这些争论的起源,并分析如何以最佳方式解决它们.然后进一步查看这些概念如何与 API 管理概念结合使用,实现更敏捷.更分散化.更具弹性的企业架构. 1 简介 在对比微服务架构和面向服务的架构(SOA)时,几乎不可能在它们彼此的关系上达成一致意见.如果应用程序编程接口(API) 再加入混战,就会让理解它们的差异变得更加困难.一些人可能会说这些概念完全不同,它们

权限管理最佳实践:二,URL权限控制

问题描述 原文地址:http://www.javaeye.com/topic/754014摘要:今天说的URL权限控制,内容主要有:URL权限控制,当用户访问某URL时,进行角色权限验证.如果有相应权限,则允许其正常访问:否则,转到拒绝页面.我们依然通过一个Filter来实现,这样就无需在代码中增加权限判断,也无需套用任何框架.对于整个权限管理系统来说,本节内容也非常简单. 解决方案 解决方案二:每个用户访问一个连接都去判断会不会影响性能解决方案三:权限不应该按照URL来划分,而应该按照功能来划

Sql Server来龙去脉系列 必须知道的权限控制基础篇

原文:Sql Server来龙去脉系列 必须知道的权限控制基础篇     题外话:最近看到各种吐槽.NET怎么落寞..NET怎么不行了..NET工资低的帖子.我也吐槽一句:一个程序猿的自身价值不是由他选择了哪一门技术来决定,而是由他自身能创造出什么价值来决定.     在进入本篇内容之前,这里有几个问题:     1.一般程序猿都知道怎样创建.修改.登录账号,但知不知道登陆账号存储在哪个表或者视图?     2.数据库中其实存在登录账号和用户两个概念,你能解释清楚这两个概念吗?     3.对于