java - 如何为 Spring Boot RESTful Web 服务配置多级身份验证?
问题描述
我们正在使用 Spring Boot 构建一个 RESTful Web 服务。我们希望有 2 级身份验证来保护端点。
首先,对于每个请求,我们要检查请求头中是否有指定的 apiKey,如果没有,我们将拒绝该请求。如果请求有 apiKey,我们将对某些请求使用用户名/密码登录进行下一次身份验证。有公共端点只需要 apiKey 身份验证,而私有端点首先需要 apiKey 身份验证,然后需要用户名/密码身份验证才能访问它们。
对于 apiKey 身份验证,我在这里复制了代码,我还可以找到许多有关用户名/密码身份验证的示例。
我的问题是:如何在 WebSecurityConfigurerAdapter 中进行 Java 配置以将它们组合在一起。
现在我为这 2 个身份验证过滤器定义了 2 个扩展 WebSecurityConfigurerAdapter 的配置类,但请求只会通过其中一个,具体取决于我设置为@Order(1) 的那个。
谢谢。
解决方案
整个答案由一个工作的 Spring Boot 应用程序支持,并带有单元测试来确认它。
如果您觉得这个答案有帮助,请投票。
简短的回答是您的安全配置可能如下所示
http
.sessionManagement()
.disable()
//application security
.authorizeRequests()
.anyRequest().hasAuthority("API_KEY")
.and()
.addFilterBefore(new ApiKeyFilter(), HeaderWriterFilter.class)
.addFilterAfter(new UserCredentialsFilter(), ApiKeyFilter.class)
.csrf().ignoringAntMatchers(
"/api-key-only",
"/dual-auth"
)
;
// @formatter:on
}
}
让我告诉你一点点是怎么回事。我鼓励您查看我的示例,特别是涵盖您的许多场景的单元测试。
我们有两个安全级别 1. 每个 API 都必须由 ApiKey 保护 2. 只有一些 API 必须由 UserCredentials 保护
在我的示例项目中,我选择了以下解决方案
我使用 WebSecurityConfigurerAdapter 来满足 ApiKey 要求
.authorizeRequests() .anyRequest().hasAuthority("API_KEY")
我通过启用它来使用方法级别的安全性
@EnableGlobalMethodSecurity(prePostEnabled = true)
然后在我的控制器中要求它
@PreAuthorize("hasAuthority('USER_CREDENTIALS')")
public String twoLayersOfAuth() {
//only logic here
}
ApiKey 过滤器超级简单
public class ApiKeyFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
final String authorization = request.getHeader("Authorization");
final String prefix = "ApiKey ";
if (hasText(authorization) && authorization.startsWith(prefix)) {
String key = authorization.substring(prefix.length());
if ("this-is-a-valid-key".equals(key)) {
RestAuthentication<SimpleGrantedAuthority> authentication = new RestAuthentication<>(
key,
Collections.singletonList(new SimpleGrantedAuthority("API_KEY"))
);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(request, response);
}
}
第二层身份验证甚至很简单(它依赖于第一层来执行)
public class UserCredentialsFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
final String userCredentials = request.getHeader("X-User-Credentials");
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if ("valid-user".equals(userCredentials) && authentication instanceof RestAuthentication) {
RestAuthentication<SimpleGrantedAuthority> restAuthentication =
(RestAuthentication<SimpleGrantedAuthority>)authentication;
restAuthentication.addAuthority(new SimpleGrantedAuthority("USER_CREDENTIALS"));
}
filterChain.doFilter(request, response);
}
}
请注意:每个过滤器如何不关心没有身份验证或身份验证不足时会发生什么。这一切都为你处理好了。您的过滤器只需验证正确的数据;
Spring、Spring Boot 和 Spring Security 有一些出色的测试设施。
我可以调用具有两种安全级别的 api-only 端点
mvc.perform(
post("/api-key-only")
.header("Authorization", "ApiKey this-is-a-valid-key")
.header("X-User-Credentials", "valid-user")
)
.andExpect(status().isOk())
.andExpect(authenticated()
.withAuthorities(
asList(
new SimpleGrantedAuthority("API_KEY"),
new SimpleGrantedAuthority("USER_CREDENTIALS")
)
)
)
.andExpect(content().string("API KEY ONLY"))
;
或者我可以通过第一级安全并被第二级拒绝
mvc.perform(
post("/dual-auth")
.header("Authorization", "ApiKey this-is-a-valid-key")
)
.andExpect(status().is4xxClientError())
.andExpect(authenticated()
.withAuthorities(
asList(
new SimpleGrantedAuthority("API_KEY")
)
)
)
;
当然,我们总是有幸福的道路
mvc.perform(
post("/dual-auth")
.header("Authorization", "ApiKey this-is-a-valid-key")
.header("X-User-Credentials", "valid-user")
)
.andExpect(status().isOk())
.andExpect(content().string("DUAL AUTH"))
.andExpect(authenticated()
.withAuthorities(
asList(
new SimpleGrantedAuthority("API_KEY"),
new SimpleGrantedAuthority("USER_CREDENTIALS")
)
)
)
;
推荐阅读
- python - 用布尔值 True 替换数字
- sql - Google Analytics BigQuery 按用户获取两个不同页面之间的时间差
- angular - 如何使用以下 HTTP 请求从服务器获取数据
- node.js - Wolkenkit 无法以“错误:无法获得最低处理位置”开始。
- python - 尝试从日期时间中减去天数并将其转换为字符串
- javascript - 如何处理 msToBlob 在 IE 上产生安全错误?
- node.js - 客户端无法连接到 ssl socket.io
- php - Google API 获取用户电子邮件 PHP
- java - GetCostAndUsageWithResources 方法在 JAVA SDK 中不可用
- visual-studio - Visual Studio .rc 文件编码问题