Сервер авторизации Spring перенаправляется на страницу входа при запросе токена
В настоящее время у нас есть 2 веб-приложения, которые аутентифицируются в CAS. Связь между ними осуществляется через Basic Auth.
Из соображений безопасности мы хотим переключиться на OAuth2, чтобы избавиться от базовой аутентификации. В то же время мы хотим избавиться от CAS, потому что он не соответствует нашим требованиям.
Итак, цель примерно такая:
Я нахожусь в том состоянии, когда могу войти в систему управления пользователями и манипулировать данными. Второе приложение также корректно перенаправляет меня в 1-е приложение для аутентификации. Аутентификация тоже работает. И 1-е приложение правильно реагирует на 2-е приложение. Затем второе приложение выполняет ожидаемый POST для URL-адреса «oauth2/token». Но первое приложение отвечает 302 на "/login" вместо токена.
Какой пункт я пропустил или мне все еще нужно настроить?
Я ориентировался на следующую документацию: https://docs.spring.io/spring-authorization-server/docs/current/reference/html/getting-started.html.
Мой AuthorizationServerConfig выглядит так:
@Configuration
public class AuthorizationServerConfig {
private final SecurityApp app;
public AuthorizationServerConfig(SecurityApp app) {
this.app = app;
}
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
authorizationServerConfigurer
.authorizationEndpoint(authorizationEndpoint ->
authorizationEndpoint.consentPage("/oauth2/authorize"))
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
RequestMatcher endpointsMatcher = authorizationServerConfigurer
.getEndpointsMatcher();
http
.securityMatcher(endpointsMatcher)
.authorizeHttpRequests(authorize ->
authorize.anyRequest().authenticated()
)
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
.exceptionHandling(exceptions ->
exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.apply(authorizationServerConfigurer);
return http.build();
}
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
// Form login handles the redirect to the login page from the
// authorization server filter chain
.formLogin(Customizer.withDefaults());
return http.build();
}
@Bean
UserDetailsService users() {
return app::findUserByLogin;
}
// OVERWATCH
@Bean
public RegisteredClientRepository registeredClientRepository() {
return new RegisteredClientRepository() {
@Override
public void save(RegisteredClient registeredClient) {
throw new NotImplementedException();
}
@Override
public RegisteredClient findById(String id) {
return app.findByClientId(id);
}
@Override
public RegisteredClient findByClientId(String clientId) {
return app.findByClientId(clientId);
}
};
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
RSAPublicKey publicKey = app.getPublicKey();
RSAPrivateKey privateKey = (RSAPrivateKey) app.getPrivateKey();
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
// region Password Authenticator
@Bean
public PasswordEncoder passwordEncoder() {
return app.passwordEncoder();
}
// endregion
}
И конфигурация из приложения 2 выглядит так:
@Configuration
@EnableWebSecurity
public class AceSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/oauth/authorize**", "/login**", "/error**")
.permitAll()
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login( oauth2Login -> oauth2Login.defaultSuccessUrl("/index.html") );
}
}
application.properties из App2:
spring.security.oauth2.client.registration.<provider-name>.client-id=${app.uuid}
spring.security.oauth2.client.registration.<provider-name>.client-secret=${app.secret}
spring.security.oauth2.client.registration.<provider-name>.scope=openid
spring.security.oauth2.client.registration.<provider-name>.redirect-uri=http://127.0.0.1:8088/login/oauth2/code/<provider-name>
spring.security.oauth2.client.registration.<provider-name>.client-name=${app.name}
spring.security.oauth2.client.registration.<provider-name>.provider=${provider.name}
spring.security.oauth2.client.registration.<provider-name>.client-authentication-method=code
spring.security.oauth2.client.registration.<provider-name>.authorization-grant type=authorization_code
spring.security.oauth2.client.provider.<provider-name>.authorization-uri=http://localhost:8086/oauth2/authorize
spring.security.oauth2.client.provider.<provider-name>.token-uri=http://localhost:8086/oauth2/token
spring.security.oauth2.client.provider.<provider-name>.user-info-uri=http://localhost:8086/oauth2/userinfo?schema=openid
spring.security.oauth2.client.provider.<provider-name>.user-name-attribute=name
spring.security.oauth2.client.provider.<provider-name>.user-info-authentication-method=header
spring.security.oauth2.client.provider.<provider-name>.jwk-set-uri=http://localhost:8086/jwks
Запрос от App2 к App 1, вызывающий перенаправление:
POST http://localhost:8086/oauth2/token
Body:
grant_type=authorization_code,
code=Zls0ppjnS_RXyMVPB8fg_eQQgoiUAxRguOMsdyVYQpgd8eDkUDzgz813L0ybovTL7sNj0TDRUHibPfek9NzwULND1mty5WPW2DOtQjTAaEROL3qP7RvyTWXTEzzYe-o,
redirect_uri=[http://127.0.0.1:8088/login/oauth2/code/<provider-name>],
client_id=<app.uuid>
Header:
Accept:"application/json;charset=UTF-8",
Content-Type:"application/x-www-form-urlencoded;charset=UTF-8"
1 ответ
Для людей, столкнувшихся с такой же или похожей проблемой. Собственно проблема была:
spring.security.oauth2.client.registration.<provider-name>.client-authentication-method=code.
В базе данных был объявлен другой метод аутентификации.
Еще одна проблема заключалась в объявлении файла PasswordEncoder. Пароли в базе данных хранились BCrypt в зашифрованном виде. Однако общий секрет хранился в незашифрованном виде в базе данных (чтобы иметь возможность отображать его во внешнем интерфейсе). Так что возникла проблема со сравнением этих двух секретов.