加载中

Although security is a crucial aspect of any application, its implementation can be difficult. Worse, it is often neglected, poorly implemented and intrusive in the code. But lately, security servers have appeared which allow for outsourcing and delegating all the authentication and authorization aspects. Of these servers, one of the most promising is Keycloak, open-source, flexible, and agnostic of any technology, it is easily deployable/adaptable in its own infrastructure.

Moreover, Keycloak is more than just an authentication server, it also provides a complete Identity Management system, user federation for third parties like LDAP and a lot more. Check it out here.

The project can also be found on GitHub.

尽管安全性是应用程序的一个关键点,但是在开发中实施起来确实比较麻烦。更加麻烦的是,这个关键点通常不怎么受重视,实现的效果普遍的 low,而且受到诸多方面的掣肘。而最近安全服务器的出现,就可以将认证和授权方面的业务逻辑外包和分派出去。 在这些服务器中,最有希望的是 Keycloak,因为它开放源代码,灵活,而且未来能适合任何技术亦未可知,它也可以轻松地部署并适应于其自身的基础设施之中。

另外,Keycloak 也不仅仅是一个身份验证服务器,它还提供了完整的身份管理系统,可以实现诸如 LDAP 那样的第三方的用户联盟。可以看看这里

该项目可以在GitHub上找到。

Spring Boot and Keycloak

Keycloak provides adapters for an application that needs to interact with a Keycloak instance. There are adapters for WildFly/EAP, NodeJS, JavaScript, and, of course, for Spring Boot.

Setting Up a Keycloak Server

You have different options to set up a Keycloak server, but the easiest one is probably to grab a standalone distribution, unzip it, and voila! Open a terminal and go to your unzipped Keycloak server and from the bin directory simply run:

./standalone.sh(bat)

Then open a browser and go to http://localhost:8080/auth.

Spring Boot 和 Keycloak

Keycloak 为需要与 Keycloak 实例交互的应用程序提供适配器。 有用于 WildFly / EAP,NodeJS,JavaScript 的适配器,当然也有用于 Spring Boot 的适配器。

设置 Keycloak 服务

你有多个选项来设置一个 Keycloak 服务器,但最简单的一个可能是获取一个独立的发布。解压缩,瞧着! 打开一个终端,然后切换到你解压缩的 Keycloak 服务,并从 bin 目录下运行:

./standalone.sh(bat)

然后打开浏览器,然后转到进入 http://localhost:8080/auth

Since it’s the first time that the server is running, you will have to create an admin user, so let’s create an admin user with admin as the username and admin for the password:

Now you can log in to your administration console and start configuring Keycloak.

由于这是服务的第一次运行,你必须创建一个管理员账户才行,所以让来我们创建一个管理员用户,用户名为 admin,密码为 admin:

现在您可以登录到管理控制台并开始配置 Keycloak。

Creating a New Realm

Keycloak defines the concept of a realm in which you will define your clients, which in Keycloak terminology means an application that will be secured by Keycloak. It can be a Web App, a Java EE backend, a Spring Boot, etc.

So let’s create a new realm by simply clicking the “Add realm” button:

Let’s call it “SpringBoot.”

创建一个新的 Realm

Keycloak 定义了一个 realm 的概念,并且你将在 realm 中定义客户端,在 Keycloak 中的术语是指由 Keycloak 保护的应用程序。 它可以是 Web App,Java EE 后端,Spring Boot 等等。

所以让我们创建一个新的 realm,只需点击“添加领域”按钮:

让我们称之为“SpringBoot.”

Creating the Client, the Role, and the User

Now we need to define a client, which will be our Spring Boot app. Go to the “Clients” section and click the “Create” button. We will call our client “product-app”:

On the next screen, we can keep the default settings, but just need to enter a valid redirect URL that Keycloak will use once the user is authenticated. As the value put: “http://localhost:8081/*”

Don’t forget to Save!

Now, we will define a role that will be assigned to our users. Let’s create a simple role called “user”:

And at last but not least let’s create a user. Only the username property is needed, so let’s call him “testuser”:

And finally, we need to set his credentials, so go to the Credentials tab of your user and choose a password. I will be using “password” for the rest of this article, and make sure to turn off the “Temporary” flag unless you want the user to have to change his password the first time he authenticates.

Now proceed to the “Role Mappings” tab and assign the role “user”:

We are done for now with the Keycloak server configuration and we can start building our Spring Boot App!

创建客户端,角色以及用户

现在我们需要定义一个客户端,这就会是我们的 Spring Boot 应用程序。转到“Clients”部分,然后单击“Create”按钮。我们把这个客户端叫做“product-app”:

在接下来的界面上,我们可以让大部分东西都保留默认设置,只需要输入一个有效的重定向 URL,让 Keycloak 将其用于用户进行了身份验证之后。这里我们输入这个 URL:“http:// localhost:8081 / *”。

不要忘了把这些配置保存下来哦!

现在,我们要把需要分配给用户的角色定义好。创建一个名为“user”的简单角色:

最后,我们得创建一个用户。这里只需要用户名属性就可以了,这里我们就叫他“testuser”吧:

最后,我们需要设置一下凭据,所以要转到用户的“Credencials(凭据)”选项卡界面,并选择一个密码。我会在本文的余下部分使用“password”,还得确保把“Temporary(临时)”这个标志关闭了,除非你是想要用户可以在首次认证时更改密码。

现在转到“Role Mappings(角色映射)”选项卡界面来分配“user”这个角色:

现在我们就完成了 Keycloak 服务器的配置,可以开始构建 Spring Boot App 了!

Creating a Simple App

Let’s create a simple Spring Boot application. You might want to use the Spring Initializr and choose the following options:

  • Web

  • Freemarker

  • Keycloak

Name your app “product-app” and download the generated project:

Import the application in your favorite IDE, I will be using IntelliJ.

Our app will be simple and will contain only 2 pages:

  • An index.html which will be the landing page containing just a link to the product page.

  • Products.ftl which will be our product page template and will only be accessible by authenticated users.

Let’s start by creating a simple index.html file in “/src/resources/static”:

<html>

 <head>
   <title>My awesome landing page</title>
 </head>

 <body>
   <h1>Landing page</h1> <a href="/products">My products</a>
 </body>

</html>

Now we need a controller:

@Controller
class ProductController {

  @Autowired ProductService productService;

  @GetMapping(path = "/products")
  public String getProducts(Model model){
     model.addAttribute("products", productService.getProducts());
     return "products";
  }

  @GetMapping(path = "/logout")
  public String logout(HttpServletRequest request) throws ServletException {
     request.logout();
     return "/";
  }
}

As you can see, it’s simple; we define a mapping for the product page and one for the logout action. You will also notice that we are calling a “ProductService” that will return a list of strings that we will put in our Spring MVC Model object, so let’s create that service:

@Component
class ProductService {
  public List<String> getProducts() {
     return Arrays.asList("iPad","iPod","iPhone");
  }
}

We also need to create the product.ftl template. Create this file in “src/resources/templates”:

<#import "/spring.ftl" as spring>
   <html>
   <h1>My products</h1>
   <ul>
       <#list products as product>
           <li>${product}</li>
       </#list>
   </ul>
   <p> <a href="/logout">Logout</a> </p>

   </html>

Here we simply iterate through the list of products that are in our Spring MVC Model object and we add a link to log out from our application.

All that is the left to do is to add some keycloak properties in our application.properties.

创建一个简单的 App

让我们来创建一个简单的 Spring Boot App。你也许会想要使用 Spring Initializrr,这时候要把如下选项给选上:

  • Web

  • Freemarker

  • Keycloak

将你的 App 命名为“product-app”,然后把生成的工程下载下来:

将应用程序导入你喜欢使用的 IDE 里面,这里我会使用 IntelliJ。

我们的应用程序会比较简单,只包含两个页面:

  • 一个 index.html,它将是登录页面,里面只包含产品页面的链接。

  • Products.ftl,它将是我们的产品页面的模板,只能被通过了身份验证的用户访问到。

首先我们在“/src/resources/static”目录中创建一个简单的 index.html 文件:

<html>

 <head>
   <title>My awesome landing page</title>
 </head>

 <body>
   <h1>Landing page</h1> <a href="/products">My products</a>
 </body>

</html>

现在,我们需要一个控制器:

@Controller
class ProductController {

  @Autowired ProductService productService;

  @GetMapping(path = "/products")
  public String getProducts(Model model){
     model.addAttribute("products", productService.getProducts());
     return "products";
  }

  @GetMapping(path = "/logout")
  public String logout(HttpServletRequest request) throws ServletException {
     request.logout();
     return "/";
  }
}

你会发现这很简单,就是定义了产品页面的映射,然后再为注销操作定义一个映射。你还会注意到,我们调用了一个“ProductService”,它会返回一个字符串列表,我们把这个列表放到 Spring MVC Model 对象里面去,所以我们要创建这个服务:

@Component
class ProductService {
  public List<String> getProducts() {
     return Arrays.asList("iPad","iPod","iPhone");
  }
}

我们还需要创建 product.ftl 模板。要在“src/resources/templates”中创建此文件:

<#import "/spring.ftl" as spring>
   <html>
   <h1>My products</h1>
   <ul>
       <#list products as product>
           <li>${product}</li>
       </#list>
   </ul>
   <p> <a href="/logout">Logout</a> </p>

   </html>

在这里,我们简单地遍历了 Spring MVC Model 对象中的产品列表,并添加一个从我们的应用程序中注销的链接。

我们要做的就是向 application.properties 中添加一些 keycloak 相关的属性。

Defining Keycloak’s Configuration

Some properties are mandatory:

keycloak.auth-server-url=http://localhost:8080/auth keycloak.realm=springboot keycloak.public-client=true keycloak.resource=product-app

Then we need to define some Security constraints, as you will do with a Java EE app in your web.xml:

keycloak.security-constraints[0].authRoles[0]=user keycloak.security-constraints[0].securityCollections[0].patterns[0]=/products/*

Here, we simply define that every request to /products/* should be done with an authenticated user and that this user should have the role “user.”

Now, we just need one last property to make sure our application will be running on port 8081:

server.port=8081

We are all set and we can run our app!

You have several options to run your Spring Boot application. With Maven, you can simply do:

mvn clean spring-boot:run

Now browse to “http://localhost:8080” and you should see the landing page. Click the “products” links and you will be redirected to the Keycloak login page:

Login with our user “testuser/password” and you should be redirected back to your product page:

Congratulations! You have secured your first Spring Boot app with Keycloak. Now Log out and go back to the Keycloak administration console and discover how you can “tune” your login page. For instance, you can activate the “Remember Me” and the “User Registration” functions. To do this, hit the save button and go back to your login screen. There, you will see that these features have been added.

定义 Keycloak 的配置

一些属性是必须要有的:

keycloak.auth-server-url=http://localhost:8080/auth keycloak.realm=springboot keycloak.public-client=true keycloak.resource=product-app

我们需要定义一些安全方面的约束,就像你在 web.xml 中使用 Java EE 应用的时候要进行的配置一样:

keycloak.security-constraints[0].authRoles[0]=user keycloak.security-constraints[0].securityCollections[0].patterns[0]=/products/*

在这里,我们简单地定义每个向 /products/* 发起的请求都应该通过用户验证,而且该用户得有“user”这个角色。

现在,我们只需要配置最后一个属性来确保我们的应用程序将会在端口8081上运行:

server.port=8081

这样我们就都设置好了,可以把应用程序运行起来了!

要运行这个 Spring Boot 应用程序,有很多方式可以选择。使用 Maven 的话,你可以简单地像下面这样做就行了:

mvn clean spring-boot:run

现在导航到“http//localhost8080”,你应该就可以看到登录页面你了。点击“products”链接,会被重定向到 Keycloak 登录页面:

使用我们的用户“testuser/password”进行登录,应该会重定向到产品页面:

恭喜哦!现在你已经使用 Keycloak 为你的第一个 Spring Boot 应用程序加上了防护措施。现在注销并返回到 Keycloak 管理员控制台,你就会知道如何去“调整”登录页面。例如,您可以启用“Remember Me(记住我)”和“User Registration(用户注册)”功能。为此,请点击保存按钮并返回到登录界面。在那里你会这些功能已经添加上了。

Introducing Spring Security Support

If you’re a Spring user and have been playing around with security, there is a big chance that you have been using Spring Security. Well, I have some good news: we also have a Keycloak Spring Security Adapter and it’s already included in our Spring Boot Keycloak Starter.

Let’s see how we can leverage Spring Security together with Keycloak.

引入 Spring Security 支持

如果你是 Spring 用户而且一直在玩安全方面的东西的话,那么很有可能就会要用到 Spring Security。而我这里有一个好消息:我们还有一个 Keycloak Spring Security Adapter,而且它已经被包含在我们的 Spring Boot Keycloak Starter 中了。

我们来看看如何将 Spring Security 和 Keycloak 放到一起使用。

Adding Spring Security Starter

First, we need the Spring Security libraries. The easiest way to do that is to add the spring-boot-starter-security artifact in your pom.xml:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Creating a SecurityConfig Class

Like any other project that is secured with Spring Security, a configuration class extending WebSecurityConfigurerAdapter is needed. Keycloak provides its own subclass that you can again subclass:

@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter
{
  /**
   * Registers the KeycloakAuthenticationProvider with the authentication manager.
   */
  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
     KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
     keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
     auth.authenticationProvider(keycloakAuthenticationProvider);
  }

  @Bean
  public KeycloakConfigResolver KeycloakConfigResolver() {
     return new KeycloakSpringBootConfigResolver();
  }

  /**
   * Defines the session authentication strategy.
   */
  @Bean
  @Override
  protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
     return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception
  {
     super.configure(http);
     http
           .authorizeRequests()
           .antMatchers("/products*").hasRole("user")
           .anyRequest().permitAll();
  }
}

Let’s have a closer look at the most important methods:

  • configureGlobal: Here we change the Granted Authority Mapper. By default in Spring Security, roles are prefixed with ROLE_. We could change that in our Realm configuration but it could be confusing for other applications that do not know this convention, so here we assign a SimpleAuthorityMapper that will make sure no prefix is added.

  • keycloakConfigResolver: By default, the Keycloak Spring Security Adapter will look for a file named keycloak.json that's present on your classpath. But here we want to leverage the Spring Boot properties file support.

  • configure: Here is where we define our security constraints. This is pretty simple to understand. All we do is secure the path “/products” with the role “user.”

添加 Spring Security Starter

首先,我们需要 Spring Security 的库。最容易的方法就是将 spring-boot-starter-security  的 artifact 添加到你的 pom.xml 中:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>

创建一个 SecurityConfig 类

同其它的得到 Spring Security 防护的应用程序一样, 这里也需要一个扩展自 WebSecurityConfigurerAdapter 的配置类。Keycloak 提供了它自己的一个子类来给你进行再次继承:

@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter
{
  /**
   * Registers the KeycloakAuthenticationProvider with the authentication manager.
   */
  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
     KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
     keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
     auth.authenticationProvider(keycloakAuthenticationProvider);
  }

  @Bean
  public KeycloakConfigResolver KeycloakConfigResolver() {
     return new KeycloakSpringBootConfigResolver();
  }

  /**
   * Defines the session authentication strategy.
   */
  @Bean
  @Override
  protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
     return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception
  {
     super.configure(http);
     http
           .authorizeRequests()
           .antMatchers("/products*").hasRole("user")
           .anyRequest().permitAll();
  }
}

让我们来仔细看看最重要的几个方法:

  • configureGlobal: 这里我们修改 Granted Authority Mapper。在 Spring Security 中角色都默认带上了前缀 ROLE_。我们可以在我们的 Realm 配置中把这个改掉,不过这样做会让其它不知道这个约定的应用程序感到困惑, 所以这里我们分配了一个 SimpleAuthorityMapper 来确保不会有前缀被加上去。

  • keycloakConfigResolver: Keycloak Spring Security Adapter 默认会从你的 classpath 中找一个叫做 keycloak.json 的文件。不过这里我们并不像要利用上 Spring Boot 的属性文件支持。

  • configure: 这就是我们定义安全限制的地方。这个相当容易理解。我们要做的就是把带有“user” 角色的路径 “/products” 给保护起来。

Now we can remove the security constraints that we had defined previously in our application.properties file and add another property to map the Principal name with our Keycloak username:

keycloak.principal-attribute=preferred_username

Now we can even inject the principal in our controller method and put the username in the Spring MVC model:

@GetMapping(path = "/products")
public String getProducts(Principal principal, Model model){
  model.addAttribute("principal",principal);
  model.addAttribute("products", productService.getProducts());
  return "products";
}

Finally, we update the product.ftl template to print out the username:

<#import "/spring.ftl" as spring>
   <html>
   <h1>Hello ${principal.getName()}</h1>
   <ul>
       <#list products as product>
           <li>${product}</li>
       </#list>
   </ul>
   <p> <a href="/logout">Logout</a> </p>

   </html>

Restart your app, authenticate again, and it should still work and you should also able to see your username printed on the product page:

现在我们可以在我们的 application.properties 文件中将之前已经定义的安全限制给去掉了,然后添加另外一个属性来将我们的 KeyCloak 用户名映射到 Principal 名称上去:

keycloak.principal-attribute=preferred_username

现在我们设置可以将规则诸如到我们的控制器方法中去,并且将用户名放到 Spring 的 MVC model 中:

@GetMapping(path = "/products")
public String getProducts(Principal principal, Model model){
  model.addAttribute("principal",principal);
  model.addAttribute("products", productService.getProducts());
  return "products";
}

最后,我们可以更新 product.ftl 模板,来把用户名打印出来:

<#import "/spring.ftl" as spring>
   <html>
   <h1>Hello ${principal.getName()}</h1>
   <ul>
       <#list products as product>
           <li>${product}</li>
       </#list>
   </ul>
   <p> <a href="/logout">Logout</a> </p>

   </html>

再一次重启你的 App,可以看到它仍然可以运行起来,你也应该能看到你的用户打印在了产品页面之上:

返回顶部
顶部