OAuth2实现单点登录SSO,第1张

1 前言

技术这东西吧,看别人写的好像很简单似的,到自己去写的时候就各种问题,“一看就会,一做就错”。网上关于实现SSO的文章一大堆,但是当你真的照着写的时候就会发现根本不是那么回事儿,简直让人抓狂,尤其是对于我这样的菜鸟。几经曲折,终于搞定了,决定记录下来,以便后续查看。先来看一下效果

2 准备

21 单点登录

最常见的例子是,我们打开淘宝APP,首页就会有天猫、聚划算等服务的链接,当你点击以后就直接跳过去了,并没有让你再登录一次

下面这个图是我再网上找的,我觉得画得比较明白:

可惜有点儿不清晰,于是我又画了个简版的:

重要的是理解:

22 OAuth2

推荐以下几篇博客

《 OAuth 20 》

《 Spring Security对OAuth2的支持 》

3 利用OAuth2实现单点登录

接下来,只讲跟本例相关的一些配置,不讲原理,不讲为什么

众所周知,在OAuth2在有授权服务器资源服务器、客户端这样几个角色,当我们用它来实现SSO的时候是不需要资源服务器这个角色的,有授权服务器和客户端就够了。

授权服务器当然是用来做认证的,客户端就是各个应用系统,我们只需要登录成功后拿到用户信息以及用户所拥有的权限即可

之前我一直认为把那些需要权限控制的资源放到资源服务器里保护起来就可以实现权限控制,其实是我想错了,权限控制还得通过Spring Security或者自定义拦截器来做

31 Spring Security 、OAuth2、JWT、SSO

在本例中,一定要分清楚这几个的作用

首先,SSO是一种思想,或者说是一种解决方案,是抽象的,我们要做的就是按照它的这种思想去实现它

其次,OAuth2是用来允许用户授权第三方应用访问他在另一个服务器上的资源的一种协议,它不是用来做单点登录的,但我们可以利用它来实现单点登录。在本例实现SSO的过程中,受保护的资源就是用户的信息(包括,用户的基本信息,以及用户所具有的权限),而我们想要访问这这一资源就需要用户登录并授权,OAuth2服务端负责令牌的发放等操作,这令牌的生成我们采用JWT,也就是说JWT是用来承载用户的Access_Token的

最后,Spring Security是用于安全访问的,这里我们我们用来做访问权限控制

4 认证服务器配置

41 Maven依赖

这里面最重要的依赖是:spring-security-oauth2-autoconfigure

42 applicationyml

43 AuthorizationServerConfig(重要)

说明:

44 WebSecurityConfig(重要)

45 自定义登录页面(一般来讲都是要自定义的)

自定义登录页面的时候,只需要准备一个登录页面,然后写个Controller令其可以访问到即可,登录页面表单提交的时候method一定要是post,最重要的时候action要跟访问登录页面的url一样

千万记住了,访问登录页面的时候是GET请求,表单提交的时候是POST请求,其它的就不用管了

46 定义客户端

47 加载用户

登录账户

加载登录账户

48 验证

当我们看到这个界面的时候,表示认证服务器配置完成  

5 两个客户端

51 Maven依赖

52 applicationyml

这里context-path不要设成/,不然重定向获取code的时候回被拦截

53 WebSecurityConfig

说明:

54 MemberController

55 Order项目跟它是一样的

56 关于退出

退出就是清空用于与SSO客户端建立的所有的会话,简单的来说就是使所有端点的Session失效,如果想做得更好的话可以令Token失效,但是由于我们用的JWT,故而撤销Token就不是那么容易,关于这一点,在官网上也有提到:

本例中采用的方式是在退出的时候先退出业务服务器,成功以后再回调认证服务器,但是这样有一个问题,就是需要主动依次调用各个业务服务器的logout

6 工程结构

附上源码: https://githubcom/chengjiansheng/cjs-oauth2-sso-demogit

7 演示

8 参考

https://wwwcnblogscom/cjsblog/p/9174797html

https://wwwcnblogscom/cjsblog/p/9184173html

https://wwwcnblogscom/cjsblog/p/9230990html

https://wwwcnblogscom/cjsblog/p/9277677html

https://blogcsdnnet/fooelliot/article/details/83617941

http://blogleapoaheadcom/2015/09/07/user-authentication-with-jwt/

https://wwwcnblogscom/lihaoyang/p/8581077html

https://wwwcnblogscom/charlypage/p/9383420html

http://www360doccom/content/18/0306/17/16915_734789216shtml

https://blogcsdnnet/chenjianandiyi/article/details/78604376

https://wwwbaeldungcom/spring-security-oauth-jwt

https://wwwbaeldungcom/spring-security-oauth-revoke-tokens

https://wwwreinforcecn/t/630html

9 文档

https://projectsspringio/spring-security-oauth/docs/oauth2html

https://docsspringio/spring-security-oauth2-boot/docs/213RELEASE/reference/htmlsingle/

https://docsspringio/spring-security-oauth2-boot/docs/213RELEASE/

https://docsspringio/spring-security-oauth2-boot/docs/

https://docsspringio/spring-boot/docs/213RELEASE/

https://docsspringio/spring-boot/docs/

https://docsspringio/spring-framework/docs/

https://docsspringio/spring-framework/docs/514RELEASE/

https://springio/guides/tutorials/spring-boot-oauth2/

https://docsspringio/spring-security/site/docs/current/reference/htmlsingle/#core-services-password-encoding

https://springio/projects/spring-cloud-security

https://cloudspringio/spring-cloud-security/single/spring-cloud-securityhtml

https://docsspringio/spring-session/docs/current/reference/html5/guides/java-securityhtml

https://docsspringio/spring-session/docs/current/reference/html5/guides/boot-redishtml#boot-spring-configuration

原文链接:https://wwwcnblogscom/cjsblog/p/10548022html

在介绍Spring Cloud Security之前,我们先要介绍一下Spring Security。

Spring Security是一套提供了完整的声明式安全访问控制的解决方案。

Spring Security是基于Spring AOP和Servlet过滤器的,它只能服务基于Spring的应用程序。

除常规认证和授权外,它还提供 ACL,LDAP,JAAS,CAS 等高级安全特性以满足复杂环境中的安全需求。

(注:在Spring Security中,Authority和Permission是两个完全独立的概念,两者没有必然的联系。它们之前需要通过配置进行关联。)

安全方案的实现通常分为 认证(Authentication) 授权(Authorization) 两部分。

使用者可以使一个 用户 设备 ,和可以在应用程序中执行某种操作的 其它系统

认证一般通过用户名和密码的正确性校验来完成通过或拒绝。

Spring Security支持的主流认证如下:

Spring Security进行认证的步骤:

++++++++++++++++++++++++++++++++++++++++++++++++++++++

++ 1 用户使用用户名和密码登陆

++

++ 2 过滤器(UsernamePasswordAuthenticationFilter)获取到用户名

++    和密码,将它们封装成Authentication

++

++ 3 AuthenticationManager认证Token(由Authentication实现类传递)

++

++ 4 AuthenticationManager认证成功,返回一个封装了用户权限信息

++    的Authentication对象,包含用户的上下文信息(角色列表)

++

++ 5 将Authentication对象赋值给当前的SecurityContext,建立这个用户

++ 的安全上下文( SecurityContextHoldergetContextsetAuthentication() )

++

++ 6 当用户进行一些受到访问控制机制保护的操作,访问控制机制会依据

++    当前安全的上下文信息来检查这个操作所需的权限

++++++++++++++++++++++++++++++++++++++++++++++++++++++

Spring Security提供了一系列基本组件,如spring-security-acl, spring-security-cas, spring-security-oauth2等。

具体这里就不详述了,读者可以查看官网的介绍。

 

Spring Cloud Security是用于构建微服务系统架构下的安全的应用程序和服务,它可以轻松实现基于微服务架构的统一的安全认证与授权。

Spring Cloud Security相对于Spring Security整合了Zuul,Feign,而且更加完美地整合了OAuth20。

 

OAuth 20是用于授权的行业标准协议。

原理:

OAuth2是用户资源和第三方应用之间的一个中间层。

它把资源和第三方应用隔开,使得第三方应用无法直接访问资源,第三方应用要访问资源需要通过提供 凭证(Token) 来获得OAuth 20授权。

OAuth2的典型例子:

==============================================

== 微信公众号授权提醒

== 页面弹出一个提示框需要获取我们的个人信息

== 单击确定

== 第三方应用会获取我们在微信公众平台中的个人信息

==============================================

OAuth的关键角色:

在用户授权第三方获取私有资源后,第三方通过获取到的token等信息通过授权服务器认证,然后去资源服务器获取资源。

 

密码模式中,资源拥有者负责向客户端提供用户名和密码;

客户端根据用户名和密码箱认证服务器申请令牌,正确后认证服务器发放令牌。

客户端请求参数:

缺点:

用户对客户端高度可信,必须把自己的密码发给客户端,存在被黑客窃取的隐患(不推荐)。

在授权码模式中,客户端是通过其后台服务器与认证服务器进行交互的。

授权码模式的运行流程:

从以上图中我们可以看到客户端服务器和认证服务器需经过2次握手,客户端服务器才能拿到最终的访问和更新令牌。

客户端申请认证的参数:

 

服务器返回的参数:

 

客户端收到授权码以后,附上重定向URI,向认证服务器申请访问令牌。

客户端申请令牌的参数:

 

服务器返回的参数:

 

如果访问令牌已经过期,可以使用更新令牌申请一个新的访问令牌。

通过更新令牌申请访问令牌的参数:

这两种模式不常用,因此在这里就不多叙述了。

随着我们的微服务越来越多,如果每个微服务都要自己去实现一套鉴权操作,那么这么操作比较冗余,因此我们可以把鉴权操作统一放到网关去做,如果微服务自己有额外的鉴权处理,可以在自己的微服务中处理。

1、在网关层完成url层面的鉴权操作。

2、将解析后的jwt token当做请求头传递到下游服务中。

3、整合Spring Security Oauth2 Resource Server

1、搭建一个可用的认证服务器, 可以参考之前的文章

2、知道Spring Security Oauth2 Resource Server资源服务器如何使用, 可以参考之前的文章

自定义授权管理器,判断用户是否有权限访问

此处我们简单判断

1、放行所有的 OPTION 请求。

2、判断某个请求(url)用户是否有权限访问。

3、所有不存在的请求(url)直接无权限访问。

1、 客户端 gateway 在认证服务器拥有的权限为 useruserInfo

3、在网关层面,findAllUsers 需要的权限为 useruserInfo ,正好 gateway 这个客户端有这个权限,所以可以访问。

演示GIF

https://giteecom/huan1993/spring-cloud-alibaba-parent/tree/master/gateway-oauth2

OAuth2是一个关于授权的开放标准,核心思路是通过各类认证手段(具体什么手段OAuth2不关心)认证用户身份,并颁发token(令牌),使得第三方应用可以使用该令牌在 限定时间 限定范围 访问指定资源。主要涉及的RFC规范有 RFC6749 (整体授权框架), RFC6750 (令牌使用), RFC6819 (威胁模型)这几个,一般我们需要了解的就是 RFC6749 。获取令牌的方式主要有四种,分别是 授权码模式 , 简单模式 , 密码模式 和 客户端模式 ,如何获取token不在本篇文章的讨论范围,我们这里假定客户端已经通过某种方式获取到了access_token,想了解具体的oauth2授权步骤可以移步阮一峰老师的 理解OAuth 20 ,里面有非常详细的说明。

这里要先明确几个OAuth2中的几个重要概念:

明确概念后,就可以看OAuth2的协议握手流程,摘自RFC6749

Spring Security是一套安全框架,可以基于RBAC(基于角色的权限控制)对用户的访问权限进行控制,核心思想是通过一系列的filter chain来进行拦截过滤,以下是ss中默认的内置过滤器列表,当然你也可以通过 custom-filter 来自定义扩展filter chain列表

这里面最核心的就是 FILTER_SECURITY_INTERCEPTOR ,通过 FilterInvocationSecurityMetadataSource 来进行资源权限的匹配, AccessDecisionManager 来执行访问策略。

一般意义来说的应用访问安全性,都是围绕认证(Authentication)和授权(Authorization)这两个核心概念来展开的。即首先需要确定用户身份,在确定这个用户是否有访问指定资源的权限。认证这块的解决方案很多,主流的有 CAS 、 SAML2 、 OAUTH2 等(不巧这几个都用过-_-),我们常说的单点登录方案(SSO)说的就是这块,授权的话主流的就是spring security和shiro。shiro我没用过,据说是比较轻量级,相比较而言spring security确实架构比较复杂。

将OAuth2和Spring Security集成,就可以得到一套完整的安全解决方案。

为了便于理解,现在假设有一个名叫“脸盆网”的社交网站,用户在首次登陆时会要求导入用户在facebook的好友列表,以便于快速建立社交关系。具体的授权流程如下:

不难看出,这个假设的场景中,脸盆网就是第三方应用(client),而facebook既充当了认证服务器,又充当了资源服务器。这个流程里面有几个比较重要的关键点,我需要重点说一下,而这也是其他的涉及spring security与OAuth2整合的文章中很少提及的,很容易云里雾里的地方。

细心的同学应该发现了,其实在标准的OAuth2授权过程中,5、6、8这几步都不是必须的,从上面贴的 RFC6749 规范来看,只要有1、2、3、4、7这几步,就完成了被保护资源访问的整个过程。事实上, RFC6749 协议规范本身也并不关心用户身份的部分,它只关心token如何颁发,如何续签,如何用token访问被保护资源(facebook只要保证返回给脸盆网的就是当前用户的好友,至于当前用户是谁脸盆网不需要关心)。那为什么spring security还要做5、6这两步呢?这是因为spring security是一套完整的安全框架,它必须关心用户身份!在实际的使用场景中,OAuth2一般不仅仅用来进行被保护资源的访问,还会被用来做单点登陆(SSO)。在SSO的场景中,用户身份无疑就是核心,而token本身是不携带用户信息的,这样client就没法知道认证服务器发的token到底对应的是哪个用户。设想一下这个场景,脸盆网不想自建用户体系了,想直接用facebook的用户体系,facebook的用户和脸盆网的用户一一对应(其实在很多中小网站现在都是这种模式,可以选择使用微信、QQ、微博等网站的用户直接登陆),这种情况下,脸盆网在通过OAuth2的认证后,就希望拿到用户信息了。所以现在一般主流的OAuth2认证实现,都会预留一个用户信息获取接口,就是上面提到的 https://apifacebookcom/user (虽然这不是OAuth2授权流程中必须的),这样client在拿到token后,就可以携带token通过这个接口获取用户信息,完成SSO的整个过程。另外从用户体验的角度来说,如果获取不到用户信息,则意味者每次要从脸盆网访问facebook的资源,都需要重定向一次进行认证,用户体验也不好。

首先要明确一点, OAuth2并不是一个SSO框架,但可以实现SSO功能 。以下是一个使用github作为OAuth2认证服务器的配置文件

可以看到 accessTokenUri 和 userAuthorizationUri 都是为了完成OAuth2的授权流程所必须的配置,而 userInfoUri 则是spring security框架为了完成SSO所必须要的。所以总结一下就是: 通过将用户信息这个资源设置为被保护资源,可以使用OAuth2技术实现单点登陆(SSO),而Spring Security OAuth2就是这种OAuth2 SSO方案的一个实现。

Spring Security在调用user接口成功后,会构造一个 OAuth2Authentication 对象,这个对象是我们通常使用的 UsernamePasswordAuthenticationToken 对象的一个超集,里面封装了一个标准的 UsernamePasswordAuthenticationToken ,同时在 detail 中还携带了OAuth2认证中需要用到的一些关键信息(比如 tokenValue , tokenType 等),这时候就完成了SSO的登陆认证过程。后续用户如果再想访问被保护资源,spring security只需要从principal中取出这个用户的token,再去访问资源服务器就行了,而不需要每次进行用户授权。这里要注意的一点是 此时浏览器与client之间仍然是通过传统的cookie-session机制来保持会话,而非通过token。实际上在SSO的过程中,使用到token访问的只有client与resource server之间获取user信息那一次,token的信息是保存在client的session中的,而不是在用户本地 。这也是之前我没搞清楚的地方,以为浏览器和client之间也是使用token,绕了不少弯路,对于Spring Security来说, 不管是用cas、saml2还是Oauth2来实现SSO,最后和用户建立会话保持的方式都是一样的

根据前面所说,大家不难看出,OAuth2的SSO方案和CAS、SAML2这样的纯SSO框架是有本质区别的。在CAS和SAML2中,没有资源服务器的概念,只有认证客户端(需要验证客户信息的应用)和认证服务器(提供认证服务的应用)的概念。在CAS中这叫做 cas-client 和 cas-server ,SAML2中这叫做 Service Providers 和 Identity Provider ,可以看出CAS、SAML2规范天生就是为SSO设计的,在报文结构上都考虑到了用户信息的问题(SAML2规范甚至还带了权限信息),而OAuth2本身不是专门为SSO设计的,主要是为了解决资源第三方授权访问的问题,所以在用户信息方面,还需要额外提供一个接口。

脸盆网的这个例子中,我们看到资源服务器和认证服务器是在一起的(都是facebook),在互联网场景下一般你很难找到一个独立的、权威的、第三方的认证中心(你很难想像腾讯的QQ空间通过支付宝的认证中心去授权,也很难想像使用谷歌服务要通过亚马逊去授权)。但是如果是在公司内部,这种场景其实是很多的,尤其在微服务架构下,有大量服务会对外提供资源访问,他们都需要做权限控制。那么最合理的当然就是建立一个统一的认证中心,而不是每个服务都做一个认证中心。我们前面也介绍了,token本身是不携带用户信息的,在分离后resouce server在收到请求后,如何检验token的真实性?又如何从token中获取对应的用户信息?这部分的介绍网上其实非常少,幸好我们可以直接从官方文档获取相关的蛛丝马迹,官方文档对于resouce server的配置是这样描述的:

寥寥数语,但已经足够我们分析了。从这个配置可以看出,client在访问resource server的被保护资源时,如果没有携带token,则资源服务器直接返回一个401未认证的错误

如果携带了token,则资源服务器会使用这个token向认证服务器发起一个用户查询的请求,若token错误或已经失效,则会返回

若token验证成功,则认证服务器向资源服务器返回对应的用户信息,此时resource server的spring security安全框架就可以按照标准的授权流程进行访问权限控制了。

从这个流程中我们可以看出,通过OAuth2进行SSO认证,有一个好处是做到了 认证与授权的解耦 。从日常的使用场景来说,认证比较容易做到统一和抽象,毕竟你就是你,走到哪里都是你,但是你在不同系统里面的角色,却可能千差万别(家里你是父亲,单位里你是员工,父母那里你是子女)。同时角色的设计,又是和资源服务器的设计强相关的。从前面的配置中不难发现,如果希望获得为不同资源服务器设计的角色,你只需要替换 https://apifacebookcom/user 这个配置就行了,这为我们的权限控制带来了更大的灵活性,而这是传统的比如SAML2这样的SSO框架做不到的。

终于来到了著名的JWT部分了,JWT全称为Json Web Token,最近随着微服务架构的流行而越来越火,号称新一代的认证技术。今天我们就来看一下,jwt的本质到底是什么。

我们先来看一下OAuth2的token技术有没有什么痛点,相信从之前的介绍中你也发现了,token技术最大的问题是 不携带用户信息 ,且资源服务器无法进行本地验证,每次对于资源的访问,资源服务器都需要向认证服务器发起请求,一是验证token的有效性,二是获取token对应的用户信息。如果有大量的此类请求,无疑处理效率是很低的,且认证服务器会变成一个中心节点,对于SLA和处理性能等均有很高的要求,这在分布式架构下是很要命的。

JWT就是在这样的背景下诞生的,从本质上来说,jwt就是一种 特殊格式 的token。普通的oauth2颁发的就是一串随机hash字符串,本身无意义,而jwt格式的token是有特定含义的,分为三部分:

这三部分均用base64进行编码,当中用 进行分隔,一个典型的jwt格式的token类似 xxxxxyyyyyzzzzz 。关于jwt格式的更多具体说明,不是本文讨论的重点,大家可以直接去官网查看 官方文档 ,这里不过多赘述。

相信看到签名大家都很熟悉了,没错,jwt其实并不是什么高深莫测的技术,相反非常简单。认证服务器通过对称或非对称的加密方式利用 payload 生成 signature ,并在 header 中申明签名方式,仅此而已。通过这种本质上极其传统的方式,jwt可以实现 分布式的token验证功能 ,即资源服务器通过事先维护好的对称或者非对称密钥(非对称的话就是认证服务器提供的公钥),直接在本地验证token,这种去中心化的验证机制无疑很对现在分布式架构的胃口。jwt相对于传统的token来说,解决以下两个痛点:

在上面的那个资源服务器和认证服务器分离的例子中,如果认证服务器颁发的是jwt格式的token,那么资源服务器就可以直接自己验证token的有效性并绑定用户,这无疑大大提升了处理效率且减少了单点隐患。

就像布鲁克斯在《人月神话》中所说的名言一样:“没有银弹”。JWT的使用上现在也有一种误区,认为传统的认证方式都应该被jwt取代。事实上,jwt也不能解决一切问题,它也有适用场景和不适用场景。

适用场景:

这些场景能充分发挥jwt无状态以及分布式验证的优势

不适用的场景:

不要试图用jwt去代替session。 这种模式下其实传统的session+cookie机制工作的更好,jwt因为其无状态和分布式,事实上只要在有效期内,是无法作废的,用户的签退更多是一个客户端的签退,服务端token仍然有效,你只要使用这个token,仍然可以登陆系统。另外一个问题是续签问题,使用token,无疑令续签变得十分麻烦,当然你也可以通过redis去记录token状态,并在用户访问后更新这个状态,但这就是硬生生把jwt的无状态搞成有状态了,而这些在传统的session+cookie机制中都是不需要去考虑的。这种场景下,考虑高可用,我更加推荐采用分布式的session机制,现在已经有很多的成熟框架可供选择了(比如spring session)。

DABAN RP主题是一个优秀的主题,极致后台体验,无插件,集成会员系统
网站模板库 » OAuth2实现单点登录SSO

0条评论

发表评论

提供最优质的资源集合

立即查看 了解详情