首页 > 解决方案 > Spring WebFlux 安全 OAuth 2.0 用户服务

问题描述

为了提供一些上下文,我目前正在从标准 Spring Security 5 (with spring-boot-starter-web) 迁移到spring-webflux- 与 Spring Cloud Gateway 一起用作我的 API Gateway - 它不支持spring-boot-starter-web依赖项。

除了我的 OAuth 2.0 用户服务(我在之前的实现中拥有)之外,我还可以使用 Spring Reactor/Webflux 进行其他所有工作。所以我已经复制了用户服务——除了我现在无法从新的安全配置中引用它。

这是我的新安全配置:

@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {

  private final OAuth2SuccessHandler oAuth2SuccessHandler;
  private final OAuth2FailureHandler oAuth2FailureHandler;
  private final OAuth2UserService customOAuth2UserService;

  public SecurityConfig(OAuth2SuccessHandler oAuth2SuccessHandler, OAuth2FailureHandler oAuth2FailureHandler, OAuth2UserService customOAuth2UserService) {
    this.oAuth2SuccessHandler = oAuth2SuccessHandler;
    this.oAuth2FailureHandler = oAuth2FailureHandler;
    this.customOAuth2UserService = customOAuth2UserService;
  }

  @Bean
  public TokenAuthenticationFilter tokenAuthenticationFilter() {
    return new TokenAuthenticationFilter();
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Bean
  public ReactiveAuthenticationManager reactiveAuthenticationManager(
          UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
    UserDetailsRepositoryReactiveAuthenticationManager authenticationManager =
        new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService);
    authenticationManager.setPasswordEncoder(passwordEncoder);

    return authenticationManager;
  }

  @Bean
  public SecurityWebFilterChain springWebFilterChain(
      ServerHttpSecurity http) {
    http
            .requestCache()
            .requestCache(NoOpServerRequestCache.getInstance())
            .and()
            .securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
            .authorizeExchange()
            .pathMatchers(
                    "/",
                    "/error",
                    "/favicon.ico",
                    "/*/*.png",
                    "/*/*.gif",
                    "/*/*.svg",
                    "/*/*.jpg",
                    "/*/*.html",
                    "/*/*.css",
                    "/*/*.js")
            .permitAll()
            .pathMatchers("/login/*", "/auth/*", "/oauth2/*")
            .permitAll()
            .anyExchange()
            .authenticated()
            .and()
            .oauth2Login()
            .authenticationSuccessHandler(oAuth2SuccessHandler)
            .authenticationFailureHandler(oAuth2FailureHandler)
            .and()
            .formLogin()
            .disable()
            .exceptionHandling()
            .authenticationEntryPoint(new AuthenticationEntryPoint())
            .and()
            .oauth2Client();

    http.addFilterBefore(tokenAuthenticationFilter(), SecurityWebFiltersOrder.AUTHENTICATION);

    return http.build();
  }
}

这是我的 OAuth 2.0 自定义用户服务

@Service
public class OAuth2UserService extends DefaultOAuth2UserService {

  private final UserDao userDao;

  public OAuth2UserService(UserDao userDao) {
    this.userDao = userDao;
  }

  @Override
  public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
    OAuth2User oAuth2User = super.loadUser(userRequest);

    OAuth2UserInfo oAuth2UserInfo =
            OAuth2UserInfoFactory.getOAuth2UserInfo(
                    userRequest.getClientRegistration().getRegistrationId(), oAuth2User.getAttributes());

    if (StringUtils.isEmpty(oAuth2UserInfo.getEmail())) {
      throw new OAuth2AuthenticationProcessingException("Email not found from any providers");
    }

    /* Find the user by email */
    User user = userDao.findUserByEmail(oAuth2UserInfo.getEmail());

    try {
      if (user == null) throw new OAuth2AuthenticationProcessingException("You must sign up!");

      if (!user.getProvider().equals(AuthProvider.valueOf(userRequest.getClientRegistration().getRegistrationId()))) {
        throw new OAuth2AuthenticationProcessingException(
            "Woah! Looks like you're already signed up with your "
                + user.getProvider().getValue()
                + ". Please use your "
                + user.getProvider().getValue()
                + " account to login.");
      }

      user = registerNewUser(userRequest, oAuth2UserInfo);
    } catch (Exception e) {

    }

    return UserPrincipal.create(user, oAuth2User.getAttributes());
  }

  private User registerNewUser(OAuth2UserRequest oAuth2UserRequest, OAuth2UserInfo oAuth2UserInfo) {
    User user = new User();

    user.setProvider(
            AuthProvider.valueOf(oAuth2UserRequest.getClientRegistration().getRegistrationId()));
    user.setProviderId(oAuth2UserInfo.getId());
    user.setUsername(oAuth2UserInfo.getName());
    user.setEmail(oAuth2UserInfo.getEmail());
    user.setProfilePicture(oAuth2UserInfo.getImageUrl());

    return userDao.save(user);
  }
}

那和我的旧配置之间的唯一区别是我可以使用它

http
    .userInfoEndpoint()
    .userService(customOAuth2UserService)

但是,没有选项可以将其与新的安全配置选项(我知道)一起使用 - 所以如果您知道将 Spring Security 指向我的自定义用户服务的方法,请回答。

非常感谢

标签: javaspringspring-webfluxspring-security-oauth2

解决方案


您的服务 bean 用于常规 OAuth 流。您想要的实际是 WebFlux 之一,即ReactiveOAuth2UserService.

您可以通过在ReactiveOAuth2UserService::loadUser方法上设置断点然后正常进行身份验证来自己验证这一点。


推荐阅读