对于一个服务系统,安全是必须需要考虑的方面。应用安全是一个不断追求更强的目标,全面性、全系统的方法很重要,因为我们永远不知道入侵者是如果对系统进行攻击。在系统安全的实现上,一般倡导使用安全层 layers of security
,即多层次安全保证,通过连续层提供额外的安全性。每一层的安全性越强,应用程序的健壮性和安全性就越高。Java EE应用程序位于安全层的高层次,需要为它添加特定的问题域安全配置。
对应用的层次的安全主要关注两个方面,认证(Authentication)和授权(Authoriztion),即你是谁,以及你能做什么。在单体应用中,开发者可以通过简单的拦截器以及session机制对用户的访问进行控制和记录。在分布式系统中,由于业务逻辑封装在各个微服务中,每个微服务都需要对用户的行为进行认证和许可,于是就产生了两种可能的方式:第一种是通过一个中心化的权限管理系统,对用户的身份和权限进行统一的管理,可以做到一次授权,多次多点使用,但是这个独立的安全微服务需要聚合各个微服务中的权限控制逻辑,多一个基于不同业务逻辑实现的微服务可能需要在安全微服务中添加新的实现;第二种是将安全部分分散到各个微服务中,由各个微服务根据自身的业务对用户的访问进行管理和控制,这会导致安全管理过于分散,甚至每个微服务都有自己的一套实现方式,不利于统一管理。这两种方式都有利有弊,如何选择需要根据项目的具体需求进行宏观判断,甚至在一定情况下可以结合使用。
spring-cloud-security提供了一组基本的组件用来构建安全应用程序和服务。它封装了spring-securtiy和spring
-security-oauth2以及spring-securtiy-jwt的相关实现,同时提供自带的安全特性,致力于为spring-cloud中微服务提供快速创建常用的安全模式。
虽然spring-cloud-security文档中对其使用提供的帮助不多,但是通过对spring-security与spring
-security-oauth2的相关使用以及探索,将有助开发者通过spring-cloud-security构建高健壮的安全应用。
在开始对相关的应用以及源码的介绍之前,需要对部分前置知识进行一定的补充,如OAuth2以及JWT。
OAuth2相关理论的介绍主要来自于OAuth2官方文档,相关地址为 https://tools.ietf.org/html/rfc6749
。
OAuth协议的目的是为了为用户资源的授权提供一个安全的、开放而简易的标准。官网中是这样进行介绍的:
An open protocol to allow secure API authorization in a simple and standard method from web, mobile and desktop applications.
OAuth1由于不被OAuth2兼容,且签名逻辑过于复杂和授权流程的过于单一,在此不过多谈论,我们重点关注OAuth2,现在Web应用的中主流授权版本也是OAuth2
OAuth2是当前授权的行业标准,其重点在于为Web应用程序、桌面应用程序、移动设备以及室内设备的授权流程提供简单的客户端开发方式。它为第三方应用提供对HTTP服务的有限访问,既可以是资源拥有者通过授权允许第三方应用获取HTTP服务,也可以是第三方以自己的名义获取访问权限。
OAuth2 中主要分为了4种角色:
在很多时候,资源服务器和授权服务器是合二为一的,在授权交互的时候是授权服务器,在请求资源交互是资源服务器。但是授权服务器是单独的实体,它可以发出被多个资源服务器接受的访问令牌。
首先看一张来自官方提供的流程图:
+--------+ +---------------+ | |--(1)- Authorization Request ->| Resource | | | | Owner | | |<-(2)-- Authorization Grant ---| | | | +---------------+ | | | | +---------------+ | |--(3)-- Authorization Grant -->| Authorization | | Client | | Server | | |<-(4)----- Access Token -------| | | | +---------------+ | | | | +---------------+ | |--(5)----- Access Token ------>| Resource | | | | Server | | |<-(6)--- Protected Resource ---| | +--------+ +---------------+
这是一张关于OAuth2角色的抽象交互流程图,主要包含以下的6个步骤:
为了获取访问令牌,客户端必须获取到资源所有者的授权许可。OAuth2默认定了四种授权类型,当然也提供了用于定义额外的授权类型的扩展机制。默认的四种授权类型为:
下面对常用的授权码类型和密码类型进行详细的介绍。
授权码类型
授权码类型(authorization code)通过重定向的方式让资源所有者直接与授权服务器进行交互来进行授权,避免了资源所有者信息泄漏给客户端,是功能最完整、流程最严密的授权类型,但是需要客户端必须能与资源所有者的代理(通常是Web浏览器)进行交互,和可从授权服务器中接受请求(重定向给予授权码),授权流程如下:
+----------+ | Resource | | Owner | | | +----------+ ^ | (2) +----|-----+ Client Identifier +---------------+ | -+----(1)-- & Redirection URI ---->| | | User- | | Authorization | | Agent -+----(2)-- User authenticates --->| Server | | | | | | -+----(3)-- Authorization Code ---<| | +-|----|---+ +---------------+ | | ^ v (1) (3) | | | | | | ^ v | | +---------+ | | | |>---(4)-- Authorization Code ---------' | | Client | & Redirection URI | | | | | |<---(5)----- Access Token -------------------' +---------+ (w/ Optional Refresh Token)
密码类型
密码类型(resource owner password credentials)需要资源所有者将密码凭证交予客户端,客户端通过自己持有的信息直接向授权服务器获取授权。在这种情况下,需要资源所有者对客户端高度可信任,同时客户端不允许保存密码凭证。这种授权类型适用于能够获取资源所有者的凭证(credentials)(如用户名和密码)的客户端。授权流程如下:
+----------+ | Resource | | Owner | | | +----------+ v | Resource Owner (1) Password Credentials | v +---------+ +---------------+ | |>--(2)---- Resource Owner ------->| | | | Password Credentials | Authorization | | Client | | Server | | |<--(3)---- Access Token ---------<| | | | (w/ Optional Refresh Token) | | +---------+ +---------------+
客户端从授权服务器中获取的访问令牌(access token)一般是具备失效性的,在访问令牌过期的情况下,持有有效用户凭证的客户端可以再次向授权服务器请求访问令牌,但是如果不持有用户凭证的客户端可以通过和上次访问令牌一同返回的刷新令牌(refresh token)向授权服务器获取新的访问令牌。
JWT,JSON Web Token,作为一个开放的标准,通过紧凑(compact,快速传输,体积小)或者自包含(self-contained,payload中将包含用户所需的所有的信息,避免了对数据库的多次查询)的方式,定义了用于在各方之间发送的安全JSON对象。
为什么要介绍JWT,因为JWT可以很好的充当在上一节介绍的访问令牌(access token)和刷新令牌(refresh token)的载体,这是Web双方之间进行安全传输信息的良好方式。当只有授权服务器持有签发和验证JWT的secret,那么就只有授权服务器能验证JWT的有效性以及发送带有签名的JWT,这就唯一保证了以JWT为载体的token的有效性和安全性。
JWT格式一般如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiY2FuZyB3dSIsImV4cCI6MTUxODA1MTE1NywidXNlcklkIjoiMTIzNDU2In0.IV4XZ0y0nMpmMX9orv0gqsEMOxXXNQOE680CKkkPQcs
它由三部分组成,每部分通过 .
分隔开,分别是:
接着我们对每一部分进行详细的介绍。
Header
头部通常由两部分组成:
一个简单的头部例子如下:
{ "alg": "HS256" "typ": "JWT" }
然后这部分JSON会被Base64Url编码用于构成JWT的第一部分:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Playload
有效负载是JWT的第二部分,是用来携带有效信息的载体,主要是关于用户实体和附加元数据的声明,由以下三部分组成:
一般不建议在payload中添加任何的敏感信息,因为Base64是对称解密的,这意味着payload中的信息的是可见的。
以上内容摘自《Spring Cloud微服务架构进阶》一书,经出版方授权发布。