java - 如何从 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);
}
}
以上提供者使用方法authenticate
和authenticateUser
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()
方法的选项。我将不胜感激有关如何解决此问题的建议。
解决方案
推荐阅读
- c - MacOS SO_REUSEADDR/SO_REUSEPORT 与 Linux 不一致?
- python - 将从文本文件中获取的所有打印字符串合并到一个列表中
- spring - 休眠中的 UUID 映射
- python - 在“Think Python”中理解海龟圆和弧
- javascript - JavaScript 对象返回空值
- java - java pojo中如何区分JSON null值和默认java null
- mysql - 为什么sql正确和运行它的内在机制呢?
- php - pug-php中的href不起作用
- sql - 使用单个语句更改多个表中的公共列
- python - Django 表单:无法显示验证错误