Spring 启动 Azure 多 HttpSecurity
Spring Boot Azure Multiple HttpSecurity
是否可以混合使用两种身份验证模式?
- 内部用户:Azure 广告
- 外部用户:表单认证
到目前为止我有这个:
@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);
}
}
}
我们有混合帐户(外部 users/internal 用户),所以我们需要首先检查哪种帐户想要访问。
我的想法是为 internal/external 用户提供一个专用的登录表单,在其中完成路由,例如 /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
是 spring 安全和基于表单的身份验证的固定路径。那么这是为了在混合环境中使用 azure 吗?
此设置是否会以任何方式相互冲突?
谢谢!
多亏了评论员的意见和大量的谷歌搜索,我最终得到了这个工作版本:
@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
我不知道我们是否会在生产就绪代码中实现它。
就个人而言,我对这种混合各种身份验证方案的感觉很糟糕。
我认为最好将两者(当有 external/internal 用户时)分成单独的应用程序,单独 SecurityConfiguration
欢迎任何help/comments/tips混音external/internal用户!
是否可以混合使用两种身份验证模式?
- 内部用户:Azure 广告
- 外部用户:表单认证
到目前为止我有这个:
@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);
}
}
}
我们有混合帐户(外部 users/internal 用户),所以我们需要首先检查哪种帐户想要访问。
我的想法是为 internal/external 用户提供一个专用的登录表单,在其中完成路由,例如 /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
是 spring 安全和基于表单的身份验证的固定路径。那么这是为了在混合环境中使用 azure 吗?
此设置是否会以任何方式相互冲突?
谢谢!
多亏了评论员的意见和大量的谷歌搜索,我最终得到了这个工作版本:
@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
我不知道我们是否会在生产就绪代码中实现它。
就个人而言,我对这种混合各种身份验证方案的感觉很糟糕。
我认为最好将两者(当有 external/internal 用户时)分成单独的应用程序,单独 SecurityConfiguration
欢迎任何help/comments/tips混音external/internal用户!