Spring Security结合Keycloak实现本地表单密码模式OAuth2认证
实现自定义表单登录对接Keycloak密码授权模式
1. 配置Keycloak客户端
- 在Keycloak控制台创建客户端,设置Access Type为
confidential - 在客户端的Credentials标签页记录客户端密钥(Client Secret)
- 在Advanced标签页的Grant Types中勾选
Password - 确保测试用户已创建并分配相应角色
2. 配置Spring Boot应用
在application.yml中添加OAuth2客户端配置:
spring: security: oauth2: client: registration: keycloak: client-id: 你的客户端ID client-secret: 你的客户端密钥 scope: openid,profile,email authorization-grant-type: password client-authentication-method: client_secret_post provider: keycloak: issuer-uri: http://你的Keycloak地址/auth/realms/你的Realm名称
3. 配置OAuth2授权客户端管理器
创建Bean用于处理密码模式的token请求:
@Bean public OAuth2AuthorizedClientManager authorizedClientManager( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientService authorizedClientService) { OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() .password() .build(); DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientService); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); return authorizedClientManager; }
4. 自定义表单登录过滤器
替换默认的用户名密码过滤器,对接Keycloak的密码授权接口:
@Component public class KeycloakPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private final OAuth2AuthorizedClientManager authorizedClientManager; public KeycloakPasswordAuthenticationFilter(AuthenticationManager authenticationManager, OAuth2AuthorizedClientManager authorizedClientManager) { super(authenticationManager); this.authorizedClientManager = authorizedClientManager; setFilterProcessesUrl("/login"); // 保持默认登录路径 } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { String username = obtainUsername(request); String password = obtainPassword(request); // 构建密码授权请求 OAuth2PasswordGrantRequest grantRequest = new OAuth2PasswordGrantRequest( ClientRegistrationRepositoryUtils.findClientRegistration( authorizedClientManager.getClientRegistrationRepository(), "keycloak"), OAuth2AuthorizationContext.withUsername(username) .password(password) .build()); // 从Keycloak获取授权客户端(包含token) OAuth2AuthorizedClient authorizedClient = authorizedClientManager.authorize(grantRequest); if (authorizedClient == null) { throw new BadCredentialsException("无法从Keycloak获取访问令牌"); } // 解析token中的角色并转换为Spring Security权限 Map<String, Object> claims = authorizedClient.getAccessToken().getClaims(); List<String> realmRoles = (List<String>) ((Map<?, ?>) claims.getOrDefault("realm_access", Collections.emptyMap())) .getOrDefault("roles", Collections.emptyList()); Set<GrantedAuthority> authorities = realmRoles.stream() .map(role -> new SimpleGrantedAuthority("ROLE_" + role)) .collect(Collectors.toSet()); // 构建OAuth2认证对象 OAuth2User oAuth2User = new DefaultOAuth2User(authorities, claims, "sub"); OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken( oAuth2User, authorities, "keycloak"); // 将认证对象存入安全上下文 SecurityContextHolder.getContext().setAuthentication(authenticationToken); return authenticationToken; } }
5. 配置SecurityFilterChain
替换默认的登录过滤器,启用自定义逻辑:
@Bean public SecurityFilterChain filterChain(HttpSecurity http, KeycloakPasswordAuthenticationFilter customFilter) throws Exception { http .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ) .formLogin(form -> form .loginPage("/login") // 若使用自定义登录页面,需配置路径并允许匿名访问 .permitAll() ) .addFilterAt(customFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); }
注意事项
- 确保Keycloak服务器地址和Realm配置正确,可通过
{issuer-uri}/.well-known/openid-configuration验证配置有效性 - 若需要自定义登录页面,需创建对应的视图并确保
/login路径可匿名访问 - 生产环境建议启用HTTPS,避免用户名密码明文传输
内容的提问来源于stack exchange,提问作者kingkong
相关产品推荐
相关产品推荐

