You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 14:45:33