1. 概述

Keycloak 是一个开源的身份与访问管理服务器,专门用于保护现代应用(如单页应用、移动应用、API等)。Keycloak支持行业标准协议,包括SAML 2.0、单点登录(SSO)和OpenID Connect(OIDC)

本教程将演示如何利用Keycloak通过OIDC(OpenID Connect)协议对SOAP Web服务进行身份认证和授权。

2. 开发SOAP Web服务

2.1. 服务操作定义

直接定义两个核心操作:

  • getProductDetails:根据产品ID返回产品详情。仅允许拥有user角色的用户调用。
  • deleteProduct:根据产品ID删除产品。仅允许拥有admin角色的用户调用。

我们实现了基于角色的访问控制(RBAC)模型。

2.2. 定义XSD

首先创建product.xsd文件:

<xs:element name="getProductDetailsRequest">
    ...
</xs:element>
<xs:element name="deleteProductRequest">
    ...
</xs:element>
    ...
</xs:schema>

添加必要依赖:

<dependency>
    <groupId>wsdl4j</groupId>
    <artifactId>wsdl4j</artifactId>
    <version>1.6.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web-services</artifactId>
    <version>2.7.5</version>
</dependency>

2.3. 实现Web服务

开发SOAP服务端点:

@PayloadRoot(namespace = "http://www.baeldung.com/springbootsoap/keycloak", localPart = "getProductDetailsRequest")
@ResponsePayload
public GetProductDetailsResponse getProductDetails(@RequestPayload GetProductDetailsRequest request) {
    ...
}
@PayloadRoot(namespace = "http://www.baeldung.com/springbootsoap/keycloak", localPart = "deleteProductRequest")
@ResponsePayload
public DeleteProductResponse deleteProduct(@RequestPayload DeleteProductRequest request) {
    ...
}

可用cURL、Postman或SOAPUI等工具测试。接下来我们将为服务添加安全防护。

3. 配置Keycloak

3.1. 创建Realm和Client

  1. 创建名为baeldung-soap-services的Realm
  2. 在该Realm下创建同名Client

Client即需要Keycloak认证服务的应用。创建时注意:

  • 应用URL作为根URL
  • 客户端协议选择openid-connect
  • 访问类型设为Confidential
  • 启用Authorization Enabled

启用Service Accounts后,应用可通过客户端凭证模式(Client Credentials Grant)认证:

Client Credentials Grant authentication flow

保存后进入Credentials标签页,记录secret值(后续配置需要)。

3.2. 用户和角色配置

创建adminuser两种角色。Keycloak支持两种角色类型:

  • Realm Roles(全局角色)
  • Client Roles(客户端角色)

这里使用Client Roles:

  1. 进入Clients选择目标客户端
  2. Roles标签页创建adminuser角色

Clients

虽然Keycloak支持从LDAP/AD导入用户,但为简化演示,我们手动创建用户并分配角色:

  1. 创建两个用户(Users > Add UserAdd User

  2. 为用户分配角色:

    • 选择用户 > Edit > Role Mappings
    • Assign role > Filter by clients选择角色
    • 为一个用户分配admin角色,另一个分配user角色

admin role

4. Spring Boot安全配置

4.1. 集成Keycloak

将身份认证委托给Keycloak服务器,添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

4.2. 启用方法级安全

两种安全约束方案:

  1. 在配置文件中声明security-constraints
  2. 使用@EnableGlobalMethodSecurity注解

SOAP服务推荐使用方法级安全,因为:

  • security-constraints缺乏细粒度控制
  • 配置方式过于繁琐

4.3. 定义KeycloakSecurityConfig

@Configuration
@EnableWebSecurity
@ConditionalOnProperty(name = "keycloak.enabled", havingValue = "true")
@EnableMethodSecurity(jsr250Enabled = true)
public class KeycloakSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable)
            .disable()
            .authorizeHttpRequests(auth -> auth.anyRequest()
                .authenticated())
            .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));
        return http.build();
    }
}

该配置会验证所有请求中的JWT令牌。

4.4. 添加授权控制

使用@RolesAllowed注解(JSR-250标准)实现方法级授权:

@RolesAllowed("user")
@PayloadRoot(namespace = "http://www.baeldung.com/springbootsoap/keycloak", localPart = "getProductDetailsRequest")
@ResponsePayload
public GetProductDetailsResponse getProductDetails(@RequestPayload GetProductDetailsRequest request) {
    ...
}
@RolesAllowed("admin")
@PayloadRoot(namespace = "http://www.baeldung.com/springbootsoap/keycloak", localPart = "deleteProductRequest")
@ResponsePayload
public DeleteProductResponse deleteProduct(@RequestPayload DeleteProductRequest request) {
    ...
}

至此安全配置完成。

5. 应用测试

5.1. 验证基础配置

使用cURL测试未认证请求:

curl -d @request.xml -i -o -X POST --header 'Content-Type: text/xml' http://localhost:18080/ws/api/v1

预期返回403错误:

<SOAP-ENV:Fault>
    <faultcode>SOAP-ENV:Server</faultcode>
    <faultstring xml:lang="en">Access is denied</faultstring>
</SOAP-ENV:Fault>

符合预期:无令牌请求被拒绝。

5.2. 获取访问令牌

认证流程: Acquiring Access Token

  1. 用户提交凭证
  2. 应用携带client-id/client-secret转发凭证至Keycloak
  3. Keycloak返回访问令牌和刷新令牌

Keycloak提供token接口获取令牌,格式为: <PROTOCOL>://<HOST>:<PORT>/realms/<REALM_NAME>/protocol/openid-connect/token

示例: http://localhost:8080/realms/baeldung-soap-services/protocol/openid-connect/token

获取令牌命令:

curl -L -X POST 'http://localhost:8080/realms/baeldung-soap-services/protocol/openid-connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_id=baeldung-soap-services' \
--data-urlencode 'client_secret=14da6f9e-261f-489a-9bf0-1441e4a9ddc4' \
--data-urlencode 'username=janedoe' \
--data-urlencode 'password=password'

响应示例:

{
    "access_token": "eyJh ...",
    "expires_in": 300,
    "refresh_expires_in": 1800,
    "refresh_token": "eyJh ...",
    "token_type": "Bearer",
    "not-before-policy": 0,
    "session_state": "364b8f3e-34ff-4ca0-8895-bfbdb8b466d4",
    "scope": "profile email"
}

expires_in字段控制令牌有效期(示例中为5分钟)。

5.3. 携带令牌调用服务

使用Bearer Token调用服务:

curl -d @request.xml -i -o -X POST -H 'Authorization: Bearer BwcYg94bGV9TLKH8i2Q' \
  -H 'Content-Type: text/xml' http://localhost:18080/ws/api/v1

成功响应:

<ns2:getProductDetailsResponse xmlns:ns2="http://www.baeldung.com/springbootsoap/keycloak">
    <ns2:product>
        <ns2:id>1</ns2:id>
            ...
        </ns2:product>
</ns2:getProductDetailsResponse>

5.4. 授权验证测试

使用user角色令牌(用户janedoe)尝试调用admin操作:

curl -d @request.xml -i -o -X POST -H 'Authorization: Bearer sSgGNZ3KbMMTQ' -H 'Content-Type: text/xml' \
  http://localhost:18080/ws/api/v1

请求体request.xml

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:key="http://www.baeldung.com/springbootsoap/keycloak">
    <soapenv:Header/>
    <soapenv:Body>
        <key:deleteProductRequest>
            <key:id>1</key:id>
        </key:deleteProductRequest>
   </soapenv:Body>
</soapenv:Envelope>

因权限不足返回403:

<SOAP-ENV:Fault>
    <faultcode>SOAP-ENV:Server</faultcode>
        <faultstring xml:lang="en">Access is denied</faultstring>
</SOAP-ENV:Fault>

6. 总结

本教程演示了:

  1. SOAP服务开发
  2. Keycloak配置
  3. 服务安全加固

保护SOAP服务与REST服务同样重要,需防范未授权访问。完整代码见GitHub仓库


原始标题:Securing SOAP Web Services With Keycloak | Baeldung