如何用Spring Security+Keycloak同时保护浏览器请求与RESTful请求
统一防护浏览器请求与REST API的Spring Boot Security + Keycloak方案
我之前帮不少开发者解决过这个场景的问题,本质上你需要的是同一应用内兼容两种不同的认证范式:浏览器的交互式会话认证,以及API的无状态Token认证。不用纠结必须用单一客户端,Keycloak的多客户端设计刚好适配这个场景,配合Spring Security的分路径配置就能完美解决。
第一步:Keycloak客户端配置
先在Keycloak后台创建两个客户端,分别对应两种请求类型:
- 公共客户端(Public Client):给浏览器请求用,开启
Standard Flow(授权码模式),禁用Direct Access Grants,Valid Redirect URIs设为你的应用域名(比如http://localhost:8080/*),Web Origins设为*或对应域名。这个客户端不需要密钥,毕竟浏览器场景下密钥没法保密。 - 保密客户端(Confidential Client):给REST API用,开启
Service Accounts Enabled,如果需要密码模式获取Token就打开Direct Access Grants,Valid Redirect URIs可以留空,记得保存生成的客户端密钥——后续Spring配置要用到。
第二步:Spring Security分路径配置
核心思路是用HttpSecurity的antMatcher()为不同路径组配置独立的认证规则,完美隔离浏览器和API的安全逻辑。下面是完整的SecurityConfig.java示例:
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) // 支持方法级权限控制 public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter { @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { KeycloakAuthenticationProvider keycloakAuthProvider = keycloakAuthenticationProvider(); // 把Keycloak角色转换为Spring Security标准权限格式 keycloakAuthProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper()); auth.authenticationProvider(keycloakAuthProvider); } // 加载Spring Boot配置文件中的Keycloak参数 @Bean public KeycloakSpringBootConfigResolver keycloakConfigResolver() { return new KeycloakSpringBootConfigResolver(); } // 浏览器请求的会话管理策略 @Bean @Override protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); } @Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); // 1. 处理浏览器页面请求(比如非/api开头的路径) http.antMatcher("/**") .authorizeRequests() .antMatchers("/api/**").permitAll() // 先放行API路径,交给下面的规则处理 .anyRequest().authenticated() .and() .oauth2Login() // 用OAuth2授权码模式处理浏览器登录 .loginPage("/sso/login") // 可自定义登录页,也用Keycloak默认页 .defaultSuccessUrl("/home"); // 2. 处理REST API请求 http.antMatcher("/api/**") .authorizeRequests() .anyRequest().authenticated() .and() .oauth2ResourceServer() .jwt() // 用JWT承载者认证 .jwtAuthenticationConverter(jwtAuthenticationConverter()); // API场景关闭CSRF,浏览器场景Spring Security会自动处理 http.csrf().ignoringAntMatchers("/api/**"); // API设置无状态会话,认证失败直接返回401而非重定向 http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .exceptionHandling() .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)); } // 自定义JWT转换器,统一Keycloak角色与Spring Security权限格式 private JwtAuthenticationConverter jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter(); authoritiesConverter.setAuthorityPrefix("ROLE_"); authoritiesConverter.setAuthoritiesClaimName("roles"); JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter(); jwtConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter); return jwtConverter; } }
关键细节说明
- 路径隔离逻辑:用
antMatcher()分别匹配浏览器路径和API路径,各自配置认证逻辑——浏览器用oauth2Login对应公共客户端的授权码模式,API用oauth2ResourceServer.jwt对应保密客户端的Token认证。 - 会话策略差异:浏览器请求保留
RegisterSessionAuthenticationStrategy管理会话,API请求设置SessionCreationPolicy.STATELESS,完全无状态符合REST规范。 - 权限统一控制:通过转换器把Keycloak角色统一转换为Spring Security的权限格式,不管是浏览器还是API请求,都能共用
@PreAuthorize("hasRole('ADMIN')")这类方法级权限注解。 - 异常处理适配:API场景下配置
HttpStatusEntryPoint,未认证时直接返回401状态码,不会像浏览器那样重定向到登录页,符合API的交互逻辑。
额外注意事项
- CORS配置:如果API需要跨域访问,记得在Spring里配置CORS允许对应Origin,避免跨域拦截问题。
- API Token获取:前端调用API时,需要先从Keycloak获取Bearer Token(比如用密码模式或授权码模式),然后在请求头带上
Authorization: Bearer {token}。 - 配置文件对应:Spring配置文件里的
keycloak.client-id设为公共客户端ID,API的资源服务器会自动读取keycloak.resource(保密客户端ID)和keycloak.credentials.secret(保密客户端密钥)。
内容的提问来源于stack exchange,提问作者Jason Tian
相关产品推荐
相关产品推荐

