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
- 创建名为
baeldung-soap-services
的Realm - 在该Realm下创建同名Client
Client即需要Keycloak认证服务的应用。创建时注意:
- 应用URL作为根URL
- 客户端协议选择
openid-connect
- 访问类型设为
Confidential
- 启用
Authorization Enabled
启用Service Accounts
后,应用可通过客户端凭证模式(Client Credentials Grant)认证:
保存后进入Credentials
标签页,记录secret
值(后续配置需要)。
3.2. 用户和角色配置
创建admin
和user
两种角色。Keycloak支持两种角色类型:
- Realm Roles(全局角色)
- Client Roles(客户端角色)
这里使用Client Roles:
- 进入
Clients
选择目标客户端 - 在
Roles
标签页创建admin
和user
角色
虽然Keycloak支持从LDAP/AD导入用户,但为简化演示,我们手动创建用户并分配角色:
为用户分配角色:
- 选择用户 >
Edit
>Role Mappings
Assign role
>Filter by clients
选择角色- 为一个用户分配
admin
角色,另一个分配user
角色
- 选择用户 >
4. Spring Boot安全配置
4.1. 集成Keycloak
将身份认证委托给Keycloak服务器,添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
4.2. 启用方法级安全
两种安全约束方案:
- 在配置文件中声明
security-constraints
- 使用
@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. 获取访问令牌
- 用户提交凭证
- 应用携带
client-id
/client-secret
转发凭证至Keycloak - 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. 总结
本教程演示了:
- SOAP服务开发
- Keycloak配置
- 服务安全加固
保护SOAP服务与REST服务同样重要,需防范未授权访问。完整代码见GitHub仓库。