Docker部署Spring Security集成Keycloak登录报Invalid credentials错误
容器化部署后触发Invalid credentials错误,核心原因是Keycloak内外访问地址混用导致JWT签发者(iss)校验不通过,同时存在配置冗余、回调地址不匹配问题:
- 现有配置同时使用Docker内部域名
http://keycloak:8080(服务间通信)和宿主机映射地址http://127.0.0.1:8972(浏览器访问),未固定Keycloak前端地址时,Keycloak会根据请求携带的Host头生成JWT的iss字段和服务端点地址,直接导致网关、用户服务配置的预期issuer值和实际JWT内的iss值不一致,JWT合法性校验失败。 - 网关端口映射为宿主机80端口,Keycloak后台配置的客户端合法重定向地址未同步更新,会被Keycloak直接拦截请求。
- 作为资源服务的user-service冗余配置了网关专属的TokenRelay过滤器,无实际作用还可能引发安全上下文异常。
1. 修正Keycloak容器配置
在docker-compose.yml的keycloak服务下添加固定前端地址的环境变量,强制Keycloak在所有访问场景下返回的端点地址、JWT签发者均为浏览器可访问的宿主机地址,从根源避免issuer不一致问题:
keycloak: container_name: keycloak networks: - kicktipp-docker-network ports: - '8972:8080' - '8443:8443' environment: - KEYCLOAK_USER=${AUTHENTICATOR_USER} - KEYCLOAK_PASSWORD=${AUTHENTICATOR_PASSWORD} # 新增固定前端地址配置 - KEYCLOAK_FRONTEND_URL=http://127.0.0.1:8972/auth volumes: - keycloak:/opt/jboss/keycloak/standalone/data/ image: xxx/keycloak:14.0.0
2. 调整API网关配置
2.1 统一OAuth2端点地址
修改api-gateway的application.yml,将所有Keycloak相关地址统一为对外的宿主机地址,彻底消除内外域名混用问题:
spring: cloud: discovery: enabled: true gateway: default-filters: - TokenRelay discovery: locator: enabled: true lowerCaseServiceId: true security: oauth2: resourceserver: jwt: # 替换为统一对外地址 issuer-uri: http://127.0.0.1:8972/auth/realms/master client: provider: keycloak: # 替换为统一对外地址 token-uri: http://127.0.0.1:8972/auth/realms/master/protocol/openid-connect/token authorization-uri: http://127.0.0.1:8972/auth/realms/master/protocol/openid-connect/auth # 替换为统一对外地址 userinfo-uri: http://127.0.0.1:8972/auth/realms/master/protocol/openid-connect/userinfo user-name-attribute: preferred_username registration: keycloak-kicktipp-client: provider: keycloak client-id: ${AUTHENTICATOR_CLIENT_ID} client-secret: ${AUTHENTICATOR_CLIENT_SECRET} authorization-grant-type: authorization_code redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
2.2 补充容器网络连通性配置
在docker-compose.yml的api-gateway服务下添加地址映射,保证容器内部可以正常访问宿主机映射的Keycloak端口:
api-gateway: container_name: api-gateway environment: - AUTHENTICATOR_CLIENT_ID=${AUTHENTICATOR_CLIENT_ID} - AUTHENTICATOR_CLIENT_SECRET=${AUTHENTICATOR_CLIENT_SECRET} networks: - kicktipp-docker-network depends_on: - naming-server - keycloak ports: - '80:8765' # 新增地址映射,将容器内127.0.0.1指向宿主机 extra_hosts: - "127.0.0.1:host-gateway" restart: unless-stopped image: xxx/api-gateway:latest
3. 调整user-service配置
3.1 清理冗余配置,统一JWT校验地址
修改user-service的application.yml,删除不属于资源服务的网关过滤器配置,将JWT校验地址替换为和网关一致的对外地址:
eureka.client.serviceUrl.defaultZone=http://naming-server:8761/eureka # 替换为统一对外地址 spring.security.oauth2.resourceserver.jwt.issuer-uri=http://127.0.0.1:8972/auth/realms/master # 删除以下冗余配置,user-service不是网关服务,不需要TokenRelay过滤器 # spring.cloud.gateway.default-filters=TokenRelay
3.2 补充连通性配置
和api-gateway保持一致,在docker-compose.yml的user-service服务下添加地址映射,保证服务可以正常访问Keycloak拉取JWK公钥完成JWT校验:
user-service: container_name: user-service depends_on: - naming-server networks: - kicktipp-docker-network ports: - '8080:8080' # 新增地址映射 extra_hosts: - "127.0.0.1:host-gateway" image: xxx/user-service:latest
4. 修正Keycloak客户端配置
登录Keycloak管理后台,找到对应客户端keycloak-kicktipp-client修改以下配置:
- 在Valid Redirect URIs栏添加
http://127.0.0.1/*,适配网关映射到宿主机80端口后的回调地址(80为HTTP默认端口,地址中不需要额外带端口号) - 在Web Origins栏添加
http://127.0.0.1,避免前端跳转触发跨域拦截 - 确认客户端密钥值和环境变量传入的
AUTHENTICATOR_CLIENT_SECRET完全一致
所有配置修改完成后,重启所有相关容器,清空浏览器缓存后重新测试登录即可。后续如果需要替换为正式域名访问,只需要将上述配置中所有http://127.0.0.1:8972替换为实际域名,同步更新Keycloak的KEYCLOAK_FRONTEND_URL和客户端重定向、跨域配置即可。
内容的提问来源于stack exchange,提问作者roro

