Simple Single Sign-On with Spring Security OAuth2 – 使用Spring Security OAuth2的简单单点登录

最后修改: 2017年 6月 2日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll discuss how to implement SSO – Single Sign On – using Spring Security OAuth and Spring Boot, using Keycloak as the Authorization Server.

在本教程中,我们将讨论如何使用Spring Security OAuth和Spring Boot实现SSO – Single Sign On,并使用Keycloak作为授权服务器。

We’ll use 4 separate applications:

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

  • An Authorization Server – which is the central authentication mechanism
  • A Resource Server – the provider of Foos
  • Two Client Applications – the applications using SSO

Very simply put, when a user tries to access a resource via one Client app, they’ll be redirected to authenticate first, through the Authorization Server. Keycloak will sign the user in, and while still being logged in the first app, if the second Client app is accessed using the same browser, the user will not need to enter their credentials again.

很简单,当用户试图通过一个客户端应用程序访问一个资源时,他们将被重定向,首先通过授权服务器进行认证。Keycloak将签署用户,并且在仍然登录到第一个应用程序时,如果使用相同的浏览器访问第二个客户端应用程序,用户将不需要再次输入他们的凭证。

We’re going to use the Authorization Code grant type out of OAuth2 to drive the delegation of authentication.

我们将使用OAuth2中的授权代码赠与类型来驱动认证的委托。

We’ll use the OAuth stack in Spring Security 5. If you want to use the Spring Security OAuth legacy stack, have a look at this previous article: Simple Single Sign-On with Spring Security OAuth2 (legacy stack)

我们将在Spring Security 5中使用OAuth协议栈。如果你想使用Spring Security OAuth传统协议栈,请看之前的这篇文章。使用Spring Security OAuth2的简单单点登录(legacy stack)

As per the migration guide:

根据迁移指南

Spring Security refers to this feature as OAuth 2.0 Login while Spring Security OAuth refers to it as SSO

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

Alright, let’s jump right in.

好吧,让我们直接跳进去。

2. The Authorization Server

2.授权服务器

Previously, the Spring Security OAuth stack offered the possibility of setting up an Authorization Server as a Spring Application.

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

However, the OAuth stack has been deprecated by Spring and now we’ll be using Keycloak as our Authorization Server.

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

So this time, we’ll set up our Authorization Server as an embedded Keycloak server in a Spring Boot app.

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

In our pre-configuration, we’ll define two clients, ssoClient-1 and ssoClient-2, one for each Client Application.

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

3. The Resource Server

3.资源服务器

Next, we need a Resource Server, or the REST API which will provide us the Foos our Client App will consume.

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

It’s essentially the same as we used for our Angular Client Apps previously.

它本质上是,与我们之前用于Angular客户端应用程序的相同。

4. The Client Applications

4.客户应用

Now let’s look at our Thymeleaf Client Application; we’ll, of course, use Spring Boot to minimize the configuration.

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

Do keep in mind that we’ll need to have 2 of these to demonstrate Single Sign-On functionality.

请记住,我们需要有2个这样的东西来演示单点登录功能

4.1. Maven Dependencies

4.1.Maven的依赖性

First, we will need the following dependencies in our pom.xml:

首先,我们在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>

To include all the client support we’ll require, including security, we just need to add spring-boot-starter-oauth2-client. Also, since the old RestTemplate is going to be deprecated, we’re going to use WebClient, and that’s why we added spring-webflux and reactor-netty.

为了包含所有我们需要的客户端支持,包括安全,我们只需要添加spring-boot-starter-oauth2-client。另外,由于旧的RestTemplate将被弃用,我们将使用WebClient,这就是为什么我们添加了spring-webfluxreactor-netty

4.2. Security Configuration

4.2.安全配置

Next, the most important part, the security configuration of our first client application:

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

@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();
    }

}

The core part of this configuration is the oauth2Login() method, which is used to enable Spring Security’s OAuth 2.0 Login support. Since we’re using Keycloak, which is by default a single sign-on solution for web apps and RESTful web services, we do not need to add any further configuration for SSO.

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

Finally, we also defined a WebClient bean to act as a simple HTTP Client to handle requests to be sent to our Resource Server.

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

And here’s the application.yml:

这里是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/baeldung/protocol/openid-connect/auth
            token-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/token
            user-info-uri: http://localhost:8083/auth/realms/baeldung/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/        

Here, spring.security.oauth2.client.registration is the root namespace for registering a client. We defined a client with registration id custom. Then we defined its client-id, client-secret, scope, authorization-grant-type and redirect-uri, which of course, should be the same as that defined for our Authorization Server.

这里,spring.security.oauth2.client.registration是用于注册客户端的根命名空间。我们定义了一个注册ID为custom的客户端。然后我们定义了它的client-idclient-secretscopeauthorization-grant-typeredirect-uri,当然,这应该与我们为授权服务器定义的相同。

After that, we defined our service provider or the Authorization Server, again with the same id of custom, and listed down its different URI’s for Spring Security to use. That’s all we need to define, and the framework does the entire logging-in process, including redirection to Keycloak, seamlessly for us.

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

Also note that, in our example here, we rolled out our Authorization Server, but of course we can also use other, third-party providers such as Facebook or GitHub.

还要注意的是,在我们这里的例子中,我们推出了我们的授权服务器,当然我们也可以使用其他的、第三方的供应商,如Facebook 或 GitHub

4.3. The Controller

4.3.控制器

Let’s now implement our controller in the Client App to ask for Foos from our Resource Server:

现在让我们在客户端应用程序中实现我们的控制器,从我们的资源服务器请求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";
    }
}

As we can see, we have only one method here that’ll dish out the resources to the foos template. We did not have to add any code for login.

正如我们所看到的,我们在这里只有一个方法,将资源分配给foos模板。我们不需要为登录添加任何代码。

4.4. Front End

4.4.前端

Now, let’s take a look at the front-end configuration of our client application. We’re not going to focus on that here, mainly because we already covered in on the site.

现在,让我们来看看我们的客户端应用程序的前端配置。我们不打算在这里集中讨论这个问题,主要是因为我们已经在网站上介绍过了

Our client application here has a very simple front-end; here’s the index.html:

我们的客户应用在这里有一个非常简单的前端;这里是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>

And the foos.html:

还有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>

The foos.html page needs the users to be authenticated. If a non-authenticated user tries to access foos.html, they’ll be redirected to Keycloak’s login page first.

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

4.5. The Second Client Application

4.5.第二个客户应用

We’ll configure a second application, Spring OAuth Client Thymeleaf -2 using another client_id ssoClient-2.

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

It’ll mostly be the same as the first application we just described.

它大多会与我们刚才描述的第一个应用相同。

The application.yml will differ to include a different client_id, client_secret and redirect_uri in its spring.security.oauth2.client.registration:

application.yml将有所不同,在其spring.security.oauth2.client.registration:中包括不同的client_idclient_secretredirect_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

And, of course, we need to have a different server port for it as well, so that we can run them in parallel:

当然,我们也需要为它准备一个不同的服务器端口,这样我们就可以并行地运行它们。

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

Finally, we’ll tweak the front end HTMLs to have a title as Spring OAuth Client Thymeleaf – 2 instead of – 1 so that we can distinguish between the two.

最后,我们将调整前端的HTMLs,使其标题为Spring OAuth Client Thymeleaf – 2,而不是– 1,这样我们就可以区分两者了。

5. Testing SSO Behavior

5.测试SSO行为

To test SSO behavior, let’s run our Applications.

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

We’ll need all our 4 Boot Apps – the Authorization Server, the Resource Server and both Client Applications – to be up and running for this.

我们需要所有的4个启动应用程序–授权服务器、资源服务器和两个客户端应用程序–都能启动并运行。

Now let’s open up a browser, say Chrome, and log in to Client-1 using the credentials john@test.com/123. Next, in another window or tab, hit the URL for Client-2. On clicking the login button, we’ll be redirected to the Foos page straightaway, bypassing the authentication step.

现在让我们打开一个浏览器,比如说Chrome浏览器,用http://localhost:8082/ui-one”>Client-1的凭证登录john@test.com/123。接下来,在另一个窗口或标签中,点击Client-2的URL。点击登录按钮后,我们将被直接转到Foos页面,绕过了认证步骤。

Similarly, if the user logs in to Client-2 first, they need not enter their username/password for Client-1.

同样地,如果用户先登录Client-2,他们不需要输入Client-1的用户名/密码。

6. Conclusion

6.结论

In this tutorial, we focused on implementing Single Sign-On using Spring Security OAuth2 and Spring Boot using Keycloak as the identity provider.

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

As always, the full source code can be found over on GitHub.

一如既往,完整的源代码可以在GitHub上找到over