Можно ли смешивать два режима аутентификации?
Пока у меня это:
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {
@Configuration
@Order(1)
public static class MfaAuthentication extends AadWebSecurityConfigurerAdapter {
private final UserService userService;
@Autowired
public MfaAuthentication(UserService userService) {
this.userService = userService;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.antMatcher("/internal/**")
.authorizeHttpRequests()
.anyRequest().authenticated()
.and()
.oauth2Login()
.userInfoEndpoint(userInfoEndpointConfig -> {
userInfoEndpointConfig.oidcUserService(this.oidcUserService());
});
}
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
final OidcUserService delegate = new OidcUserService();
return (userRequest) -> {
// Delegate to the default implementation for loading a user
OidcUser oidcUser = delegate.loadUser(userRequest);
OAuth2AccessToken accessToken = userRequest.getAccessToken();
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
// TODO
// 1) Fetch the authority information from the protected resource using accessToken
// 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
// 3) Create a copy of oidcUser but use the mappedAuthorities instead
List<String> dummy = userService.fetchUserRoles("dummy");
dummy.forEach(user -> mappedAuthorities.add((GrantedAuthority) () -> user));
oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
return oidcUser;
};
}
}
@Configuration
public static class ExternalAuthentication extends WebSecurityConfigurerAdapter {
private final ThdAuthenticationProvider thdAuthenticationProvider;
@Autowired
public ExternalAuthentication(ThdAuthenticationProvider thdAuthenticationProvider) {
this.thdAuthenticationProvider = thdAuthenticationProvider;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/external/**")
.authorizeRequests()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.anyRequest().fullyAuthenticated()
.and()
.formLogin()
.loginPage("/external/login").permitAll()
.defaultSuccessUrl("/external/index", true)
.failureUrl("/external/denied")
.and()
.logout()
.invalidateHttpSession(true)
.and()
.authenticationProvider(thdAuthenticationProvider);
}
}
}
У нас есть смешанные учетные записи (внешние пользователи/внутренние пользователи), поэтому нам нужно проверить, какой тип учетной записи хочет иметь доступ в первую очередь.
Моя идея состоит в том, чтобы предоставить специальную форму входа для внутреннего/внешнего пользователя, где маршрутизация выполняется так, как /internal/**
идет к нашему входу в Azure, а /external/**
идет к пользовательскому поставщику аутентификации.
Когда я еду в http://localhost:8080/internal
, меня перенаправляют на http://localhost:8080/oauth2/authorization/azure
, говоря, что карты нет. Я хочу, чтобы меня перенаправили на нашу учетную запись Azure.
Это можно сделать?
РЕДАКТИРОВАТЬ
application.properties
# Enable related features.
spring.cloud.azure.active-directory.enabled=true
# Specifies your Active Directory ID:
spring.cloud.azure.active-directory.profile.tenant-id=some-id
# Specifies your App Registration's Application ID:
spring.cloud.azure.active-directory.credential.client-id=some-client-id
# Specifies your App Registration's secret key:
spring.cloud.azure.active-directory.credential.client-secret=some-secret
Сообщение об ошибке:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Fri May 06 12:41:41 CEST 2022
There was an unexpected error (type=Not Found, status=404).
РЕДАКТИРОВАТЬ 2
Благодаря комментариям я понял правильную конфигурацию - по крайней мере, для маршрутизации. У меня на данный момент такая конфигурация:
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {
@Configuration
public static class MfaAuthentication extends AadWebSecurityConfigurerAdapter {
private final UserService userService;
@Autowired
public MfaAuthentication(UserService userService) {
this.userService = userService;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.antMatchers("/index").permitAll()
.antMatchers("/public/**").permitAll()
.antMatchers("/internal/**").hasAnyAuthority("Administrator")
.anyRequest()
.authenticated()
.and()
.oauth2Login()
.userInfoEndpoint(userInfoEndpointConfig -> {
userInfoEndpointConfig.oidcUserService(this.oidcUserService());
})
.defaultSuccessUrl("/internal/index", true);
}
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
final OidcUserService delegate = new OidcUserService();
return (userRequest) -> {
// Delegate to the default implementation for loading a user
OidcUser oidcUser = delegate.loadUser(userRequest);
OAuth2AccessToken accessToken = userRequest.getAccessToken();
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
// TODO
// 1) Fetch the authority information from the protected resource using accessToken
// 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
// 3) Create a copy of oidcUser but use the mappedAuthorities instead
List<String> dummy = userService.fetchUserRoles("dummy");
dummy.forEach(user -> mappedAuthorities.add((GrantedAuthority) () -> user));
oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
return oidcUser;
};
}
}
@Configuration
@Order(1)
public static class ExternalAuthentication extends WebSecurityConfigurerAdapter {
private final ThdAuthenticationProvider thdAuthenticationProvider;
@Autowired
public ExternalAuthentication(ThdAuthenticationProvider thdAuthenticationProvider) {
this.thdAuthenticationProvider = thdAuthenticationProvider;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/external/**")
.authorizeRequests()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.antMatchers("/login").permitAll()
.anyRequest()
.fullyAuthenticated()
.and()
.formLogin()
.loginPage("/external/login").permitAll()
.loginProcessingUrl("/external/login").permitAll()
.defaultSuccessUrl("/external/index", true)
.failureUrl("/external/denied")
.and()
.logout()
.invalidateHttpSession(true)
.and()
.authenticationProvider(thdAuthenticationProvider);
}
}
}
Проблема сейчас:
Когда я еду в /external/index
, меня перенаправляют на мою пользовательскую страницу входа. Когда я хочу войти в систему (перенаправляется через POST в /login), меня перенаправляют на страницу, где я могу выбрать один из логинов oauth2, который сам нацелен на http://localhost:8080/oauth2/authorization/azure
Вот выдержка из моей формы (thymeleaf):
<form action = "#" th:action = "@{/login}" method = "post" class = "form-signin"
accept-charset = "utf-8">
</form>
Я знаю, что /login
— это фиксированный маршрут для весенней безопасности и аутентификации на основе форм. Так это предназначено для работы с лазурью в смешанной среде?
Эта установка как-то противоречит друг другу?
Благодарю вас!
Вы читали: docs.microsoft.com/en-us/azure/developer/java/spring-framework/… и docs.microsoft.com/en-us/azure/developer/java/spring-framework/…
Короткий ответ: да, у вас может быть два разных режима аутентификации. Однако вы можете подумать о том, чтобы заставить работать первый (Azure), прежде чем внедрять второй в свой код.
Спасибо вам всем. Пожалуйста, смотрите мой Редактировать 2. У меня есть новая проблема по этому поводу :) Может быть, вы можете помочь?
Благодаря вкладу комментаторов и тщательному гуглению я получил эту рабочую версию:
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {
@Configuration
public static class MfaAuthentication extends AadWebSecurityConfigurerAdapter {
private final UserService userService;
@Autowired
public MfaAuthentication(UserService userService) {
this.userService = userService;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.csrf()
.and()
.authorizeRequests(authorize -> authorize.antMatchers("/").permitAll()
.antMatchers("/index").permitAll()
.antMatchers("/public/**").permitAll()
.antMatchers("/internal/**").hasAnyAuthority("Administrator")
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.anyRequest().authenticated())
.oauth2Login()
.userInfoEndpoint(userInfoEndpointConfig -> userInfoEndpointConfig.oidcUserService(this.oidcUserService()))
.defaultSuccessUrl("/internal/index", true);
}
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
final OidcUserService delegate = new OidcUserService();
return (userRequest) -> {
// Delegate to the default implementation for loading a user
OidcUser oidcUser = delegate.loadUser(userRequest);
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
List<String> dummy = userService.fetchUserRoles("dummy");
dummy.forEach(user -> mappedAuthorities.add((GrantedAuthority) () -> user));
oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
return oidcUser;
};
}
}
@Configuration
@Order(1)
public static class ExternalAuthentication extends WebSecurityConfigurerAdapter {
private final ThdAuthenticationProvider thdAuthenticationProvider;
@Autowired
public ExternalAuthentication(ThdAuthenticationProvider thdAuthenticationProvider) {
this.thdAuthenticationProvider = thdAuthenticationProvider;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/external/**")
.authorizeRequests(authorize -> authorize.antMatchers("/").permitAll()
.antMatchers("/index").permitAll()
.antMatchers("/public/**").permitAll()
.antMatchers("/external/**").hasAnyAuthority("External")
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.anyRequest().authenticated())
.formLogin()
.loginPage("/external/login").permitAll()
.loginProcessingUrl("/external/login").permitAll()
.defaultSuccessUrl("/external/index", true)
.failureUrl("/external/denied")
.and()
.logout()
.invalidateHttpSession(true)
.and()
.authenticationProvider(thdAuthenticationProvider);
}
}
}
Вот моя пользовательская внешняя форма входа в систему - по крайней мере, ее выдержка:
<form accept-charset = "utf-8" action = "#" class = "form-signin" method = "post"
th:action = "@{/external/login}">
</form>
Все /internal/**
маршруты идут к нашему входу в Azure AD.
Обратите внимание, что существует пользовательская служба oidc для загрузки дополнительных ролей для данного пользователя.
Все /external/**
маршруты идут в наш кастом AuthenticationProvider
Я не знаю, будем ли мы реализовывать это в готовом для производства коде.
Лично у меня плохое предчувствие по поводу этого смешения различных сценариев аутентификации.
Я думаю, что лучше разделить оба (при наличии внешнего/внутреннего пользователя) на отдельные приложения с отдельными SecurityConfiguration
Любая помощь/комментарии/советы по смешиванию внешних/внутренних пользователей приветствуется!
Вы не настроили клиент OAuth2. Путь:
spring.security.oauth2.client
.