Keycloak集成Spring Boot报Invalid parameter: redirect_uri错误
Spring Boot集成Keycloak重定向URI错误排查
问题背景
基于Spring Boot与Keycloak搭建测试应用,基础环境配置如下:
- 应用地址:
http://localhost:80 - Keycloak地址:
http://localhost:8080 - 使用Realm:
webReport - 客户端ID:
testK - Keycloak端已配置有效重定向URI:
http://localhost:80/*
访问受保护接口http://localhost:80/api/admin时,页面返回Invalid parameter: redirect_uri错误。查看控制台日志,实际发起的授权请求为:
GET http://localhost:8080/realms/webReport/protocol/openid-connect/auth?response_type=code&client_id=my_webreport&state=***
请求携带的redirect_uri参数值为http://localhost/login/oauth2/code/keycloak,接口返回400 Bad Request状态码,参考公开方案调整后问题未解决。
现有项目配置
pom.xml依赖配置
<?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 http://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.5.0</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>org.example</groupId> <artifactId>testOfKeyCloack</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>15</maven.compiler.source> <maven.compiler.target>15</maven.compiler.target> <springboot.plugin.version>2.1.4.RELEASE</springboot.plugin.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${springboot.plugin.version}</version> </plugin> </plugins> </build> </project>
application.yml配置
server: port: "80" spring: application.name: ${APPLICATION_NAME:spring-security-keycloak-oauth} security: oauth2: client: provider: keycloak: issuer-uri: http://localhost:8080/realms/webReport registration: keycloak: client-id: testK
测试接口Controller
@RestController @RequestMapping("/api") public class SampleController { @GetMapping("/anonymous") public String getAnonymousInfo() { return "Anonymous"; } @GetMapping("/user") @PreAuthorize("hasRole('USER')") public String getUserInfo() { return "user info"; } @GetMapping("/admin") @PreAuthorize("hasRole('ADMIN')") public String getAdminInfo() { return "admin info"; } @GetMapping("/service") @PreAuthorize("hasRole('SERVICE')") public String getServiceInfo() { return "service info"; } @GetMapping("/me") public Object getMe() { final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); return authentication.getName(); } }
安全配置类
@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests(authorizeRequests -> authorizeRequests .antMatchers("/api/anonymous/**").permitAll() .anyRequest().authenticated()) .oauth2Login(oauth2Login -> oauth2Login .userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint .oidcUserService(this.oidcUserService()) ) ); } @Bean public OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() { final OidcUserService delegate = new OidcUserService(); return (userRequest) -> { OidcUser oidcUser = delegate.loadUser(userRequest); final Map<String, Object> claims = oidcUser.getClaims(); final JSONArray groups = (JSONArray) claims.get("groups"); final Set<GrantedAuthority> mappedAuthorities = groups.stream() .map(role -> new SimpleGrantedAuthority(("ROLE_" + role))) .collect(Collectors.toSet()); return new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo()); }; } }
排查步骤与修复方案
从日志能直接定位两个核心问题,按顺序修复即可:
客户端ID不匹配
配置的客户端ID是testK,但实际发起请求带的client_id是my_webreport,说明当前项目加载的OAuth2配置根本不是写在application.yml里的内容。先排查以下场景:- 项目resources目录下是否有其他
application.properties、application-*.yml配置文件写了keycloak客户端配置 - IDE启动配置、系统环境变量、启动命令参数里是否存在
SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KEYCLOAK_CLIENT_ID这类配置覆盖了yml里的值 - 排查是否有其他配置类手动注册了ClientRegistrationRepository的Bean,覆盖了自动配置的客户端信息
修复后确认实际发起授权请求时的client_id为testK。
- 项目resources目录下是否有其他
重定向URI不匹配
Keycloak配置的白名单是http://localhost:80/*,但Spring Security自动生成的回调地址是http://localhost/login/oauth2/code/keycloak,二者字符串不匹配——Keycloak对重定向URI做精确字符串匹配,不会自动识别80是HTTP默认端口做兼容,两个地址会被判定为不同。两种修复方式选一个就行:- 在Keycloak客户端的有效重定向URI列表里,新增
http://localhost/login/oauth2/code/keycloak - 在application.yml的keycloak注册配置下,强制指定带端口的回调地址,和现有白名单匹配:
spring: security: oauth2: client: registration: keycloak: client-id: testK redirect-uri: http://localhost:80/login/oauth2/code/keycloak
- 在Keycloak客户端的有效重定向URI列表里,新增
附带修复其他已知隐患
- 版本兼容问题:项目parent依赖是Spring Boot 2.5.0,但spring-boot-maven-plugin指定的是2.1.4.RELEASE,版本差距过大会导致配置解析、依赖加载异常,把插件版本改成和parent一致的2.5.0即可。
- 空指针隐患:当前角色映射逻辑直接强转
claims.get("groups")为JSONArray,如果用户没有分配角色/组,Keycloak不会返回groups字段,这里会直接抛空指针,改成非空获取:final JSONArray groups = (JSONArray) claims.getOrDefault("groups", new JSONArray()); - 确认Keycloak客户端配置里添加了Groups Mapper,把用户的组/角色信息放到id_token和userinfo的claims里,不然后续接口的角色校验会失效。
内容的提问来源于stack exchange,提问作者Axel HK
相关产品推荐
相关产品推荐

