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

Spring Boot中基于Basic Auth实现OAuth2密码模式登录问题

问题描述

我有一个集成外部Keycloak的Spring Boot应用,配置了多套Http Security规则:

  • API接口采用Bearer token认证
  • Swagger使用授权码流程(需用户交互)
  • 部分接口计划通过OAuth2密码授权类型替代原Basic Auth认证(原Basic Auth配置未生效)

现有配置

application.properties中的OAuth2配置

spring.security.oauth2.client.registration.keycloak2.client-id=${KEYCLOAK_RESOURCE}
spring.security.oauth2.client.registration.keycloak2.client-secret=${KEYCLOAK_RESOURCE_CLIENT_SECRET}
spring.security.oauth2.client.registration.keycloak2.authorization-grant-type=password
spring.security.oauth2.client.registration.keycloak2.scope=openid
spring.security.oauth2.client.provider.keycloak2.issuer-uri=${keycloak.auth-server-url}/realms/${keycloak.realm}

Basic Auth端点的配置代码

@Configuration
@Order(2)
public static class ProcessConfigurationAdapter extends WebSecurityConfigurerAdapter {
    
    public static class OAuth2PasswordAuthenticationProvider implements AuthenticationProvider {

        private final OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient;
        
        private final OAuth2UserService<OAuth2UserRequest, OAuth2User> userService;
        
        private final ClientRegistrationRepository clientRegistrationRepository;
        
        private GrantedAuthoritiesMapper authoritiesMapper = ((authorities) -> authorities);

        public OAuth2PasswordAuthenticationProvider(
                OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient,
                OAuth2UserService<OAuth2UserRequest, OAuth2User> userService,
                ClientRegistrationRepository clientRegistrationRepository) {
            super();
            this.accessTokenResponseClient = accessTokenResponseClient;
            this.userService = userService;
            this.clientRegistrationRepository = clientRegistrationRepository;
        }

        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {

            if (!(authentication instanceof UsernamePasswordAuthenticationToken)) {
                return null;
            }

            final UsernamePasswordAuthenticationToken usernamePassword = (UsernamePasswordAuthenticationToken) authentication;

            final String username = (String) usernamePassword.getPrincipal();
            final String password = (String) usernamePassword.getCredentials();

            final String registrationId = "keycloak2";
            final ClientRegistration keycloak2 = clientRegistrationRepository.findByRegistrationId(registrationId);
            
            final OAuth2PasswordGrantRequest request = new OAuth2PasswordGrantRequest(keycloak2, username, password);
            final OAuth2AccessTokenResponse accessTokenResponse = accessTokenResponseClient.getTokenResponse(request);
            final OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest(
                    keycloak2, accessTokenResponse.getAccessToken(), accessTokenResponse.getAdditionalParameters()));
            final Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
                    .mapAuthorities(oauth2User.getAuthorities());
            final OAuth2AuthenticationToken authenticationResult = new OAuth2AuthenticationToken(oauth2User, mappedAuthorities, registrationId);
            return authenticationResult;
        }

        @Override
        public boolean supports(Class authentication) {
            return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
        }
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/api/v1/process/**")
            .authorizeRequests()
            .anyRequest()
            .authenticated()
            .and()
        .oauth2Client()
            .and()
        .httpBasic();
    
        http.csrf().disable();
    }
    
    @Bean
    public OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient() {
        return new DefaultPasswordTokenResponseClient();
    }
    
    @Bean
    public OAuth2PasswordAuthenticationProvider oAuth2PasswordAuthenticationProvider(
            OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient,
            OAuth2UserService<OAuth2UserRequest, OAuth2User> userService,
            ClientRegistrationRepository clientRegistrationRepository) {
        
        // Here I'm missing userService
        return new OAuth2PasswordAuthenticationProvider(accessTokenResponseClient, userService, clientRegistrationRepository);
    }
}

遇到的错误

Parameter 1 of method oAuth2PasswordAuthenticationProvider in com.example.config.SecurityConfig$ProcessConfigurationAdapter required a bean of type 'org.springframework.security.oauth2.client.userinfo.OAuth2UserService' that could not be found.

原本认为OAuth2UserService会根据配置自动装配,但未成功,请问如何获取这个Bean?


解决方案

Spring Security不会自动为密码授权类型创建OAuth2UserService的Bean,需要手动定义,以下两种方式可选:

方法1:使用默认实现

直接注入Spring提供的DefaultOAuth2UserService,它能处理标准的OAuth2用户信息请求:

@Bean
public OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
    return new DefaultOAuth2UserService();
}

方法2:自定义实现(适配Keycloak特性)

如果需要解析Keycloak返回的额外属性(如Realm角色、Client角色),可以扩展默认实现:

@Bean
public OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
    DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
    return request -> {
        OAuth2User oauth2User = delegate.loadUser(request);
        // 此处添加自定义逻辑,比如解析Keycloak的角色信息
        return oauth2User;
    };
}

额外配置要点

  1. 需在配置类中注册自定义认证提供者:
    在ProcessConfigurationAdapter中添加如下方法:
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(oAuth2PasswordAuthenticationProvider(
            accessTokenResponseClient(), 
            oauth2UserService(), 
            clientRegistrationRepository()
        ));
    }
    
  2. 确保Keycloak客户端配置中开启了密码授权类型,且客户端访问类型设置为confidential(因配置了client-secret)。

内容的提问来源于stack exchange,提问作者user1745656

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 08:56:06