spring-boot - 如何在 Spring Boot 应用程序中添加自定义 OpenId 过滤器?
问题描述
我正在尝试实现 OpenId Connect 身份验证的后端。它是一个无状态 API,所以我添加了一个过滤器来处理 Bearer 令牌。
我创建了处理身份验证的 OpenIdConnect 过滤器并将其添加到 WebSecurityConfigurerAdapter 中。
public class OpenIdConnectFilter extends
AbstractAuthenticationProcessingFilter {
@Value("${auth0.clientId}")
private String clientId;
@Value("${auth0.issuer}")
private String issuer;
@Value("${auth0.keyUrl}")
private String jwkUrl;
private TokenExtractor tokenExtractor = new BearerTokenExtractor();
public OpenIdConnectFilter() {
super("/connect/**");
setAuthenticationManager(new NoopAuthenticationManager());
}
@Bean
public FilterRegistrationBean registration(OpenIdConnectFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
try {
Authentication authentication = tokenExtractor.extract(request);
String accessToken = (String) authentication.getPrincipal();
String kid = JwtHelper.headers(accessToken)
.get("kid");
final Jwt tokenDecoded = JwtHelper.decodeAndVerify(accessToken, verifier(kid));
final Map<String, Object> authInfo = new ObjectMapper().readValue(tokenDecoded.getClaims(), Map.class);
verifyClaims(authInfo);
Set<String> scopes = new HashSet<String>(Arrays.asList(((String) authInfo.get("scope")).split(" ")));
int expires = (Integer) authInfo.get("exp");
OpenIdToken openIdToken = new OpenIdToken(accessToken, scopes, Long.valueOf(expires), authInfo);
final OpenIdUserDetails user = new OpenIdUserDetails((String) authInfo.get("sub"), "Test", openIdToken);
return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
} catch (final Exception e) {
throw new BadCredentialsException("Could not obtain user details from token", e);
}
}
public void verifyClaims(Map claims) {
int exp = (int) claims.get("exp");
Date expireDate = new Date(exp * 1000L);
Date now = new Date();
if (expireDate.before(now) || !claims.get("iss").equals(issuer) || !claims.get("azp").equals(clientId)) {
throw new RuntimeException("Invalid claims");
}
}
private RsaVerifier verifier(String kid) throws Exception {
JwkProvider provider = new UrlJwkProvider(new URL(jwkUrl));
Jwk jwk = provider.get(kid);
return new RsaVerifier((RSAPublicKey) jwk.getPublicKey());
}
这是安全配置:
@Configuration
@EnableWebSecurity
public class OpenIdConnectWebServerConfig extends
WebSecurityConfigurerAdapter {
@Bean
public OpenIdConnectFilter myFilter() {
final OpenIdConnectFilter filter = new OpenIdConnectFilter();
return filter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors();
http.antMatcher("/connect/**").authorizeRequests()
.antMatchers(HttpMethod.GET, "/connect/public").permitAll()
.antMatchers(HttpMethod.GET, "/connect/private").authenticated()
.antMatchers(HttpMethod.GET, "/connect/private-
messages").hasAuthority("read:messages")
.antMatchers(HttpMethod.GET, "/connect/private-
roles").hasAuthority("read:roles")
.and()
.addFilterBefore(myFilter(),
UsernamePasswordAuthenticationFilter.class);
}
休息端点如下所示:
@RequestMapping(value = "/connect/public", method = RequestMethod.GET,
produces = "application/json")
@ResponseBody
public String publicEndpoint() throws JSONException {
return new JSONObject()
.put("message", "All good. You DO NOT need to be authenticated to
call /api/public.")
.toString();
}
@RequestMapping(value = "/connect/private", method = RequestMethod.GET,
produces = "application/json")
@ResponseBody
public String privateEndpoint() throws JSONException {
return new JSONObject()
.put("message", "All good. You can see this because you are
Authenticated.")
.toString();
}
如果我完全删除配置过滤器以及 @Bean 定义,则配置按预期工作: /connect/public 可访问,而 /connect/private 被禁止。
如果我保留@Bean 定义并将其添加到过滤器链中,则响应会为 /connect/public 和 /connect/private 上的请求返回 Not Found 状态:
"timestamp": "18.01.2019 09:46:11",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/
调试时我注意到过滤器正在处理令牌并返回身份验证的实现。
- 过滤器是否正确添加到过滤器链中并且位置正确?
为什么应该在 /connect/public 路径上调用过滤器,而这应该是公共的。它是否应用于匹配 super("/connect/**") 调用的所有路径?
为什么在 /connect/private 发出请求时将路径返回为“/”
似乎过滤器有问题,因为每次应用它时,响应都搞砸了。
解决方案
推荐阅读
- json - jq 在管道输出时打印帮助
- excel - Excel 2016 错误?
- angularjs - 验证 ng-repeat 表中的选择选项
- python - 使用保存的 scikit learn 模型查看模型中的类
- angular - 在角度 4 中单击单选按钮时如何禁用文本字段
- javascript - 图像未在 codeigniter 项目的实时服务器中上传,它给出 404 和 500 错误
- cmake - 如何将 cmake 变量设置为目标的 URL
- javascript - 图像重影效果
- extjs - 修改 ExtNet 中的 GridFilters
- c - inotify_add_watch 是否按升序返回监视描述符?