我想使用 Keycloak 保护我的 Spring Backend。我有一个安全的控制器,我设法使它成为可能,以便您可以通过浏览器访问它,并且必须使用 keycloak SSO 登录,或者您可以使用 postman 并使用它发送 JWT 承载令牌。
但是如果我使用选项 1,我就不能使用 postman,它只会返回登录页面的 html。另一方面,如果我使用选项 2,我只能使用 postman,而不能使用浏览器访问端点。是否有可能使端点可以双向访问?
也许@Ch4mp有任何线索?
我尝试了不同的安全配置,但我不知道是否可以混合它们:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
interface AuthoritiesConverter extends Converter<Map<String, Object>, Collection<GrantedAuthority>> {}
@Bean
AuthoritiesConverter realmRolesAuthoritiesConverter() {
return claims -> {
final var realmAccess = Optional.ofNullable((Map<String, Object>) claims.get("realm_access"));
final var roles =
realmAccess.flatMap(map -> Optional.ofNullable((List<String>) map.get("roles")));
return roles.map(List::stream).orElse(Stream.empty()).map(SimpleGrantedAuthority::new)
.map(GrantedAuthority.class::cast).toList();
};
}
// @Bean
// GrantedAuthoritiesMapper authenticationConverter(
// Converter<Map<String, Object>, Collection<GrantedAuthority>> realmRolesAuthoritiesConverter) {
// return (authorities) -> authorities.stream()
// .filter(authority -> authority instanceof OidcUserAuthority)
// .map(OidcUserAuthority.class::cast).map(OidcUserAuthority::getIdToken)
// .map(OidcIdToken::getClaims).map(realmRolesAuthoritiesConverter::convert)
// .flatMap(roles -> roles.stream()).collect(Collectors.toSet());
// }
//
@Bean
JwtAuthenticationConverter authenticationConverterJWT(
Converter<Map<String, Object>, Collection<GrantedAuthority>> authoritiesConverter) {
var authenticationConverter = new JwtAuthenticationConverter();
authenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> {
return authoritiesConverter.convert(jwt.getClaims());
});
return authenticationConverter;
}
// @Bean
// SecurityFilterChain clientSecurityFilterChain(HttpSecurity http,
// ClientRegistrationRepository clientRegistrationRepository) throws Exception {
// http.oauth2Login(Customizer.withDefaults());
// http.logout((logout) -> {
// final var logoutSuccessHandler =
// new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
// logoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}/");
// logout.logoutSuccessHandler(logoutSuccessHandler);
// });
//
// http.authorizeHttpRequests(requests -> {
// requests.requestMatchers("/", "/favicon.ico", "/v3/api-docs/**","/swagger-ui/**", "/swagger-ui.html" ).permitAll();
//
// requests.anyRequest().authenticated();
// });
//
// return http.build();
// }
//
@Bean
SecurityFilterChain resourceServerSecurityFilterChain(
HttpSecurity http,
Converter<Jwt, AbstractAuthenticationToken> authenticationConverter) throws Exception {
http.oauth2ResourceServer(resourceServer -> {
resourceServer.jwt(jwtDecoder -> {
jwtDecoder.jwtAuthenticationConverter(authenticationConverter);
});
});
http.sessionManagement(sessions -> {
sessions.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}).csrf(csrf -> {
csrf.disable();
});
http.authorizeHttpRequests(requests -> {
requests.requestMatchers("/", "/favicon.ico", "/v3/api-docs/**","/swagger-ui/**", "/swagger-ui.html" ).permitAll();
requests.anyRequest().authenticated();
});
return http.build();
}
}
编辑: Ch4mp 给出了正确的解决方案,可以使用具有不同优先级的多个安全过滤器。然后,您可以检查标头是否包含 Bearer 令牌并使用资源服务器安全链,否则使用 oauth2Login
@Bean
@Order(Ordered.LOWEST_PRECEDENCE)
SecurityFilterChain clientSecurityFilterChain(HttpSecurity http,
ClientRegistrationRepository clientRegistrationRepository) throws Exception {
http.oauth2Login(Customizer.withDefaults());
http.logout((logout) -> {
final var logoutSuccessHandler =
new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
logoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}/");
logout.logoutSuccessHandler(logoutSuccessHandler);
});
http.authorizeHttpRequests(requests -> {
requests.requestMatchers("/", "/favicon.ico", "/v3/api-docs/**","/swagger-ui/**", "/swagger-ui.html" ).permitAll();
requests.anyRequest().authenticated();
});
return http.build();
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE - 1)
SecurityFilterChain resourceServerSecurityFilterChain(
HttpSecurity http,
Converter<Jwt, AbstractAuthenticationToken> authenticationConverter) throws Exception {
http.securityMatcher((HttpServletRequest request) -> {
return Optional.ofNullable(request.getHeader(HttpHeaders.AUTHORIZATION)).map(h -> {
return h.toLowerCase().startsWith("bearer ");
}).orElse(false);
});
http.oauth2ResourceServer(resourceServer -> {
resourceServer.jwt(jwtDecoder -> {
jwtDecoder.jwtAuthenticationConverter(authenticationConverter);
});
});
http.sessionManagement(sessions -> {
sessions.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}).csrf(csrf -> {
csrf.disable();
});
http.authorizeHttpRequests(requests -> {
requests.requestMatchers("/", "/favicon.ico", "/v3/api-docs/**","/swagger-ui/**", "/swagger-ui.html" ).permitAll();
requests.anyRequest().authenticated();
});
return http.build();
}