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

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

1. 背景

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

好比用户A访问User Service,A若是未登陆,则首先须要登陆,请求获取受权token。获取token以后,A将携带着token去请求访问某个文件,这样就须要对A的身份进行校验,而且A能够访问该文件。数据库

为了适应架构的变化、需求的变化,auth权限模块被单独出来做为一个基础的微服务系统,为其余业务service提供服务。浏览器

2. 系统架构的变动

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

(1)单体应用简化版架构图:服务器

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

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

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

3. 技术方案

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

3.1 认证与鉴权

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

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

  2. 基于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既能配合Spring使用也能够单独使用。

  2. 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系统的最终使用组件以下:

4.2 步骤

主要步骤为:

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

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

4.3 endpoint

提供的endpoint:

4.4 maven依赖

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

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

 1 @Override
 2    public void configure(HttpSecurity http) throws Exception {
 3        http.csrf().disable()
 4                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
 5                .and()
 6                .requestMatchers().antMatchers("/**")
 7                .and().authorizeRequests()
 8                .antMatchers("/**").permitAll()
 9                .anyRequest().authenticated()
10                .and().logout()
11                .logoutUrl("/logout")
12                .clearAuthentication(true)
13                .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())
14                .addLogoutHandler(customLogoutHandler());

4.7 执行endpoint

1. 首先执行获取受权的endpoint。

上述构造了一个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

 1 method: post 
 2 url: http://localhost:12000/oauth/check_token
 3 header:
 4 {
 5     Authorization: Basic ZnJvbnRlbmQ6ZnJvbnRlbmQ=,
 6     Content-Type: application/x-www-form-urlencoded
 7 }
 8 body:
 9 {
10     token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsImV4cCI6MTUwODQ0Nzc1NiwidXNlcl9uYW1lIjoia2VldHMiLCJqdGkiOiJiYWQ3MmIxOS1kOWYzLTQ5MDItYWZmYS0wNDMwZTdkYjc5ZWQiLCJjbGllbnRfaWQiOiJmcm9udGVuZCIsInNjb3BlIjpbImFsbCJdfQ.5ZNVN8TLavgpWy8KZQKArcbj7ItJLLaY1zBRaAgMjdo
11 }

上面即为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

因为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: Basic ZnJvbnRlbmQ6ZnJvbnRlbmQ=
}

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

5. 总结

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


参考

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

 

原文连接:http://blueskykong.com/2017/10/19/security1/

相关文章
相关标签/搜索