Ben Neighbour
Ben Neighbour

Reputation: 75

Spring WebFlux Security OAuth 2.0 User Service

To give some context, I am currently migrating from standard Spring Security 5 (with spring-boot-starter-web) to spring-webflux - being used with the Spring Cloud Gateway as my API Gateway - which doesn't support spring-boot-starter-web dependencies.

I have got everything else working with Spring Reactor/Webflux - apart from my OAuth 2.0 User Service (that I had in the previous implementation). So I have copied the user service over - except I am now unable to reference that from the new Security Configuration.

Here's my new Security Configuration:

@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();
  }
}

Here's my OAuth 2.0 Custom User Service

@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);
  }
}

the only difference between that and my old configuration was that I could use this

http
    .userInfoEndpoint()
    .userService(customOAuth2UserService)

however, there is no option to use this with the New Security Configuration Options (that I am aware of) - so please answer if you know a way of pointing Spring Security to my Custom User Service.

Many Thanks

Upvotes: 0

Views: 1802

Answers (1)

M. L.
M. L.

Reputation: 46

Your Service bean is for the regular OAuth flow. The actual one you want is the WebFlux one, which is ReactiveOAuth2UserService.

You can verify this for yourself by setting a breakpoint on the ReactiveOAuth2UserService::loadUser method and then authenticating normally.

Upvotes: 2

Related Questions