前段时间有人问到单点登录如何实现,那本篇就来介绍一下单点登录的实现。由于单点登录涉及到多个客户端,本篇的客户端就以订单服务、商品服务为例。以下是单点登录中,订单服务、商品服务、认证服务器的交互时序图。
认证服务器搭建
认证服务的搭建可以参照本系列中的《Spring Authorization Server (二)认证服务器搭建》一篇,将该章节所搭建的认证服务器直接启动即可作为SSO认证服务器。如果忍受不了框架自带的登录页面和授权确认页面卡顿的情况,也可以选择《Spring Authorization Server (五)设备授权码登录》这一篇所搭建的服务器作为SSO认证服务器,也是直接启动即可。本篇我们将认证服务器工程取名为spring-oauth-sso-server。
订单服务搭建
订单服务作为客户端,在本系列的《Spring Authorization Server (三)客户端搭建》一篇中,我们也是介绍了客户端的搭建。其实在《Spring Authorization Server (三)客户端搭建》一篇中,已经完成了单点登录的一部分工作了,我们现在将该章节所搭建的客户端代码复制一份,工程取名为spring-oauth-sso-client-order,在此基础上进行修改。为了添加演示所需的交互页面,我们需要添加thymeleaf相关的maven依赖以及一些html页面。
pom.xml文件内容如下。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.oauth</groupId>
<artifactId>spring-oauth-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>spring-oauth-sso-client-order</artifactId>
<name>SSO客户端-订单服务</name>
<properties>
<spring-security.version>6.1.2</spring-security.version>
</properties>
<dependencies>
<!--spring-boot-starter-oauth2-client-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring-boot-starter-thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- webjars-locator-core -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator-core</artifactId>
</dependency>
<!-- bootstrap:5.2.3 -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>5.2.3</version>
</dependency>
<!-- jquery:3.6.4 -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.6.4</version>
</dependency>
</dependencies>
</project>
application.yml配置如下。
server:
ip: spring-oauth-client-order
port: 9003
logging:
level:
org.springframework.security: trace
spring:
application:
name: spring-oauth-client-order
security:
oauth2:
client:
provider:
#认证服务器信息
oauth-server:
#授权地址
issuer-uri: http://spring-oauth-server:9000
authorizationUri: ${spring.security.oauth2.client.provider.oauth-server.issuer-uri}/oauth2/authorize
#令牌获取地址
tokenUri: ${spring.security.oauth2.client.provider.oauth-server.issuer-uri}/oauth2/token
registration:
messaging-client-oidc:
#认证提供者,标识由哪个认证服务器进行认证,和上面的oauth-server进行关联
provider: oauth-server
#客户端名称
client-name: web平台-SSO客户端-订单服务
#客户端id,从认证平台申请的客户端id
client-id: web-client-id-order
#客户端秘钥
client-secret: secret
#客户端认证方式
client-authentication-method: client_secret_basic
#使用授权码模式获取令牌(token)
authorization-grant-type: authorization_code
#回调地址,接收认证服务器回传code的接口地址,之前我们是使用http://www.baidu.com代替
redirect-uri: http://spring-oauth-client-order:9003/login/oauth2/code/messaging-client-oidc
scope:
- profile
- openid
上面的配置中,我们将spring-oauth-client-order服务的端口设置为9003,为方便在本地进行测试,我们在C:\Windows\System32\drivers\etc目录下的hosts文件添加了127.0.0.1 spring-oauth-client-order spring-oauth-client-product域名映射.
在resource目录下创建templates文件夹,添加index.html、order_1.html、order_2.html三个测试页面用以测试。
index.html内容如下。
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Spring Authorization Server sample</title>
<link rel="stylesheet" href="/webjars/bootstrap/css/bootstrap.css" th:href="@{/webjars/bootstrap/css/bootstrap.css}" />
</head>
<body>
<div class="container" align="center">
<h1>订单服务</h1>
<h2 style="color: darkgoldenrod;">这是订单服务首页,访问该页面,无需登录。</h2>
</div>
</body>
</html>
order_1.html内容如下。
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Spring Authorization Server sample</title>
<link rel="stylesheet" href="/webjars/bootstrap/css/bootstrap.css" th:href="@{/webjars/bootstrap/css/bootstrap.css}" />
</head>
<body>
<div class="container" align="center">
<h1>订单服务</h1>
<h2 style="color: blue">订单页面1,认证信息如下。</h2>
<div>
<span th:text="${authentication}">认证信息:</span>
</div>
<h2><a href="http://spring-oauth-client-order:9003/order2">跳转到订单页面2</a></h2>
<h2><a href="http://spring-oauth-client-product:9004/product2">跳转到商品页面2</a></h2>
</div>
</body>
</html>
order_2.html内容如下。
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Spring Authorization Server sample</title>
<link rel="stylesheet" href="/webjars/bootstrap/css/bootstrap.css" th:href="@{/webjars/bootstrap/css/bootstrap.css}" />
</head>
<body>
<div class="container" align="center" >
<h1>订单服务</h1>
<h2 style="color: red">订单页面2,认证信息如下。</h2>
<div>
<span th:text="${authentication}">认证信息:</span>
</div>
<h2><a href="http://spring-oauth-client-order:9003/order1">跳转到订单页面1</a></h2>
<h2><a href="http://spring-oauth-client-product:9004/product1">跳转到商品页面1</a></h2>
</div>
</body>
</html>
在org.oauth.client.controller目录下添加OrderController,代码如下。
@Controller
public class OrderController {
@GetMapping("/index")
public String home() {
return "index";
}
@GetMapping("/order1")
public String order1(Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println("authentication:"+ authentication);
model.addAttribute("authentication",authentication);
return "order_1";
}
@GetMapping("/order2")
public String order2(Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println("authentication:"+ authentication);
model.addAttribute("authentication",authentication);
return "order_2";
}
}
在org.oauth.client包路径下新建config目录,添加WebSecurityConfig配置类,内容如下。
package org.oauth.client.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.web.SecurityFilterChain;
/**
* @author Rommel
* @version 1.0
* @date 2023/8/13-21:51
* @description TODO
*/
@Configuration
public class WebSecurityConfig {
@Bean
public SecurityFilterChain authorizationClientSecurityFilterChain(HttpSecurity http) throws Exception {
http
// 请求路径管理
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/assets/**", "/webjars/**", "/index").permitAll()
.anyRequest().authenticated()
)
// Session会话管理
.sessionManagement(sessionManagementConfigurer->{
sessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
})
//OAuth2.0登录配置
.oauth2Login(oauth2LoginCustomizer->{
// 设置用以获取token请求的客户端
oauth2LoginCustomizer.tokenEndpoint(tokenEndpointCustomizer->{
//采用DefaultAuthorizationCodeTokenResponseClient默认处理
tokenEndpointCustomizer.accessTokenResponseClient(new DefaultAuthorizationCodeTokenResponseClient());
});
})
;
return http.build();
}
}
上面的配置信息中,我们对 "/assets/**", "/webjars/**", "/index" 路径做了放行,其他请求都需要进行认证;对session会话管理进行了设置,将session会话创建策略设置为SessionCreationPolicy.ALWAYS;对OAuth2.0登录配置进行手动设置。
说明:此处的OAuth2.0登录配置,如果是使用Spring Boot2.6.8及以下的版本,可以添加以下的依赖,
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.6.8</version>
</dependency>
然后使用@EnableOAuth2Sso注解即可,本客户端工程使用的是Spring Boot3.1.1版本,需要手动设置oauth2Login,否则请求受保护的资源将不会重定向到授权服务器进行认证,而是直接返回403拒绝。关于oauth2Login的配置,可以参照官方文档:https://docs.spring.io/spring-security/reference/servlet/oauth2/login/advanced.html。
订单服务测试
oauth2_registered_client表增加一条订单服务的客户端,记录如下。
INSERT INTO `oauth-server`.`oauth2_registered_client` (`id`, `client_id`, `client_id_issued_at`, `client_secret`, `client_secret_expires_at`, `client_name`, `client_authentication_methods`, `authorization_grant_types`, `redirect_uris`, `post_logout_redirect_uris`, `scopes`, `client_settings`, `token_settings`) VALUES ('3eacac0e-0de9-4727-9a64-6bdd4be2ee3', 'web-client-id-order', '2023-07-12 07:33:42', '$2a$10$.J0Rfg7y2Mu8AN8Dk2vL.eBFa9NGbOYCPOAFEw.QhgGLVXjO7eFDC', NULL, 'web平台-SSO客户端-订单服务', 'client_secret_basic', 'refresh_token,authorization_code', 'http://spring-oauth-client-order:9003/login/oauth2/code/messaging-client-oidc', 'http://127.0.0.1:9000/', 'openid,profile', '{\"@class\":\"java.util.Collections$UnmodifiableMap\",\"settings.client.require-proof-key\":false,\"settings.client.require-authorization-consent\":true}', '{\"@class\":\"java.util.Collections$UnmodifiableMap\",\"settings.token.reuse-refresh-tokens\":true,\"settings.token.id-token-signature-algorithm\":[\"org.springframework.security.oauth2.jose.jws.SignatureAlgorithm\",\"RS256\"],\"settings.token.access-token-time-to-live\":[\"java.time.Duration\",1800.000000000],\"settings.token.access-token-format\":{\"@class\":\"org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat\",\"value\":\"self-contained\"},\"settings.token.refresh-token-time-to-live\":[\"java.time.Duration\",3600.000000000],\"settings.token.authorization-code-time-to-live\":[\"java.time.Duration\",300.000000000],\"settings.token.device-code-time-to-live\":[\"java.time.Duration\",300.000000000]}');
启动认证服务器spring-oauth-sso-server,订单服务器spring-oauth-client-order,
测试无需登录的请求
浏览器输入地址http://spring-oauth-client-order:9003/index,测试放开保护的资源,结果如下。
测试需要登录的请求
浏览器输入地址http://spring-oauth-client-order:9003/order1,测试受保护的资源,结果如下。
输入用户:user,密码:123456,提交登录,跳转到授权确认页面。
勾选授权信息,提交确认授权,则成功跳转到订单页面1,结果如下。
此时点击”跳转到订单页面2“,则亦能成功跳转过去,结果如下。
上面的操作都是订单服务与认证服务器之间的交互,从订单页面1跳转到订单页面2,属于同一个服务之间的跳转。从订单服务页面跳转到商品页面的功能尚未实现,下面需要搭建商品服务,实现订单服务到商品服务之间的跳转,这个跳转是跨客户端服务之间的跳转,这就涉及到单点登录了。
商品服务搭建
将订单服务spring-oauth-client-order复制一份进行修改,取名为我们将spring-oauth-client-product,服务的端口设置为9004。
pom.xml文件内容如下。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.oauth</groupId>
<artifactId>spring-oauth-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>spring-oauth-sso-client-product</artifactId>
<name>SSO客户端-商品服务</name>
<dependencies>
<!--spring-boot-starter-oauth2-client-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<!--spring-boot-starter-web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring-boot-starter-thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- webjars-locator-core -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator-core</artifactId>
</dependency>
<!-- bootstrap:5.2.3 -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>5.2.3</version>
</dependency>
<!-- jquery:3.6.4 -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.6.4</version>
</dependency>
</dependencies>
</project>
application.yml配置如下。
server:
ip: spring-oauth-client-product
port: 9004
logging:
level:
org.springframework.security: trace
spring:
application:
name: spring-oauth-client-product
security:
oauth2:
client:
provider:
#认证服务器信息
oauth-server:
#授权地址
issuer-uri: http://spring-oauth-server:9000
authorizationUri: ${spring.security.oauth2.client.provider.oauth-server.issuer-uri}/oauth2/authorize
#令牌获取地址
tokenUri: ${spring.security.oauth2.client.provider.oauth-server.issuer-uri}/oauth2/token
registration:
messaging-client-oidc:
#认证提供者,标识由哪个认证服务器进行认证,和上面的oauth-server进行关联
provider: oauth-server
#客户端名称
client-name: web平台-SSO客户端-商品服务
#客户端id,从认证平台申请的客户端id
client-id: web-client-id-product
#客户端秘钥
client-secret: secret
#客户端认证方式
client-authentication-method: client_secret_basic
#使用授权码模式获取令牌(token)
authorization-grant-type: authorization_code
#回调地址,接收认证服务器回传code的接口地址,之前我们是使用http://www.baidu.com代替
redirect-uri: http://spring-oauth-client-product:9004/login/oauth2/code/messaging-client-oidc
scope:
- profile
- openid
index.html内容如下。
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Spring Authorization Server sample</title>
<link rel="stylesheet" href="/webjars/bootstrap/css/bootstrap.css" th:href="@{/webjars/bootstrap/css/bootstrap.css}" />
</head>
<body>
<div class="container" align="center">
<h1>商品服务</h1>
<h2 style="color: darkgoldenrod;">这是商品服务首页,访问该页面,无需登录。</h2>
</div>
</body>
</html>
product_1.html内容如下。
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Spring Authorization Server sample</title>
<link rel="stylesheet" href="/webjars/bootstrap/css/bootstrap.css" th:href="@{/webjars/bootstrap/css/bootstrap.css}" />
</head>
<body>
<div class="container" align="center">
<h1>商品服务</h1>
<h2 style="color: blue">商品页面1,认证信息如下。</h2>
<div>
<span th:text="${authentication}">认证信息:</span>
</div>
<h2><a href="http://spring-oauth-client-product:9004/product2">跳转到商品页面2</a></h2>
<h2><a href="http://spring-oauth-client-order:9003/order2">跳转到订单页面2</a></h2>
</div>
</body>
</html>
product_2.html内容如下。
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Spring Authorization Server sample</title>
<link rel="stylesheet" href="/webjars/bootstrap/css/bootstrap.css" th:href="@{/webjars/bootstrap/css/bootstrap.css}" />
</head>
<body>
<div class="container" align="center">
<h1>商品服务</h1>
<h2 style="color: red">商品页面2,认证信息如下。</h2>
<div>
<span th:text="${authentication}">认证信息:</span>
</div>
<h2><a href="http://spring-oauth-client-product:9004/product1">跳转到商品页面1</a></h2>
<h2><a href="http://spring-oauth-client-order:9003/order1">跳转到订单页面1</a></h2>
</div>
</body>
</html>
ProductController,代码如下。
@Controller
public class ProductController {
@GetMapping("/index")
public String home() {
return "index";
}
@GetMapping("/product1")
public String product1(Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println("authentication:"+ authentication);
model.addAttribute("authentication",authentication);
return "product_1";
}
@GetMapping("/product2")
public String product(Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println("authentication:"+ authentication);
model.addAttribute("authentication",authentication);
return "product_2";
}
}
WebSecurityConfig配置类,内容如下。
package org.oauth.client.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.web.SecurityFilterChain;
/**
* @author Rommel
* @version 1.0
* @date 2023/8/13-21:51
* @description TODO
*/
@Configuration
public class WebSecurityConfig {
@Bean
public SecurityFilterChain authorizationClientSecurityFilterChain(HttpSecurity http) throws Exception {
http
// 请求路径管理
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/assets/**", "/webjars/**", "/index").permitAll()
.anyRequest().authenticated()
)
// Session会话管理
.sessionManagement(sessionManagementConfigurer->{
sessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
})
//OAuth2.0登录配置
.oauth2Login(oauth2LoginCustomizer->{
// 设置用以获取token请求的客户端
oauth2LoginCustomizer.tokenEndpoint(tokenEndpointCustomizer->{
//采用DefaultAuthorizationCodeTokenResponseClient默认处理
tokenEndpointCustomizer.accessTokenResponseClient(new DefaultAuthorizationCodeTokenResponseClient());
});
})
;
return http.build();
}
}
单点登录测试
oauth2_registered_client表增加一条商品服务的客户端,记录如下。
INSERT INTO `oauth-server`.`oauth2_registered_client` (`id`, `client_id`, `client_id_issued_at`, `client_secret`, `client_secret_expires_at`, `client_name`, `client_authentication_methods`, `authorization_grant_types`, `redirect_uris`, `post_logout_redirect_uris`, `scopes`, `client_settings`, `token_settings`) VALUES ('3eacac0e-0de9-4727-9a64-6bdd4be2ee4', 'web-client-id-product', '2023-07-12 07:33:42', '$2a$10$.J0Rfg7y2Mu8AN8Dk2vL.eBFa9NGbOYCPOAFEw.QhgGLVXjO7eFDC', NULL, 'web平台-SSO客户端-商品服务', 'client_secret_basic', 'refresh_token,authorization_code', 'http://spring-oauth-client-product:9004/login/oauth2/code/messaging-client-oidc', 'http://127.0.0.1:9000/', 'openid,profile', '{\"@class\":\"java.util.Collections$UnmodifiableMap\",\"settings.client.require-proof-key\":false,\"settings.client.require-authorization-consent\":true}', '{\"@class\":\"java.util.Collections$UnmodifiableMap\",\"settings.token.reuse-refresh-tokens\":true,\"settings.token.id-token-signature-algorithm\":[\"org.springframework.security.oauth2.jose.jws.SignatureAlgorithm\",\"RS256\"],\"settings.token.access-token-time-to-live\":[\"java.time.Duration\",1800.000000000],\"settings.token.access-token-format\":{\"@class\":\"org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat\",\"value\":\"self-contained\"},\"settings.token.refresh-token-time-to-live\":[\"java.time.Duration\",3600.000000000],\"settings.token.authorization-code-time-to-live\":[\"java.time.Duration\",300.000000000],\"settings.token.device-code-time-to-live\":[\"java.time.Duration\",300.000000000]}');
启动认证服务器spring-oauth-sso-server,订单服务器spring-oauth-client-order,商品服务spring-oauth-client-product。
浏览器输入地址http://spring-oauth-client-product:9004/index,测试商品服务放开保护的资源,结果如下。
浏览器输入地址http://spring-oauth-client-product:9004/product1,测试受保护的资源,结果如下。
输入用户:user,密码:123456,提交登录,跳转到授权确认页面。
勾选授权信息,提交确认授权,则成功跳转到商品页面1,结果如下。
点击”跳转到商品页面2“,结果如下。
点击”跳转到订单页面1“,结果如下。
注意:此时我们从商品服务跳转到订单服务,属于跨客户端服务,由于我们在操作商品服务时已经输入用户名和密码进行了登录,session会话已存在,因此在此处就绕过了用户名、密码登录了。
勾选授权信息,提交确认授权,则成功跳转到订单页面1,结果如下。
点击”跳转到订单页面2“,结果如下。
点击”跳转到商品页面1“,结果如下。
点击”跳转到商品页面2“,结果如下。
说明:在首次访问订单服务或商品服务受保护的资源时,在登录过程中,都会跳转到授权确认页面进行授权确认,如果想去掉授权确认页面的这一步操作,实现无感确认,可以将oauth2_registered_client表中对应的客户端记录,client_settings字段的settings.client.require-authorization-consent值设置为false。将settings.client.require-authorization-consent值设置为false后,上面的测试,首次访问商品服务进行登录时,输入用户名、密码提交后,将不会跳转到授权确认页面,直接默认授权,从商品服务的页面首次跳转到订单服务的页面时,也不会先跳转到授权确认页面,而是直接跳转到订单服务的页面。
总结
本篇先是用时序图介绍了单点登录的交互流程。接着介绍了单点登录中认证服务器、客户端商品服务的搭建,同时对客户端商品服务进行了测试。然后以客户端订单服务为样板搭建了一份客户端商品服务。最后对单点登录的全流程进行了闭环测试。