使用Spring Security OAuth2进行简单的单点登录

2023/05/27

1. 概述

在本教程中,我们将讨论如何使用Spring Security OAuth和Spring Boot以及使用Keycloak作为授权服务器来实现SSO(单点登录)

我们将使用4个独立的应用程序:

  • 授权服务器:作为中央身份验证机制
  • 资源服务器:Foo的提供者
  • 两个客户端应用程序:使用SSO的应用程序

简而言之,当用户试图通过一个客户端应用程序访问资源时,他们将被重定向到首先通过授权服务器进行身份验证。Keycloak将让用户登录,并且在登录第一个应用的同时,如果使用相同的浏览器访问第二个客户端应用,则用户无需再次输入其凭据。

我们将使用OAuth2中的Authorization Code授权类型来驱动身份验证委托。

我们将在Spring Security 5中使用OAuth堆栈。如果你想使用Spring Security OAuth遗留堆栈,请查看之前的文章:使用Spring Security OAuth2(遗留堆栈)进行简单单点登录

根据迁移指南

Spring Security将此功能称为OAuth 2.0登录,而Spring Security OAuth将其称为SSO

延伸阅读:

Spring Security 5 - OAuth2登录

了解如何在Spring Security 5中使用OAuth2使用Facebook、Google或其他凭据对用户进行身份验证。

阅读更多

Spring Security OAuth2中的新功能 - 验证Claim

Spring Security OAuth中新Claim验证支持的快速实用介绍。

阅读更多

使用Spring Social的二级Facebook登录

快速了解在标准表单登录Spring应用程序旁边实现Facebook驱动的身份验证。

阅读更多

2. 授权服务器

以前,Spring Security OAuth堆栈提供了将授权服务器设置为Spring应用程序的可能性。

然而,OAuth堆栈已被Spring弃用,现在我们将使用Keycloak作为我们的授权服务器。

所以这一次,我们将授权服务器设置为Spring Boot应用程序中的嵌入式Keycloak服务器

在我们的预配置中,我们将定义两个客户端,ssoClient-1和ssoClient-2,每个客户端应用程序一个。

3. 资源服务器

接下来,我们需要一个资源服务器或REST API,它将为我们提供我们的客户端应用程序将使用的Foos。

它基本上与我们之前用于Angular客户端应用程序的功能相同。

4. 客户端应用程序

现在让我们看看我们的Thymeleaf客户端应用程序;当然,我们将使用Spring Boot来最小化配置。

请记住,我们需要其中的两个来演示单点登录功能

4.1 Maven依赖项

首先,我们需要在pom.xml中添加以下依赖项:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
    <groupId>io.projectreactor.netty</groupId>
    <artifactId>reactor-netty</artifactId>
</dependency>

为了包括我们需要的所有客户端支持,包括安全性,我们只需要添加spring-boot-starter-oauth2-client。此外,由于旧的RestTemplate将被弃用,我们将使用WebClient,这就是我们添加spring-webflux和reactor-netty的原因。

4.2 安全配置

接下来,最重要的部分,我们第一个客户端应用程序的安全配置:

@EnableWebSecurity
public class UiSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/", "/login**")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .oauth2Login();
        return http.build();
    }

    @Bean
    WebClient webClient(ClientRegistrationRepository clientRegistrationRepository,
                        OAuth2AuthorizedClientRepository authorizedClientRepository) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 =
                new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository, authorizedClientRepository);
        oauth2.setDefaultOAuth2AuthorizedClient(true);
        return WebClient.builder()
                .apply(oauth2.oauth2Configuration())
                .build();
    }
}

此配置的核心部分是oauth2Login()方法,用于启用Spring Security的OAuth 2.0登录支持。由于我们使用的是Keycloak,它默认是Web应用程序和RESTful Web服务的单点登录解决方案,因此我们不需要为SSO添加任何进一步的配置。

最后,我们还定义了一个WebClient bean作为一个简单的HTTP客户端来处理发送到我们的资源服务器的请求。

这是application.yml:

spring:
    security:
        oauth2:
            client:
                registration:
                    custom:
                        client-id: ssoClient-1
                        client-secret: ssoClientSecret-1
                        scope: read,write
                        authorization-grant-type: authorization_code
                        redirect-uri: http://localhost:8082/ui-one/login/oauth2/code/custom
                provider:
                    custom:
                        authorization-uri: http://localhost:8083/auth/realms/tuyucheng/protocol/openid-connect/auth
                        token-uri: http://localhost:8083/auth/realms/tuyucheng/protocol/openid-connect/token
                        user-info-uri: http://localhost:8083/auth/realms/tuyucheng/protocol/openid-connect/userinfo
                        user-name-attribute: preferred_username
    thymeleaf:
        cache: false

server:
    port: 8082
    servlet:
        context-path: /ui-one

resourceserver:
    api:
        project:
            url: http://localhost:8081/sso-resource-server/api/foos/

在这里,spring.security.oauth2.client.registration是注册客户端的根命名空间。我们定义了一个注册ID为custom的客户端。然后我们定义了它的client-id,client-secret,scope,authorization-grant-type和redirect-uri,当然,这应该和我们的授权服务器定义的一样。

之后,我们定义了我们的服务提供者或授权服务器,再次使用相同的custom id,并列出了其不同的URI供Spring Security使用。这就是我们需要定义的全部内容,框架会为我们无缝地完成整个登录过程,包括重定向到Keycloak

另请注意,在我们的示例中,我们推出了授权服务器,但当然我们也可以使用其他第三方提供商,例如Facebook或GitHub

4.3 控制器

现在让我们在客户端应用程序中实现我们的控制器,以从我们的资源服务器请求Foos:

@Controller
public class FooClientController {

    @Value("${resourceserver.api.url}")
    private String fooApiUrl;

    @Autowired
    private WebClient webClient;

    @GetMapping("/foos")
    public String getFoos(Model model) {
        List<FooModel> foos = this.webClient.get()
                .uri(fooApiUrl)
                .retrieve()
                .bodyToMono(new ParameterizedTypeReference<List<FooModel>>() {})
                .block();
        model.addAttribute("foos", foos);
        return "foos";
    }
}

正如我们所看到的,我们这里只有一种方法可以将资源分发给foos模板。我们不必添加任何登录代码

4.4 前端

现在,让我们看一下客户端应用程序的前端配置。我们不打算在这里重点讨论,主要是因为我们已经在网站上进行了介绍

我们这里的客户端应用程序有一个非常简单的前端;这是index.html:

<a class="navbar-brand" th:href="@{/foos/}">Spring OAuth Client Thymeleaf - 1</a>
<label>Welcome !</label> <br /> <a th:href="@{/foos/}">Login</a>

还有foos.html:

<a class="navbar-brand" th:href="@{/foos/}">Spring OAuth Client Thymeleaf -1</a>
Hi, <span sec:authentication="name">preferred_username</span>

<h1>All Foos:</h1>
<table>
    <thead>
    <tr>
        <td>ID</td>
        <td>Name</td>
    </tr>
    </thead>
    <tbody>
    <tr th:if="${foos.empty}">
        <td colspan="4">No foos</td>
    </tr>
    <tr th:each="foo : ${foos}">
        <td><span th:text="${foo.id}"> ID </span></td>
        <td><span th:text="${foo.name}"> Name </span></td>
    </tr>
    </tbody>
</table>

foos.html页面需要对用户进行身份验证。如果未经身份验证的用户试图访问foos.html,他们将首先被重定向到Keycloak的登录页面

4.5 第二个客户端应用程序

我们将使用另一个client_id ssoClient-2配置第二个应用程序Spring OAuth Client Thymeleaf - 2。

它与我们刚刚描述的第一个应用程序基本相同。

只有application.yml将有所不同,在其spring.security.oauth2.client.registration中包含不同的client_id、client_secret和redirect_uri

spring:
    security:
        oauth2:
            client:
                registration:
                    custom:
                        client-id: ssoClient-2
                        client-secret: ssoClientSecret-2
                        scope: read,write
                        authorization-grant-type: authorization_code
                        redirect-uri: http://localhost:8084/ui-two/login/oauth2/code/custom

而且,当然,我们还需要有一个不同的服务器端口,以便我们可以并行运行它们:

server:
    port: 8084
    servlet:
        context-path: /ui-two

最后,我们将调整前端HTML的标题为Spring OAuth Client Thymeleaf – 2而不是Spring OAuth Client Thymeleaf - 1,以便我们可以区分两者。

5. 测试SSO行为

要测试SSO行为,让我们运行我们的应用程序。

为此,我们需要启动并运行所有4个启动应用程序-授权服务器、资源服务器和两个客户端应用程序。

现在让我们打开一个浏览器,比如Chrome,并使用凭据john@test.com/123登录到Client-1。接下来,在另一个窗口或选项卡中,点击Client-2的URL。单击登录按钮后,我们将立即被重定向到Foos页面,绕过身份验证步骤。

类似地,如果用户首先登录到Client-2,则他们无需输入Client-1的用户名/密码。

6. 总结

在本教程中,我们重点介绍了使用Spring Security OAuth2和使用Keycloak作为身份提供者的Spring Boot实现单点登录。

与往常一样,可以在GitHub上找到完整的源代码。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章