rest - MvC中spring security中每个路径/路由的不同AuthenticationManager
问题描述
前言
由于 StackOverflow 上已经有很多关于此的问题,我首先要确保这不是重复和区分。
这是关于
- 在 2 个不同的 AuthenticationManager 中有 2 个(或更多)不同的 AuthenticationProviders 用于不同的路由。
- 使用 Spring Security 5.5 而不是 3.x 中的方法
- 使用基于非 XML 配置的方法
所以问题不在于:
- 如何在 AuthenticationManager 中包含几个 AuthenticationProvideres 以提供“替代身份验证”(大多数问题往往是)
案子
假设一个有 2 个自定义 AuthenticationProviders:CATApiTokenProvider和DOGApiTokenProvider。我们不谈论 AOuth/JWT/Basic/Form 提供程序是设计使然,因为它们提供了快捷方式。
现在我们有 2 个 REST API 端点/dog/endpoint
和/cat/endpoint
.
问题
今天如何使用 Spring Security 5.5 正确实现这一点:
- 我们希望身份验证提供程序
CATApiTokenProvider
只能对请求进行身份验证/cat/endpoint
- 我们希望身份验证提供程序
DOGApiTokenProvider
只能对请求进行身份验证/dog/endpoint
因此,无法使用 cat 令牌进行身份验证,也无法使用/dog/endpoint
dog 令牌进行身份验证/cat/endpoint
。
我的想法/方法
a) 我知道,由于我有自定义 Cat/Dog 过滤器,因此可以AuthenticationManagerResolver
在创建 bean 时使用并将一个实例传递给过滤器。这个解析器可能看起来像
public AuthenticationManagerResolver<HttpServletRequest> resolver()
{
return request -> {
if (request.getPathInfo().startsWith("/dog/")) {
try {
return ???;
} catch (Exception exception) {
log.error(exception);
}
}
if (request.getPathInfo().startsWith("/cat/")) {
try {
return ???;
} catch (Exception exception) {
log.error(exception);
}
}
};
}
有两个问题是:
- 如何在这里返回不同的身份验证管理器?如何分别使用 CatAP 和 DogAP 实例化 2 个不同的 AM?目前我使用
public void configure(AuthenticationManagerBuilder auth)
,但据我了解,我只会配置“一个”AuthenticationManager,我可以在那里添加 DogAP 和 CatAP,但这会让 1 AM 和 2 个 AP,所以当使用这个 AM 时,我可以使用猫端点上的狗令牌 - 这真的是实现这一点的正确方法吗?我本来希望能够在 SecurityConfiguration 级别上提供 AM
b) 以某种方式实例化 2 个不同的 AuthenticationManager,然后使用 SecurityConfiguration 将它们分配给不同的匹配器。
两个问题:
- 用不同的提供商产生 2 个不同的 AM 的正确方法是什么?
- 我无法理解如何为规范添加 AM
http.authorizeRequests()
.antMatchers("/dog/**")
.?
解决方案
您可以发布多个过滤器链或将您自己AuthenticationFilter
的过滤器链与AuthenticationManagerResolver
您可以使用AuthenticationManagerResolver
返回不同AuthenticationManager
的 s。从 Spring Security 5.4.0 开始,我们不再需要扩展WebSecurityConfigurerAdapter
来配置我们SecurityFilterChain
的,您可以改为定义一个SecurityFilterChain
类型的 bean。
我将详细介绍您自己的接线AuthenticationFilter
。
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain apiSecurity(HttpSecurity http) throws Exception {
http.authorizeRequests((authz) -> authz
.anyRequest().authenticated());
http.addFilterBefore(apiAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
private AuthenticationFilter apiAuthenticationFilter() {
AuthenticationFilter authenticationFilter = new AuthenticationFilter(new ApiAuthenticationManagerResolver(), new BasicAuthenticationConverter());
authenticationFilter.setSuccessHandler((request, response, authentication) -> {});
return authenticationFilter;
}
public static class ApiAuthenticationManagerResolver implements AuthenticationManagerResolver<HttpServletRequest> {
private final Map<RequestMatcher, AuthenticationManager> managers = Map.of(
new AntPathRequestMatcher("/dog/**"), new DogAuthenticationProvider()::authenticate,
new AntPathRequestMatcher("/cat/**"), new CatAuthenticationProvider()::authenticate
);
@Override
public AuthenticationManager resolve(HttpServletRequest request) {
for (Map.Entry<RequestMatcher, AuthenticationManager> entry : managers.entrySet()) {
if (entry.getKey().matches(request)) {
return entry.getValue();
}
}
throw new IllegalArgumentException("Unable to resolve AuthenticationManager");
}
}
public static class DogAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication.getName().endsWith("_dog")) {
return new UsernamePasswordAuthenticationToken(authentication.getName(), authentication.getCredentials(),
AuthorityUtils.createAuthorityList("ROLE_DOG"));
}
throw new BadCredentialsException("Username should end with _dog");
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
}
public static class CatAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication.getName().endsWith("_cat")) {
return new UsernamePasswordAuthenticationToken(authentication.getName(), authentication.getCredentials(),
AuthorityUtils.createAuthorityList("ROLE_CAT"));
}
throw new BadCredentialsException("Username should end with _cat");
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
}
}
在上面的示例中,我们有两个AuthenticationProvider
s,一个用于 cat,另一个用于 dog。它们在. AntPathRequestMatcher
_ /dog/**
_ 不需要为每只狗和猫定义一个,因为它们具有相同的接口。/cat/**
ApiAuthenticationManagerResolver
AuthenticationManager
AuthenticationProvider/Manager
然后将ApiAuthenticationManagerResolver
其连接到AuthenticationFilter
您的过滤器链中。
您还可以为每个端点定义两个不同的过滤器链,如下所示:
@Bean
public SecurityFilterChain dogApiSecurity(HttpSecurity http) throws Exception {
http.requestMatchers((matchers) -> matchers
.antMatchers("/dog/**"));
http.authorizeRequests((authz) -> authz
.anyRequest().authenticated());
http.httpBasic();
http.authenticationProvider(new DogAuthenticationProvider());
return http.build();
}
@Bean
public SecurityFilterChain catApiSecurity(HttpSecurity http) throws Exception {
http.requestMatchers((matchers) -> matchers
.antMatchers("/cat/**"));
http.authorizeRequests((authz) -> authz
.anyRequest().authenticated());
http.httpBasic();
http.authenticationProvider(new CatAuthenticationProvider());
return http.build();
}
请在定义多个过滤器链时,排序很重要,@Order
在这些场景中使用注释。
当您这样做时,http.requestMatcher(new AntPathRequestMatcher("/endpoint/**"));
您是在告诉 Spring Security 仅在请求与该路径匹配时才调用过滤器链。
Spring Security 的存储库中还有一张票,提供了一个AuthenticationManagerResolver
接受的实现,Map<RequestMatcher, AuthenticationManager>
如果您认为它有道理,那就太好了,在那里竖起大拇指。
推荐阅读
- scala - 如何使用 slick 3.2 + 在 select 子句中编写嵌套查询
- javascript - 如何使用 JavaScript/Jquery 逐一输出查询结果
- reactjs - 如何在 React js 中加入工具提示功能
- java - 从字符串中获取 Lat 和 Long
- solr - 带有typo3的solr没有索引
- java - 运算符不存在:整数 = 字符变化
- tfs - 我可以多次启动相同的构建定义并且它们是并行处理的,我怎么能否认这种行为?
- d3.js - 在特定弧或切片上突出显示节点文本和切片
- r - 为线性回归图(ggplot2)添加 R 平方?
- python-3.x - 评估一张图像的 CNN 模型