我正在使用 Spring 构建 REST API,目前正在使用自定义用户详细信息服务和此配置代码来验证我的所有请求:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
}
我还设置了
DaoAuthenticationProvider
以使用我的用户详细信息服务并使用它来配置全局安全性。
现在,我想提供一个端点(同时仍通过 HTTP 基本身份验证进行保护)使用不同的用户详细信息服务来检查是否允许用户访问给定资源。
如何为不同的端点使用两个不同的用户详细信息服务?
你可以做的一件事就是拥有两个
WebSecurityConfigurerAdapter
:
@EnableWebSecurity
@Order(Ordered.HIGHEST_PRECEDENCE)
class FirstEndpointConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) {
http
.requestMatchers()
.antMatchers("/specialendpoint")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.userDetailsService(/* first of your userDetailsServices */);
}
}
@Configuration
class SecondEndpointConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) {
http // all other requests handled here
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.userDetailsService(/* second of your userDetailsServices */);
}
}
requestMatchers()
的存在是为了将 springSecurityFilterChain
定位到特定端点。
编辑:Mahmoud Odeh 提出了一个很好的观点,即如果用户群相同,那么您可能不需要多个
UserDetailsService
实例。相反,您可以使用一项更改,通过用户帐户的权限来隔离您的特殊端点:
http
.authorizeRequests()
.antMatchers("/specialendpoint").hasAuthority("SPECIAL")
.anyRequest().authenticated()
.and()
.httpBasic();
然后,您的单个
UserDetailsService
将查找所有用户。对于有权访问 SPECIAL
的用户,它将在 GrantedAuthority
实例中包含 UserDetails
/specialendpoint
。
我正在尝试遵循 M. Deinum 给出的解决方案,但在我的情况下,无论执行哪个 URL /v3/authorize/login 或 /v2/authorize/login,它总是转到相同的用户服务 (v2userDetailsService)。这是我的代码:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration {
@Configuration
@Order(2)
public static class V2Configuration extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("v2userDetailsService")
private UserDetailsService v2userDetailsService;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(256);
auth
.userDetailsService(v2userDetailsService)
.passwordEncoder(passwordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and().csrf().disable().headers()
.frameOptions().disable().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/app").permitAll()
.antMatchers("/v2/authorize/login").permitAll()
.antMatchers("/v2/authorize/reLogin").permitAll()
.antMatchers("/v2/authorize/logout").permitAll();
}
}
@Configuration
@Order(1)
public static class V3Configuration extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("v3UserDetailsService")
private UserDetailsService v3UserDetailsService;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(256);
auth
.userDetailsService(v3UserDetailsService)
.passwordEncoder(passwordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and().csrf().disable().headers()
.frameOptions().disable().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/v3/authorize/login").permitAll()
.antMatchers("/v3/authorize/reLogin").permitAll()
.antMatchers("/v3/authorize/logout").permitAll();
}
}
}
身份验证的 ProviderManager 实现在身份验证时迭代所有身份验证提供程序,并检查特定提供程序是否支持特定令牌(如果支持),然后将其委托给提供程序。 因此,我们可以做的是为每个实体创建 UsernamePasswordAuthentication 令牌的两个子类(在卖家和客户的示例中)。并且还为卖家和客户子类化 DaoAuthenticationProvider,每个提供商都将支持其各自实体的令牌。我们只需将请求委托给超类进行身份验证,因为令牌是 UsernamePasswordAuthentication 的子类。在登录卖家时,我们可以创建 SellerAuthenticationToken,对于客户,我们可以为 CustomerAuthenticationToken 创建一个令牌,并且在验证 ProviderManager 时将其委托给正确的提供商。此外,您还需要实现两个 UserDetailsService 并为提供者正确设置用户详细信息,所有配置和示例如下。
@Configuration
public class SecurityConfig{
@Bean
public CustomerAuthenticationProvider customerAuthenticationProvider(@Qualifier("customerUserDetailsService")
UserDetailsService userDetailsService){
CustomerAuthenticationProvider customerAuthenticationProvider = new CustomerAuthenticationProvider();
customerAuthenticationProvider.setPasswordEncoder(passwordEncoder());
customerAuthenticationProvider.setUserDetailsService(userDetailsService);
return customerAuthenticationProvider;
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public SellerAuthenticationProvider sellerAuthenticationProvider(@Qualifier("sellerUserDetailsService")
UserDetailsService userDetailsService
){
SellerAuthenticationProvider sellerAuthenticationProvider = new SellerAuthenticationProvider();
sellerAuthenticationProvider.setPasswordEncoder(passwordEncoder());
sellerAuthenticationProvider.setUserDetailsService(userDetailsService);
return sellerAuthenticationProvider;
}
@Bean
public AuthenticationManager authenticationManager(SellerAuthenticationProvider sellerAuthenticationProvider,
CustomerAuthenticationProvider customerAuthenticationProvider){
return new ProviderManager(sellerAuthenticationProvider, customerAuthenticationProvider);
}
}
public class SellerAuthenticationToken extends UsernamePasswordAuthenticationToken {
public SellerAuthenticationToken(Object principal, Object credentials) {
super(principal, credentials);
}
public SellerAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
}
}
public class CustomerAuthenticationToken extends UsernamePasswordAuthenticationToken {
public CustomerAuthenticationToken(Object principal, Object credentials) {
super(principal, credentials);
}
public CustomerAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
}
}
public class CustomerAuthenticationProvider extends DaoAuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if(!(authentication instanceof CustomerAuthenticationToken)){
throw new IllegalArgumentException("invalid");
}
return super.authenticate(authentication);
}
@Override
public boolean supports(Class<?> authentication) {
return (CustomerAuthenticationToken.class.isAssignableFrom(authentication));
}
}
public class SellerAuthenticationProvider extends DaoAuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException{
if(!(authentication instanceof SellerAuthenticationToken)){
throw new IllegalArgumentException("invalid");
}
return super.authenticate(authentication);
}
@Override
public boolean supports(Class<?> authentication) {
return (SellerAuthenticationToken.class.isAssignableFrom(authentication));
}
}
//While logging in from the seller login endpoint
Authentication authentication = authenticationManager.authenticate(
new SellerAuthenticationToken(request.username(), request.password())
);
//While logging the customer from the customer endpoint
Authentication authentication = authenticationManager.authenticate(
new CustomerAuthenticationToken(request.username(), request.password())
);