SpringBoot+OAuth2实现单点登录
写在前面
我们知道分布式系统由多个不同的子系统组成,但是我们希望在使用系统的时只需登录一次就能访问该系统,而不用多次登录,因此单点登录是一个很常见的需求。
在《OAuth2.0+JWT实现单点登录》一文中,我们使用OAuth2+JWT
这一技术实现了单点登录,其实正确来说我们实现的是无状态登录,只是这种无状态登录满足单点登录的要求。
前面也说了无状态存在一些缺点,因此接下来我们尝试使用另一种技术,使用SpringBoot+OAuth2.0并结合@EnableOAuth2Sso
注解这一方式来实现单点登录。
请注意上述两种实现方案都有其比较适合的场景,因此具体选择哪种需要结合实际情况来进行选择。
初始化项目
前面我们都是将授权服务器和资源服务器分开搭建,本篇出于简单考虑,就直接将两者放在一个服务器上,名称统一为“统一认证中心”。此案例除了授权服务器外,还需要多个客户端应用,这里就提供两个。各实例项目名称、角色名称和端口如下表所示:
项目名称 | 角色名称 | 端口 |
---|---|---|
auth-server | 授权服务器+资源服务器 | 2019 |
client-app1 | 客户端1 | 2020 |
client-app2 | 客户端2 | 2021 |
可以看到此处的auth-server
项目充当授权服务器和资源服务器的角色,client-app1
和client-app2
项目分别扮演子系统角色,之后当用户从client-app1
上登录成功后,此时也能访问到client-app2
,这样我们就能验证单点登录功能配置成功了。
空Maven父工程搭建
使用Maven新建一个空白的父工程,名称为oauth2-sso
,之后我们将在这个父工程中搭建子项目。
统一认证中心搭建
在oauth2-sso
父工程中新建一个子模块,名称为auth-server
,在选择依赖的时候选择如下三个依赖:Web、Spring Cloud Security下的Cloud Security和Cloud OAuth2:
第一步,将父工程oauth2-sso
项目的pom.xml依赖文件修改为如下所示配置:
1 | <?xml version="1.0" encoding="UTF-8"?> |
第二步,由于此项目需要充当授权服务器和资源服务器的角色,因此需要在这个项目的启动类上添加@EnableResourceServer
注解,表示开启资源服务器配置:
1 | @SpringBootApplication |
第三步,接下来我们开始对授权服务器进行配置,由于此处的资源服务器和授权服务器放在一起,因此对于授权服务器的配置非常简单。新建一个config包,并在该包内新建一个AuthServerConfig
类,注意它需要继承AuthorizationServerConfigurerAdapter
类,里面的代码如下所示:
1 | @Configuration |
可以看到此处我们只是简单的配置了客户端的信息,且配置的非常简单,直接将客户端信息保存在内存中。
第四步,在config包内新建一个SecurityConfig
类,注意它需要继承WebSecurityConfigurerAdapter
类,里面的代码如下所示:
1 | @Configuration |
可以看到我们首先提供了一个返回BCryptPasswordEncoder
实例的方法,之后重写了configure(WebSecurity web)
方法,该方法用户放开对静态资源的访问权限。接着重写了configure(HttpSecurity http)
方法,在该方法中我们对认证相关的端点进行放行,同时自定义了登录页面和登录接口。然后重写了configure(AuthenticationManagerBuilder auth)
方法,我们在该方法中定义了一个用户,该用户信息均保存在内存中。最后还有一个非常重要的地方,需要在配置类上添加@Order(1)
注解,用于提升Spring Security框架的优先级,默认数字越小,优先级越大。
第五步,由于上面定义的SecurityConfig
和AuthServerConfig
都是授权服务器提供的,因此我们还需要提供一个暴露用户信息的接口。(此处由于授权服务器和资源服务器放在一起,因此可直接定义。但是当授权服务器和资源服务器是分开搭建时,此时由资源服务器提供该接口。)
在auth-server
项目内新建一个controller包,并在该包内新建一个UserController
类,里面的代码如下所示:
1 | @RestController |
第六步,在项目resource/static
目录下新建一个login.html
文件,具体内容可以下载本项目源码进行查看。
第七步,在项目的application.properties
配置文件内配置项目的端口号,如下所示:
1 | server.port=2019 |
这样我们就搭建完成了统一认证中心。
客户端搭建
接下来我们开始搭建客户端,在oauth2-sso
父工程中新建一个子模块,名称为client-app1
,在选择依赖的时候选择如下三个依赖:Web、Spring Cloud Security下的Cloud Security和Cloud OAuth2:
第一步,将新创建的client-app1
项目配置到父工程oauth2-sso
项目的pom.xml依赖文件中:
1 | <modules> |
第二步,新建一个config包,并在该包内新建一个SecurityConfig
类,注意它需要继承WebSecurityConfigurerAdapter
类,里面的代码如下所示:
1 | @Configuration |
上述代码非常简单,表示client-app1
项目中所有的接口都需要经过认证之后才能访问,同时还需要在该类上添加@EnableOAuth2Sso
注解,表示开启单点登录功能。
第三步,新建一个controller包,并在该包内新建一个HelloController
类,里面的代码如下所示:
1 | @RestController |
可以看到我们定义了一个/hello
接口,该接口的作用是返回当前登录用户的姓名和角色信息。
第四步,在项目的application.properties
配置文件内配置项目的端口号和OAuth2的相关信息,如下所示:
1 | security.oauth2.client.client-secret=1234 |
在上面的配置中,client-secret
表示客户端密码;client-id
表示客户端ID;user-authorization-uri
表示用户授权的端点;access-token-uri
表示获取令牌的端点;user-info-uri
表示从资源服务器上获取信息的接口;之后就是配置项目端口号和cookie的名称,这样我们就完成了客户端1的配置。
由于客户端1和客户端2的配置完成一致,只有最后项目名称和端口以及cookie的不同,因此关于客户端2的创建工作就省略。
项目测试
接下来分别启动项目,开始进行测试。首先我们在浏览器中输入http://127.0.0.1:2020/hello
链接,尝试去访问客户端1中的hello接口,此时页面会跳转到统一认证中心进行认证:
之后用户以envy/1234进行登录,此时页面会跳转到http://127.0.0.1:2020/hello
链接,并显示以下信息:
此时当开发者直接去访问客户端2的hello接口时,也就是http://127.0.0.1:2021/hello
链接,可以看到页面显示如下信息:
可以看到用户不需要再次登录,也能访问到客户端2中的hello接口,这样我们就实现了单点登录这一功能。
请求流程分析
为了更好的对上述内容进行分析,接下来我们将通过请求流程来进一步分析:
(1)当我们访问client-app1
项目的hello接口时,由于此接口被保护,只有通过认证后才能访问,因此此时我们的请求会被拦截下来,系统会将我们重定向到client-app1
项目的login接口,也就是登录页面:
(2)当我们去访问client-app1
项目的login接口时,由于我们配置了@EnableOAuth2Sso
注解,因此请求会被再次拦截下来,单点登录拦截器会根据我们在application.properties
配置文件中的配置,自动发起请求去获取授权码:
(3)由于(2)发送的请求是请求auth-server
授权服务器上的内容,因此这个请求也需要先登录才能访问,所以会再次重定向到auth-server
授权服务器的登录页面,也就是我们看到的统一认证中心页面:
(4)在统一认证中心用户成功登录之后,会继续执行(2)中的请求,此时就能获取到授权码了。
(5)在获取到授权码之后,此时会重定向到client-app1
项目的login页面,而实际上client-app1
项目是没有登录页面的,因此这个请求依旧会被拦截,此时拦截到的地址中包含授权码。拿着授权码在OAuth2ClientAuthenticationProcessingFilter
类中向auth-server
发起请求请求就能得到access_token
了。
(6)在获取到access_token
之后,接下来就向我们配置的user-info-uri
地址发送请求,来获取登录的用户信息。在拿到用户信息之后,接下来会在client-app1
项目上重写执行一次Spring Security的登录流程,这样完成了接口信息的获取。
以上就是如何使用Spring Boot+OAuth2.0来实现单点登录这一功能的相关分析。
(完)