我对 Spring Boot 和 Keycloak 的多领域使用有疑问。
我在我的应用程序中配置 OAuth2 有 2 个这样的领域:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8080/auth/realms/realmuser
client:
provider:
realmuser:
props: ...
realmadmin:
props: ...
registration:
realmuser:
props: ...
realmadmin:
props: ...
Spring Security 涵盖了我的应用程序中的多个端点。
当我向他们中的任何一个发出请求时,我会看到 Spring OAuth2 登录页面,建议我在其中选择“realmuser”或“realmadmin”进行登录。之后一切正常。
但我的目标是按领域划分端点。
例如,我希望将端点“/v1/admin/etc..”重定向到“realmadmin”,将端点“/v1/user/etc..”立即重定向到“realmuser”,即没有 Spring OAuth2 登录页面。
有什么办法可以达到这个目的吗?
我的安全配置:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/v1/*")
.hasRole("USER")
.anyRequest()
.authenticated()
.and()
.cors()
.and()
.csrf()
.disable();
http.oauth2Login()
.and()
.logout()
.addLogoutHandler(keycloakLogoutHandler)
.logoutSuccessUrl("/");
http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
我尝试做的事情:
使用 JwtIssuerAuthenticationManagerResolver 通过声明解决租户,如 docs.spring.io - Multinenancy OAuth 2.0 资源服务器中所述
使用注解@RegisteredOAuth2AuthorizedClient
更新 SecurityConfig
http.oauth2Login(oauth2Login ->
oauth2Login
.authorizationEndpoint(authorizationEndpoint ->
authorizationEndpoint
.baseUri("/oauth2/authorization")
.authorizationRequestResolver(new CustomAuthorizationRequestResolver("/oauth2/authorization/realmuser",
"/oauth2/authorization/realmadmin", clientRegistrationRepository()))
)
)
然后创建类
{
CustomAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver
//...
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
String authorizationUri;
if (request.getServletPath().startsWith("/v1/admin")) {
authorizationUri = adminAuthorizationUri;
} else if (request.getServletPath().startsWith("/v1/user")) {
authorizationUri = userAuthorizationUri;
} else {
authorizationUri = "/oauth2/authorize";
}
OAuth2AuthorizationRequest authorizationRequest = defaultAuthorizationRequestResolver.resolve(request);
return authorizationRequest != null ? OAuth2AuthorizationRequest.from(authorizationRequest)
.authorizationUri(authorizationUri)
.build() : null;
}
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) {
return defaultAuthorizationRequestResolver.resolve(request, clientRegistrationId);
}
}
他们都没有帮助我解决我的问题。
客户端和资源服务器配置非常不同:对 OAuth2 客户端的请求使用会话保护,而对资源服务器的请求使用访问令牌保护(这使得摆脱会话成为可能)。
oauth2Login
、logout
和所有 spring.security.oauth2.client
属性都是客户端特征。在上面的配置中,客户端配置启用并需要会话。 在 OAuth2 客户端上禁用 CSRF 保护是非常不安全的,不应该这样做.
如果您检查浏览器发送的请求,您将找不到任何访问令牌。您只会找到会话 cookie。您必须使用基于 Javascript 的框架(Angular、Vue、React、NextJS 等)并将其配置为公共 OAuth2 客户端(在库的帮助下),以便使用访问令牌授权浏览器请求。另一种(最近首选的)保护从 Javascript 应用程序到资源服务器的请求的方法是在服务器上插入一个中间件(Backend F 或 Frontend)以从 Javascript 代码中隐藏 OAuth2 令牌。在此架构中,BFF 是 OAuth2 客户端,负责在将请求从浏览器转发到 REST API 之前用访问令牌替换会话 cookie。
资源服务器不关心登录、注销或如何获取或保留令牌。对它来说重要的是一个请求是否被访问令牌授权,这个访问令牌是否有效,它是否是由它信任的授权服务器发出的,以及他是否应该根据令牌声明授予对所请求资源的访问权限。
上面的资源服务器配置大部分是无效的,只会接受
realmuser
发出的令牌(前提是您编写了一个发送此类令牌的客户端,因为如上所述,如果您的浏览器使用Spring的oauth2Login
,则不会) .
所以,您应该回答的第一个问题是:
一旦你对 OAuth2 客户端和资源服务器的需求有了更清晰的认识(并了解两者的安全性有多么不同),我建议你参考我写的教程,都支持多个授权服务器(或领域) .