我们希望自己的微服务能够在用户登录之后才可以访问,而单独给每个微服务单独做用户权限模块就显得很弱了,从复用角度来说是需要重构的,从功能角度来说,也是欠缺的。尤其是前后端完全分离之后,我们的用户信息不一定存在于 Session 会话中。
应用场景
常见的应用场景如下图,用户通过浏览器进行登录,一旦确定用户名和密码正确,那么在服务器端使用秘钥创建 JWT,并且返回给浏览器;接下来我们的请求需要在头部增加 jwt 信息,服务器端进行解密获取用户信息,然后进行其他业务逻辑处理,再返回客户端
什么是OAuth2?
OAuth2是一个关于授权的开放标准,核心思路是通过各类认证手段(具体什么手段OAuth2不关心)认证用户身份,并颁发token(令牌),使得第三方应用可以使用该令牌在限定时间、限定范围访问指定资源。主要涉及的RFC规范有RFC6749
(整体授权框架),RFC6750
(令牌使用),RFC6819
(威胁模型)这几个,一般我们需要了解的就是RFC6749
。获取令牌的方式主要有四种,分别是授权码模式
,简单模式
,密码模式
和客户端模式
,如何获取token不在本篇文章的讨论范围,我们这里假定客户端已经通过某种方式获取到了access_token,想了解具体的oauth2授权步骤可以移步阮一峰老师的理解OAuth 2.0,里面有非常详细的说明。
这里要先明确几个OAuth2中的几个重要概念:
-
resource owner
: 拥有被访问资源的用户 -
user-agent
: 一般来说就是浏览器 -
client
: 第三方应用 -
Authorization server
: 认证服务器,用来进行用户认证并颁发token -
Resource server
:资源服务器,拥有被访问资源的服务器,需要通过token来确定是否有权限访问
明确概念后,就可以看OAuth2的协议握手流程,摘自RFC6749
什么是Spring Security?
Spring Security是一套安全框架,可以基于RBAC(基于角色的权限控制)对用户的访问权限进行控制,核心思想是通过一系列的filter chain来进行拦截过滤,以下是ss中默认的内置过滤器列表,当然你也可以通过custom-filter
来自定义扩展filter chain列表
Alias | Filter Class | Namespace Element or Attribute |
CHANNEL_FILTER | ChannelProcessingFilter | http/intercept-url@requires-channel |
SECURITY_CONTEXT_FILTER | SecurityContextPersistenceFilter | http |
CONCURRENT_SESSION_FILTER | ConcurrentSessionFilter | session-management/concurrency-control |
HEADERS_FILTER | HeaderWriterFilter | http/headers |
CSRF_FILTER | CsrfFilter | http/csrf |
LOGOUT_FILTER | LogoutFilter | http/logout |
X509_FILTER | X509AuthenticationFilter | http/x509 |
PRE_AUTH_FILTER | AbstractPreAuthenticatedProcessingFilter | N/A |
CAS_FILTER | CasAuthenticationFilter | N/A |
FORM_LOGIN_FILTER | UsernamePasswordAuthenticationFilter | http/form-login |
BASIC_AUTH_FILTER | BasicAuthenticationFilter | http/http-basic |
SERVLET_API_SUPPORT_FILTER | SecurityContextHolderAwareRequestFilter | http/@servlet-api-provision |
JAAS_API_SUPPORT_FILTER | JaasApiIntegrationFilter | http/@jaas-api-provision |
REMEMBER_ME_FILTER | RememberMeAuthenticationFilter | http/remember-me |
ANONYMOUS_FILTER | AnonymousAuthenticationFilter | http/anonymous |
SESSION_MANAGEMENT_FILTER | SessionManagementFilter | session-management |
EXCEPTION_TRANSLATION_FILTER | ExceptionTranslationFilter | http |
FILTER_SECURITY_INTERCEPTOR | FilterSecurityInterceptor | http |
SWITCH_USER_FILTER | SwitchUserFilter | N/A |
这里面最核心的就是FILTER_SECURITY_INTERCEPTOR
,通过FilterInvocationSecurityMetadataSource
来进行资源权限的匹配,AccessDecisionManager
来执行访问策略。
认证与授权(Authentication and Authorization)
一般意义来说的应用访问安全性,都是围绕认证(Authentication)和授权(Authorization)这两个核心概念来展开的。即首先需要确定用户身份,在确定这个用户是否有访问指定资源的权限。认证这块的解决方案很多,主流的有CAS
、SAML2
、OAUTH2
等(不巧这几个都用过-_-),我们常说的单点登录方案(SSO)说的就是这块,授权的话主流的就是spring security和shiro。shiro我没用过,据说是比较轻量级,相比较而言spring security确实架构比较复杂。
OAuth2与SSO
首先要明确一点,OAuth2并不是一个SSO框架,但可以实现SSO功能。
所以总结一下就是:通过将用户信息这个资源设置为被保护资源,可以使用OAuth2技术实现单点登陆(SSO),而Spring Security OAuth2就是这种OAuth2 SSO方案的一个实现。
JWT介绍
终于来到了著名的JWT部分了,JWT全称为Json Web Token,最近随着微服务架构的流行而越来越火,号称新一代的认证技术。今天我们就来看一下,jwt的本质到底是什么。
我们先来看一下OAuth2的token技术有没有什么痛点,相信从之前的介绍中你也发现了,token技术最大的问题是不携带用户信息,且资源服务器无法进行本地验证,每次对于资源的访问,资源服务器都需要向认证服务器发起请求,一是验证token的有效性,二是获取token对应的用户信息。如果有大量的此类请求,无疑处理效率是很低的,且认证服务器会变成一个中心节点,对于SLA和处理性能等均有很高的要求,这在分布式架构下是很要命的。
JWT就是在这样的背景下诞生的,从本质上来说,jwt就是一种特殊格式的token。普通的oauth2颁发的就是一串随机hash字符串,本身无意义,而jwt格式的token是有特定含义的,分为三部分:
- 头部
Header
- 载荷
Payload
- 签名
Signature
这三部分均用base64进行编码,当中用.
进行分隔,一个典型的jwt格式的token类似xxxxx.yyyyy.zzzzz
。关于jwt格式的更多具体说明,不是本文讨论的重点,大家可以直接去官网查看官方文档,这里不过多赘述。
jwt相对于传统的token来说,解决以下两个痛点:
- 通过验证签名,token的验证可以直接在本地完成,不需要连接认证服务器
- 在payload中可以定义用户相关信息,这样就轻松实现了token和用户信息的绑定
在上面的那个资源服务器和认证服务器分离的例子中,如果认证服务器颁发的是jwt格式的token,那么资源服务器就可以直接自己验证token的有效性并绑定用户,这无疑大大提升了处理效率且减少了单点隐患。
JWT适用场景与不适用场景
JWT的使用上现在也有一种误区,认为传统的认证方式都应该被jwt取代。事实上,jwt也不能解决一切问题,它也有适用场景和不适用场景。
适用场景:
- 一次性的身份认证
- api的鉴权
这些场景能充分发挥jwt无状态以及分布式验证的优势
不适用的场景:
- 传统的基于session的用户会话保持
不要试图用jwt去代替session。这种模式下其实传统的session+cookie机制工作的更好,jwt因为其无状态和分布式,事实上只要在有效期内,是无法作废的,用户的签退更多是一个客户端的签退,服务端token仍然有效,你只要使用这个token,仍然可以登陆系统。另外一个问题是续签问题,使用token,无疑令续签变得十分麻烦,当然你也可以通过redis去记录token状态,并在用户访问后更新这个状态,但这就是硬生生把jwt的无状态搞成有状态了,而这些在传统的session+cookie机制中都是不需要去考虑的。这种场景下,考虑高可用,我更加推荐采用分布式的session机制,现在已经有很多的成熟框架可供选择了(比如spring session)。
基于Spring Security OAuth2和JWT构建保护微服务系统
本工程代码是基于简书一文基于 Spring Security OAuth2和 JWT 构建保护微服务系统所编写的。
目录说明
- ljl-architecture-spring-cloud 基于Dalston.SR5版本Spring Cloud + 1.5.13.RELEASE版本Spring Boot去构建。
- ljl-architecture-spring-cloud2 基于Finchley.SR2版本Spring Cloud + 2.0.8.RELEASE版本Spring Boot构建。
github地址:
https://github.com/leslie-1994/spring-security-oauth2-jwt-demo