当前位置 博文首页 > Zhang_Xiang:Keycloak 13 自定义用户身份认证流程(User Storag
版本:13.0.0
spring-boot 项目 Github
user-storage-spi 项目 Github
Keycloak 是为现代应用程序和服务提供的一个开源的身份和访问管理的解决方案。
Keycloak 在测试环境可以使用内嵌数据库,生产环境需要重新配置数据库。以下将一一介绍如何使用内嵌数据库、重新配置数据库。
特别需要注意 Keycloak 是在 WildFly 上构建的。
启动容器
docker run -p 10010:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin quay.io/keycloak/keycloak:13.0.0
成功启动后访问:http://127.0.0.1:10010/
登录 Keycloak 服务
a. 点击 Administration Console
b. 输入账号:admin,密码:admin
c. 进入控制台界面
创建一个 realm 和用户以访问内置的账户管理控制台。把 realm 想像成租户的概念。
Master realm
—— 这个 realm 是初始化时创建的,它包含超级管理员。使用这个 realm 管理其它的 realm。Other realm
—— 超级管理员创建的 realm 。在这些 realm 里,超级管理员创建用户和应用程序,用户拥有应用程序。点击 Add realm
按钮
输入 realm 名称
点击 create
按钮完成创建
点击创建完成 demo realm 创建。
切换 realm 到 Demo
创建用户,Users -> Add User,输入 Username
,点击 Save
按钮
设置密码
http://127.0.0.1:10010/auth/realms/demo/account/
,点击 Sign In
按钮,使用新创建的用户 Zhang 登录应用程序或服务为了能使用 Keycloak,必须在 Keycloak 中注册一个客户端。可以通过超级管理员界面注册,客户端也可以自己通过 Keycloak 服务注册。
客户端注册服务提供内置支持:Keycloak Client Representations、OIDC 客户端元数据、SAML 实体描述。客户端注册服务地址是:/auth/realms/<realm>/clients-registrations/<provider>
内置的 provider
是
调用客户端注册服务需要令牌。令牌可以是 bearer 令牌,初始化访问令牌或者注册令牌。不需要令牌注册客户端也可以,但是需要配置客户端注册策略。
Bearer 令牌可以代表用户或者服务账户。调用端点需要以下权限:
如果使用 Bearer 令牌创建客户端,推荐使用来自服务账户(create-client
角色)的令牌。
推荐使用初始化访问令牌注册客户端。初始化访问令牌只能用于创建客户端,并且可以配置有效期,同时可以配置可以创建多少客户端。
初始化访问令牌可以通过超级管理员控制台创建。
点击保存,完成令牌的创建。
当点击保存以后会生成令牌,这个令牌如果忘记复制了,那么就只能重新创建了。使用 bearer 令牌:
Authorization: bearer eyJhbGciOiJSUz...
当通过客户端注册服务创建客户端时,返回值会包含一个注册访问令牌。注册访问令牌提供检索客户端配置、更新或者删除客户端的权限。注册访问令牌使用方式和 bear 令牌、初始化访问令牌的使用方式是一样的。注册访问令牌是一次性的,当它使用时,返回值里总会包含一个新的令牌。
如果客户端在客户端注册服务之外创建,注册访问令牌就不会和客户端关联起来了。但可以在超级管理员控制台生成注册访问令牌。
default
客户端注册提供商可以创建、检索、更新、删除客户端。使用 Keycloak Client Representation 转换提供配置客户端支持,就像在超级管理员控制台配置的一样。
创建 Client Representation(JSON)执行 HTTP POST 请求 /auth/realms/<realm>/clients-registrations/default
。
检索 Client Representation 执行 GET 请求/auth/realms/<realm>/clients-registrations/default/<client id>
。
它也会返回新的注册访问令牌。
要更新 Client Representation 执行 HTTP PUT 请求 /auth/realms/<realm>/clients-registrations/default/<client id>
。
它也会返回一个新的注册访问令牌。
要删除 Client Representation 执行 HTTP DELETE 请求 /auth/realms/<realm>/clients-registrations/default/<client id>
。
installation
客户端注册提供商可以用于为客户端获取适配器配置。除了令牌身份验证之外,还可以使用 HTTP basic 认证(通过客户端凭证)。使用下列请求头以完成 HTTP basic 认证:
Authorization: basic BASE64(client-id + ':' + client-secret)
要获取适配器配置执行 HTTP GET 请求:/auth/realms/<realm>/clients-registrations/install/<client id>
。
公共客户端不需要身份认证。这意味着 JavaScript 适配器可以通过以上 URL 直接从 Keycloak 加载客户端配置。
终端在 Keycloak 中注册客户端 /auth/realms/<realm>/clients-registrations/openid-connect[/<client id>]
。
在 OIDC 发现中可以为 realm 找到终端,/auth/realms/<realm>/.well-known/openid-configuration
。
Keycloak 当前支持两种方式注册客户端(通过客户端注册服务)。
匿名客户端注册请求是非常有趣和强大的功能,任何人都可以注册客户端并且没有限制。因此提出了客户端注册策略 SPI,它提供了一个限制的方式(谁能注册,在什么条件下)。
在 Keycloak 超级管理员控制台中,你可以看到匿名请求策略配置和认证请求策略配置。
当前支持的策略:
Consent Allowed
开关是启动的。所以身份认证成功后,用户将看到准许会话(如果需要的话)。Client Scopes
白名单,用于新注册的客户端或者更新的客户端。Full Scope Allowed
开关是关闭的。意味这这些客户端没有任何 realm 角色或者客户端角色。客户端是用户请求身份认证的实体。客户端有两种格式。第一种是单点登录的(SSO)。另一种是类型是获取访问令牌然后代表用户访问服务。
客户端列表
添加客户端,Client ID
是客户端的身份标识。下一步选择客户端协议 openid-connect
。
Client ID
数据字母字符串,用于客户端身份识别(当 OIDC 请求时)。
Name
客户端名称。
Description
客户端描述。
Enabled
如果关闭,客户端将不允许请求验证。
Consent Required
如果打开,用户将得到一个准许页面(用于询问用户是否授权应用程序访问)。页面同时显示客户端要访问的元数据信息,用户可以看到客户端要访问的信息。
Access Type
OIDC 客户端类型。
Standard Flow Enabled
如果打开这个,客户端将使用 OIDC 授权码工作流。
Implicit Flow Enabled
如果打开这个,客户端将使用 OIDC 隐式工作流。
Direct Access Grants Enabled
如果打开这个,客户端将使用 OIDC 直接访问授权。
OAuth 2.0 Device Authorization Grant Enabled
如果打开这个,客户端将使用 OIDC 设备授权许可。
OpenID Connect Client Initiated Backchannel Authentication Grant Enabled
如果打开这个,客户端将使用 OIDC 客户端初始化后端渠道认证许可。
Root URL
如果 Keycloak 不使用任何相对 URL,这个值就是预留值。
Valid Redirect URIs
这个是必填字段。输入 URL 模版然后点击 + 号添加。点击 - 号移除 URL。记住,最后还要点击 Sava
按钮。通配符 * 只能用于 URI 的末端,例如:http://host.com/*
注册重定向 URL 模版时,你应该考虑避免被攻击。
Base URL
如果 Keycloak 要链接客户端,这个值需要设置。
Admin URL
为 Keycloak 指定客户端适配器,这个值是客户端回调终端。Keycloak 服务将使用这个 URI 回调(比如:推送取消策略、执行后端渠道退出登录、其它超级管理员操作)。对于 Keycloak servlet 适配器来说,这个值是 servlet 应用程序的 root URL。
Web Origins
这个设置是以 CORS 为中心。
保密客户端证书
如果客户端 access type
设置为 confidential
时,页面将会显示 Credentials
标签。注意,选择 Confidential
标签要保存以后才会能看到 Credentials
标签。
Client Authenticator
下拉框指定你的加密客户端证书类型。默认是 Client Id and Secret
。secret 自动生成,并且 Regenerate Secret
按钮可以重新生成 secret。
此外,可以选择 Signed Jwt
或者 X509 Certificate
验证代替 secret。
Signed JWT
当选择 Signed Jwt 类型时,你需要为客户端生成私钥和证书。私钥用于 JWT 签名,证书用于服务端验证签名。点击 Generate new keys and certificate
按钮生成私钥和证书。
也可以使用其它工具生成,然后导入。
新建 Spring-boot 项目
创建 Keycloak 客户端,导航到 http://127.0.0.1:10010/
,切换到 Demo
realm ,点击 Clients
菜单,点击 Create
按钮,创建一个 Demo
realm 下的客户端。客户端 ID 为:spring-boot-toy。选择 Access Type
为 confidential
。
创建角色 toy-admin,并给用户赋予角色
Spring 项目添加 Maven 引用
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.toy.keycloak</groupId>
<artifactId>toy-keycloak</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>toy-keycloak</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.keycloak.bom</groupId>
<artifactId>keycloak-adapter-bom</artifactId>
<version>13.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
暴露 Api 接口
package com.toy.keycloak.webapi;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Zhang_Xiang
* @since 2021/5/11 16:56:57
*/
@RestController
@RequestMapping("temp")
public class TempController {
@GetMapping("weather")
public String weather(){
return "晴天??";
}
}
配置
使用 Tomcat、Undertow、Jetty 不需要额外配置。在 application.properties
中配置 Keycloak 如下:
keycloak.realm=demo # realm 名称
keycloak.auth-server-url=http://127.0.0.1:10010/auth # Keycloak 基础服务地址
keycloak.ssl-required=external
keycloak.resource=spring-boot-toy # 应用程序客户端 ID
keycloak.credentials.secret=91437668-b8f8-425b-ba9d-38439115dfbc
keycloak.use-resource-role-mappings=true
keycloak.securityConstraints[0].authRoles[0]=toy-admin
keycloak.security-constraints[0].securityCollections[0].patterns[0]=/*
设置 keycloak.enabled = false
可以停用 Keycloak 。
启动 Spring 应用程序,并访问 http://localhost:8080/temp/weather
输入用户名 zhang
,密码123456
管理登录用户会话,用户登录后,进入 Keycloak 管理后台,切换到 Demo realm 可以看到目前登录的会话列表。
点击 Logout all
按钮,所有用户都将退出登录。
Keycloak 内嵌了 H2 内存数据库。Keycloak 默认使用 H2 持久化数据。H2 数据库不适用于高并发场景并且不适用于集群。
Keycloak 使用两层技术持久化关系数据。底层技术是 JDBC。JDBC 用于连接 RDBMS。每个数据库提供商都有不同的 JDBC 驱动。顶层技术用于持久化的是 Hibernate JPA。
使用用户存储 SPI 扩展 Keycloak 以连接外部用户数据和证书存储。当 Keycloak 运行时查找用户时,比如用户登录,Keycloak 执行几个步骤定位用户。首先看用户是在在用户缓存中,然后在本地数据库中查找,如果找不到,循环用户存储 SPI 执行查找。
用户存储 SPI 提供商实现打包和部署和 Java EE 组件相似。默认不启用组件,如果要启用需要在 Keycloak 超级管理员控制台界面中配置 User Feberation
。
用户存储提供商打包为 JAR 然后部署到 Keycloak 运行时或者从 Keycloak 运行时取消部署,就像 WildFly 应用程序服务部署服务一样。你也可以直接拷贝 JAR 包到 standalone/deployments/
服务器目录,或者使用 JBoss CLI 执行部署。
为了 Keycloak 能识别服务提供商,你需要添加一个文件到 JAR 包中:META-INF/services/org.keycloak.storage.UserStorageProviderFactory
。这个文件必须包含实现 UserStorageProviderFactory
类的全路径:
org.keycloak.examples.federation.properties.ClasspathPropertiesStorageFactory
org.keycloak.examples.federation.properties.FilePropertiesStorageFactory
使用已有用户数据库。
MysqlUserStorageProvider 实现了很多接口,实现 UserStorageProvider
是实现是 SPI 的基本要求,换句话说,不实现 UserStorageProvider
就不能实现 SPI,UserLookupProvider
用于实现从外部数据库查找用户以实现用户登录,UserQueryProvider
定义复杂查询以查找用户,CredentialInputValidator
验证不同的证书类型(比如:验证登录密码)。
provider
public class MysqlUserStorageProvider implements UserLookupProvider, UserQueryProvider, CredentialInputValidator, UserStorageProvider {
protected KeycloakSession session;
protected ComponentModel model;
public MysqlUserStorageProvider(KeycloakSession session, ComponentModel model) {
this.session = session;
this.model = model;
}
...
}
ProviderFactory
,这里的 ProviderConfigProperty
用于自定义标签,换句话说,在这里定义了标签以后,可以在 Keycloak 管理界面设置值,代码可以直接读取到这些值。添加自定义 SPI 时,要留意添加在什么 realm 下。
public class MysqlUserStorageProviderFactory implements UserStorageProviderFactory<MysqlUserStorageProvider> {
protected final List<ProviderConfigProperty> configMetadata;
public MysqlUserStorageProviderFactory() {
configMetadata = ProviderConfigurationBuilder.create()
.property()
.name(CONFIG_KEY_JDBC_DRIVER)
.label("JDBC Driver Class")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue("org.h2.Driver")
.helpText("Fully qualified class name of the JDBC driver")
.add()
.property()
.name(CONFIG_KEY_JDBC_URL)
.label("JDBC URL")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue("jdbc:h2:mem:customdb")
.helpText("JDBC URL used to connect to the user database")
.add()
.property()
.name(CONFIG_KEY_DB_USERNAME)
.label("Database User")
.type(ProviderConfigProperty.STRING_TYPE)
.helpText("Username used to connect to the database")
.add()
.property()
.name(CONFIG_KEY_DB_PASSWORD)
.label("Database Password")
.type(ProviderConfigProperty.STRING_TYPE)
.helpText("Password used to connect to the database")
.secret(true)
.add()
.property()
.name(CONFIG_KEY_VALIDATION_QUERY)
.label("SQL Validation Query")
.type(ProviderConfigProperty.STRING_TYPE)
.helpText("SQL query used to validate a connection")
.defaultValue("select 1")
.add()
.build();
}
@Override
public MysqlUserStorageProvider create(KeycloakSession session, ComponentModel model) {
return new MysqlUserStorageProvider(session, model);
}
@Override
public String getId() {
return "user-center-provider";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configMetadata;
}
@Override
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException {
try (Connection c = DbUtil.getConnection(config)) {
System.out.println(config.get(CONFIG_KEY_VALIDATION_QUERY));
c.createStatement().execute(config.get(CONFIG_KEY_VALIDATION_QUERY));
} catch (Exception ex) {
System.out.println(ex.getMessage());
throw new ComponentValidationException("Unable to validate database connection", ex);
}
}
}
查看用户列表
application.properties
keycloak.realm=demo
keycloak.auth-server-url=http://127.0.0.1:10010/auth
keycloak.ssl-required=external
keycloak.resource=spring-boot-toy
keycloak.credentials.secret=91437668-b8f8-425b-ba9d-38439115dfbc
keycloak.use-resource-role-mappings=true
keycloak.verify-token-audience=true
keycloak.securityConstraints[0].authRoles[0]=user
keycloak.security-constraints[0].securityCollections[0].patterns[0]=/temp/weather
all
,默认值是 external
,即外部请求需要 HTTPS,可选值是:all
、external
、none
。一个 Realm 有多个客户端,一个客户端有多个用户。在 Keycloak 有3种角色:
在客户端设置中启用 Service Accounts Enabled
,在 demo Realm 中添加 ordinary_user
角色。
在 Service Account Roles
标签中添加 ordinary_user
角色。
添加该角色以后,切换到 User
标签,可以看到用户中已经关联了角色。
添加角色限制 ordinary_user
keycloak.realm=demo
keycloak.auth-server-url=http://127.0.0.1:10010/auth
keycloak.ssl-required=external
keycloak.resource=spring-boot-toy
keycloak.credentials.secret=91437668-b8f8-425b-ba9d-38439115dfbc
keycloak.use-resource-role-mappings=true
keycloak.verify-token-audience=true
keycloak.securityConstraints[0].authRoles[0]=toy-admin
keycloak.security-constraints[0].auth-roles[1]=ordinary_user
keycloak.security-constraints[0].securityCollections[0].patterns[0]=/temp/weather
启动服务,访问 localhost:8080/temp/weather,输入自定义数据库中的用户、密码,以访问接口。