首页 > 解决方案 > 如何从 AuthenticationManager 调用的多个 AuthenticaionProviders 中选择特定的身份验证方法

问题描述

AuthenticaionProvider我的应用程序中有两个用于AWS Cognito对用户进行身份验证。

首先为Admin

@Component
@RequiredArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider {

  private final CognitoAuthenticationService cognitoService;
  private final AuthenticationHelper authenticationHelper;

  @SuppressWarnings("unchecked")
  @Override
  public Authentication authenticate(Authentication authentication) {
    AuthenticationRequest authenticationRequest;

    if (isNotNull(authentication)) {
      authenticationRequest = new AuthenticationRequest();
      Map<String, String> credentials = (Map<String, String>) authentication.getCredentials();
      authenticationRequest.setNewPassword(credentials.get(NEW_PASSWORD_KEY));
      authenticationRequest.setPassword(credentials.get(PASSWORD));
      authenticationRequest.setUsername(authentication.getName());

      SpringSecurityUser userAuthenticated = cognitoService.authenticate(authenticationRequest);
      if (isNotNull(userAuthenticated)) {

        Map<String, String> authenticatedCredentials =
            authenticationHelper.prepareAuthenticationResponse(userAuthenticated);
        return new UsernamePasswordAuthenticationToken(
            userAuthenticated.getUsername(),
            authenticatedCredentials,
            userAuthenticated.getAuthorities());
      } else {
        return null;
      }
    } else {
      throw new UsernameNotFoundException("No application user for given username");
    }
  }

  @Override
  public boolean supports(Class<?> authentication) {
    return authentication.equals(UsernamePasswordAuthenticationToken.class);
  }
}

第二个传统User

@Component
@RequiredArgsConstructor
public class UserAuthenticationProvider implements AuthenticationProvider {

  private final CognitoAuthenticationService cognitoAuthenticationService;
  private final AuthenticationHelper authenticationHelper;

  @SuppressWarnings("unchecked")
  @Override
  public Authentication authenticate(Authentication authentication) {

    if (isNotNull(authentication)) {
      AuthenticationRequest authenticationRequest = new AuthenticationRequest();
      Map<String, String> credentials = (Map<String, String>) authentication.getCredentials();
      authenticationRequest.setPassword(credentials.get(PASSWORD));
      authenticationRequest.setUsername(authentication.getName());

      SpringSecurityUser userAuthenticated =
          cognitoAuthenticationService.authenticateUser(authenticationRequest);

      if (isNotNull(userAuthenticated)) {
        Map<String, String> authenticatedCredentials =
            authenticationHelper.prepareAuthenticationResponse(userAuthenticated);
        return new UsernamePasswordAuthenticationToken(
            userAuthenticated.getUsername(),
            authenticatedCredentials,
            userAuthenticated.getAuthorities());
      } else {
        return null;
      }
    } else {
      throw new UsernameNotFoundException("No application user for given username");
    }
  }

  @Override
  public boolean supports(Class<?> authentication) {
    return authentication.equals(UsernamePasswordAuthenticationToken.class);
  }
}

以上提供者使用方法authenticateauthenticateUser

public SpringSecurityUser authenticate(AuthenticationRequest authenticationRequest) {

    AuthenticationResultType authenticationResult;

    try {

      final Map<String, String> authParams = new HashMap<>();
      authParams.put(USERNAME, authenticationRequest.getUsername());
      authParams.put(PASSWORD, authenticationRequest.getPassword());

      final AdminInitiateAuthRequest authRequest =
          new AdminInitiateAuthRequest()
              .withAuthFlow(AuthFlowType.ADMIN_NO_SRP_AUTH)
              .withClientId(cognitoConfig.getClientId())
              .withUserPoolId(cognitoConfig.getPoolId())
              .withAuthParameters(authParams);

      AdminInitiateAuthResult result = awsCognitoIdentityProvider.adminInitiateAuth(authRequest);

      // Has a Challenge
      if (StringUtils.isNotBlank(result.getChallengeName())) {

        // If the challenge is required new Password validates if it has the new password variable.
        if (NEW_PASSWORD_REQUIRED.equals(result.getChallengeName())) {

          if (isNull(authenticationRequest.getNewPassword())) {
            throw new CognitoException(
                "User must provide a new password",
                CognitoException.USER_MUST_CHANGE_PASS_WORD_EXCEPTION_CODE,
                result.getChallengeName());
          } else {
            // we still need the username

            final Map<String, String> challengeResponses = new HashMap<>();
            challengeResponses.put(USERNAME, authenticationRequest.getUsername());
            challengeResponses.put(PASSWORD, authenticationRequest.getPassword());

            // add the new password to the params map
            challengeResponses.put(NEW_PASSWORD, authenticationRequest.getNewPassword());

            // populate the challenge response
            final AdminRespondToAuthChallengeRequest request =
                new AdminRespondToAuthChallengeRequest()
                    .withChallengeName(ChallengeNameType.NEW_PASSWORD_REQUIRED)
                    .withChallengeResponses(challengeResponses)
                    .withClientId(cognitoConfig.getClientId())
                    .withUserPoolId(cognitoConfig.getPoolId())
                    .withSession(result.getSession());

            AdminRespondToAuthChallengeResult resultChallenge =
                awsCognitoIdentityProvider.adminRespondToAuthChallenge(request);
            authenticationResult = resultChallenge.getAuthenticationResult();
          }
        } else {
          // has another challenge
          throw new CognitoException(
              result.getChallengeName(), CognitoException.USER_MUST_DO_ANOTHER_CHALLENGE);
        }

      } else {
        // Doesn't have a challenge
        authenticationResult = result.getAuthenticationResult();
      }

      var userAuthenticated =
          SpringSecurityUser.builder()
              .username(authenticationRequest.getUsername())
              .password(authenticationRequest.getPassword())
              .accessToken(authenticationResult.getAccessToken())
              .refreshToken(authenticationResult.getRefreshToken())
              .expiresIn(authenticationResult.getExpiresIn())
              .tokenType(authenticationResult.getTokenType())
              .idToken(authenticationResult.getIdToken())
              .build();

      log.info("User with username: {} authenticated", authenticationRequest.getUsername());

      return userAuthenticated;
    } catch (AWSCognitoIdentityProviderException e) {
      log.error(e.getMessage(), e);
      throw new CognitoException(
          e.getMessage(), e.getErrorCode(), e.getMessage() + e.getErrorCode());
    } catch (CognitoException cognitoException) {
      throw cognitoException;
    } catch (Exception e) {
      log.error(e.getMessage(), e);
      throw new CognitoException(
          e.getMessage(), CognitoException.GENERIC_EXCEPTION_CODE, e.getMessage());
    }
  }

public SpringSecurityUser authenticateUser(AuthenticationRequest request) {

    try {

      final var authRequest =
          new InitiateAuthRequest()
              .withAuthFlow(AuthFlowType.USER_PASSWORD_AUTH)
              .withClientId(cognitoConfig.getClientId())
              .withAuthParameters(prepareAuthorizationParameters(request));

      var initiateAuthResult = awsCognitoIdentityProvider.initiateAuth(authRequest);
      var authenticationResult = initiateAuthResult.getAuthenticationResult();

      Set<GrantedAuthority> grantedAuthorities = prepareUserAuthorities(request.getUsername());

      var userAuthenticated =
          SpringSecurityUser.builder()
              .username(request.getUsername())
              .password(request.getPassword())
              .accessToken(authenticationResult.getAccessToken())
              .refreshToken(authenticationResult.getRefreshToken())
              .expiresIn(authenticationResult.getExpiresIn())
              .tokenType(authenticationResult.getTokenType())
              .idToken(authenticationResult.getIdToken())
              .authorities(grantedAuthorities)
              .build();

      log.info(USER_SUCCESSFULLY_AUTHENTICATED, request.getUsername(), grantedAuthorities);
      return userAuthenticated;
    } catch (AWSCognitoIdentityProviderException e) {
      log.error(e.getMessage(), e);
      throw new CognitoException(
          e.getMessage(), e.getErrorCode(), e.getMessage() + e.getErrorCode());
    } catch (CognitoException cognitoException) {
      throw cognitoException;
    } catch (Exception e) {
      log.error(e.getMessage(), e);
      throw new CognitoException(
          e.getMessage(), CognitoException.GENERIC_EXCEPTION_CODE, e.getMessage());
    }
  }

我的配置如下:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableTransactionManagement
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

  private CustomAuthenticationProvider authProvider;
  private UserAuthenticationProvider userAuthProvider;
  private AccountControllerExceptionHandler exceptionHandler;
  private static final String LOGIN_URL = "/auth/login";
  private static final String LOGOUT_URL = "/auth/signOut";

  @Autowired
  public WebSecurityConfiguration(
      CustomAuthenticationProvider authProvider,
      UserAuthenticationProvider userAuthProvider,
      AccountControllerExceptionHandler exceptionHandler) {
    this.authProvider = authProvider;
    this.userAuthProvider = userAuthProvider;
    this.exceptionHandler = exceptionHandler;
  }

  public WebSecurityConfiguration() {
    super(true);
  }

  @Override
  protected void configure(AuthenticationManagerBuilder auth) {
    auth.authenticationProvider(authProvider).eraseCredentials(false);
    auth.authenticationProvider(userAuthProvider).eraseCredentials(false);
  }

  @Bean
  @Override
  public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
  }

  @Override
  public void configure(WebSecurity web) {
    // TokenAuthenticationFilter will ignore the below paths
    web.ignoring().antMatchers("/auth");
    web.ignoring().antMatchers("/auth/**");
    web.ignoring().antMatchers("/v2/api-docs");
    web.ignoring().antMatchers(GET, "/nutrition/api/**");
    web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
  }

  @Override
  protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity
        .addFilterAfter(corsFilter(), ExceptionTranslationFilter.class)
        .exceptionHandling()
        .authenticationEntryPoint(new SecurityAuthenticationEntryPoint())
        .accessDeniedHandler(new RestAccessDeniedHandler())
        .and()
        .anonymous()
        .and()
        .sessionManagement()
        .sessionCreationPolicy(STATELESS)
        .and()
        .authorizeRequests()
        .antMatchers("/auth")
        .permitAll()
        .anyRequest()
        .authenticated()
        .and()
        // Instantiate a new instance of the filter
        .addFilterBefore(
            awsCognitoJwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
        .formLogin(
            formLogin -> formLogin.loginProcessingUrl(LOGIN_URL).failureHandler(exceptionHandler))
        .logout(logout -> logout.permitAll().logoutUrl(LOGOUT_URL))
        .csrf(AbstractHttpConfigurer::disable);
  }

  private CorsFilter corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOrigin("*");
    config.addAllowedHeader(ORIGIN);
    config.addAllowedHeader(CONTENT_TYPE);
    config.addAllowedHeader(ACCEPT);
    config.addAllowedHeader(AUTHORIZATION);
    config.addAllowedMethod(GET);
    config.addAllowedMethod(PUT);
    config.addAllowedMethod(POST);
    config.addAllowedMethod(OPTIONS);
    config.addAllowedMethod(DELETE);
    config.addAllowedMethod(PATCH);
    config.setMaxAge(3600L);

    source.registerCorsConfiguration("/v2/api-docs", config);
    source.registerCorsConfiguration("/**", config);
    return new CorsFilter();
  }

  private AwsCognitoJwtAuthenticationFilter awsCognitoJwtAuthenticationFilter() {
    return new AwsCognitoJwtAuthenticationFilter(new AwsCognitoIdTokenProcessor(), exceptionHandler);
  }
}

我的控制器看起来像:

@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthenticationController {

  private final AuthenticationManager authenticationManager;
  private final CognitoAuthenticationService authService;

  @SuppressWarnings("unchecked")
  @CrossOrigin
  @PostMapping("/login")
  public ResponseEntity<AuthenticationResponse> authenticationRequest(
      @RequestBody AuthenticationRequest authRequest) {

    String accessToken;

    Map<String, String> authorizationParameters =
        authService.prepareAuthorizationParameters(authRequest);

    Authentication authentication =
        authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                authRequest.getUsername(), authorizationParameters));

    //todo verify if above can be simplified
    Map<String, String> authenticatedCredentials =
        (Map<String, String>) authentication.getCredentials();

    accessToken = authenticatedCredentials.get(ACCESS_TOKEN_KEY);

    UserResponse userResponse = authService.getUserInfo(accessToken);

    SecurityContextHolder.getContext().setAuthentication(authentication);

    AuthenticationResponse authResponse =
        AuthenticationResponse.builder()
            .accessToken(authenticatedCredentials.get(ID_TOKEN_KEY))
            .expiresIn(authenticatedCredentials.get(EXPIRES_IN_KEY))
            .sessionToken(accessToken)
            .userData(userResponse)
            .build();

    return ResponseEntity.ok(authResponse);
  }

  @SuppressWarnings("unchecked")
  @CrossOrigin
  @PostMapping("/loginUser")
  public ResponseEntity<AuthenticationResponse> authenticationUserRequest(
      @RequestBody AuthenticationRequest authRequest) {

    String accessToken;

    Map<String, String> authorizationParameters =
        authService.prepareAuthorizationParameters(authRequest);

    Authentication authentication =
        authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                authRequest.getUsername(), authorizationParameters));

    Map<String, String> authenticatedCredentials =
        (Map<String, String>) authentication.getCredentials();

    accessToken = authenticatedCredentials.get(ACCESS_TOKEN_KEY);

    UserResponse userResponse = authService.getUserInfo(accessToken);

    SecurityContextHolder.getContext().setAuthentication(authentication);

    AuthenticationResponse authResponse =
        AuthenticationResponse.builder()
            .accessToken(authenticatedCredentials.get(ID_TOKEN_KEY))
            .expiresIn(authenticatedCredentials.get(EXPIRES_IN_KEY))
            .sessionToken(accessToken)
            .userData(userResponse)
            .build();

    return ResponseEntity.ok(authResponse);
  }
}

并且存在一个问题,即我无法选择将AuthenticationManager在该Controller行中调用的幕后方法:authenticationManager.authenticate()。我知道将身份验证分为两种方式,比如管理员/用户不是最好的选择,在未来,我将摆脱这种方式,但它不会改变我想为社交身份验证创建另一个提供者的事实和会有同样的问题,因为到目前为止我没有选择合适的authentication()方法的选项。我将不胜感激有关如何解决此问题的建议。

标签: javaspringamazon-web-servicesauthenticationspring-security

解决方案


推荐阅读