J. *_*old 5 java spring authorization spring-security spring-boot
我目前正在使用Spring Security开发 OAuth 2.0 登录/用户管理系统。由于我正在使用该模块编写自己的授权服务器(基于此处的文档)spring-security-oauth2-authorization-server,因此我还实现了用户管理/管理仪表板。
当然,用户管理的端点位于身份验证服务器上。因此,身份验证服务器充当授权服务器和(有点像)资源服务器。要授权用户使用管理仪表板,他们当然需要登录,因此首先他们被重定向到身份验证服务器/authorize端点,然后端点将他们重定向到登录菜单。然后正常执行授权码授予流程。但每个步骤都是在同一台服务器上完成的(即受保护的管理端点的身份验证和访问)!
由于以下问题,我很难将我们的身份验证服务器配置为身份验证服务器和资源服务器:
\n授权服务器将其保存securityContext到会话中。然后,会话 ID ( JSESSIONID) 将作为 cookie 保留在用户浏览器中。问题是,当用户尝试访问身份验证服务器上的安全端点(例如端点)时{...}/admin/users,仅 cookie 就足以授权他们向该端点发出请求。这意味着当应首先请求承载令牌来访问受保护端点时,可以绕过整个授权流程。我们希望只能通过不记名令牌访问安全端点,并且仅使用不记名令牌,而不是会话(或两者的组合)。
这是当前安全配置的简化版本:
\n@Bean\n@Order(1)\npublic CorsFilter corsFilter(CorsConfigurationSource corsConfigurationSource) {\n logger.info("Creating corsFilter bean");\n return new CorsFilter(corsConfigurationSource);\n}\n\n\n/**\n * Configures the authorization server endpoints.\n */\n@Bean\n@Order(2)\npublic SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, RegisteredClientRepository clientRepository) throws Exception {\n\n OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);\n\n http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)\n .registeredClientRepository(clientRepository) // autowired from ClientConfig.java\n .oidc(Customizer.withDefaults());\n\n http.exceptionHandling((exceptions) -> exceptions\n .defaultAuthenticationEntryPointFor(\n new LoginUrlAuthenticationEntryPoint("/login"),\n new MediaTypeRequestMatcher(MediaType.TEXT_HTML)\n )\n );\n\n http.oauth2ResourceServer((resourceServer) -> resourceServer\n .jwt(Customizer.withDefaults()));\n\n http.csrf(AbstractHttpConfigurer::disable);\n\n return http.build();\n}\n\n@Bean\n@Order(3)\npublic SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {\n http.securityMatcher(new NegatedRequestMatcher(new AntPathRequestMatcher("/admin/**")));\n\n http.authorizeHttpRequests((authorize) ->\n authorize\n .requestMatchers(new AntPathRequestMatcher("/register")).permitAll()\n .requestMatchers(new AntPathRequestMatcher("/recover")).permitAll()\n .requestMatchers(new AntPathRequestMatcher("/error/**")).permitAll()\n .requestMatchers(new AntPathRequestMatcher("/css/**")).permitAll()\n .requestMatchers(new AntPathRequestMatcher("/js/**")).permitAll()\n .requestMatchers(new AntPathRequestMatcher("/favicon.ico")).permitAll()\n .anyRequest().authenticated());\n\n http.oauth2ResourceServer((resourceServer) -> resourceServer\n .jwt(Customizer.withDefaults()));\n\n // set custom login form\n http.formLogin(form -> {\n form.loginPage("/login");\n form.permitAll();\n });\n\n http.logout(conf -> {\n // default logout url\n conf.logoutSuccessHandler(logoutSuccessHandler());\n });\n\n http.csrf(AbstractHttpConfigurer::disable);\n http.cors(AbstractHttpConfigurer::disable);\n\n return http.build();\n}\n\n@Bean\n@Order(4)\npublic SecurityFilterChain adminResourceFilterChain(HttpSecurity http) throws Exception {\n\n // handle out custom endpoints in this filter chain\n http.authorizeHttpRequests((authorize) ->\n authorize\n .requestMatchers(new AntPathRequestMatcher("/admin/**")).hasRole("ADMIN")\n .anyRequest().authenticated());\n\n http.sessionManagement(conf -> conf.sessionCreationPolicy(SessionCreationPolicy.STATELESS));\n\n http.oauth2ResourceServer((resourceServer) -> resourceServer\n .jwt(Customizer.withDefaults()));\n\n http.csrf(AbstractHttpConfigurer::disable);\n http.cors(AbstractHttpConfigurer::disable);\n\n return http.build();\n}\nRun Code Online (Sandbox Code Playgroud)\n如何配置授权服务器,以便管理端点的安全独立于会话的安全上下文?
\n出于披露目的,我进行了很多调试并基本上尝试了整个基础知识!我还尝试了其他一些事情:
\n根据Spring Security文档,可以.sessionManagement在安全配置中将其设置为STATELESS。我本来希望这可以解决问题,但在资源服务器过滤器链中设置它会导致另一个问题:将会话管理标志设置为 STATELESS 时,登录\xe2\x80\x99t 无法正确处理。从登录表单发出 POST 请求后,/authorize身份验证服务器不会重定向到请求中的 \xe2\x80\x9credirect_url\xe2\x80\x9d,而是重定向到 \xe2\x80\x9c/\xe2\x80\x9d\xe2 \x80\xa6?我认为这是因为身份验证服务器模块依赖于保存到会话中的某些过滤器的安全上下文。
我也遇到了 CORS 的一些问题,并认为这可能会导致这种情况。考虑到文档说:
\n\n\nCORS 必须在 Spring Security 之前处理,因为飞行前请求不会包含任何 cookie(即 JSESSIONID)。如果请求不包含任何cookie并且Spring Security是第一个,则该请求将确定用户未经过身份验证(因为请求中没有cookie)并拒绝它。
\n
这可以解释前端 Vue.js 应用程序无法正常工作,但不能解释通过 Postman 进行的调试调用。现在我停用了 CORS,以避免处理这些问题。
\n以下配置为一组管理端点设置了一个额外的过滤器链,排列在身份验证服务器端点和应用程序的其余部分(用户身份验证端点)之间。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults());
http
.exceptionHandling((exceptions) -> exceptions
.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
)
)
.oauth2ResourceServer((oauth2) -> oauth2
.jwt(Customizer.withDefaults())
);
return http.build();
}
@Bean
@Order(2)
public SecurityFilterChain adminSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.securityMatcher("/admin/**")
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(HttpMethod.GET).hasAuthority("SCOPE_admin:read")
.anyRequest().hasAuthority("SCOPE_admin:write")
)
.oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));
return http.build();
}
@Bean
@Order(3)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults);
return http.build();
}
}
Run Code Online (Sandbox Code Playgroud)
您可以简单地创建一个RegisteredClient提供admin:read和/或admin:write范围的。您应该使用普通的 OAuth2 流程(例如authorization_code或client_credentials)来获取具有该范围的访问令牌。您不想尝试自定义adminSecurityFilterChain来处理用户身份验证,这已经由defaultSecurityFilterChain.
如果您有 SPA(例如 Vue.js),它会成为 OAuth2 客户端来启动授权请求的重定向 ( GET /oauth2/authorize),或者更好的是应该使用会话与 BFF 交互,而根本不用担心访问令牌。无论哪种方式,管理 API 都可以通过不记名令牌访问,尽管这种模式应该很少使用。如果构建一个授权服务器产品(即类似于 keycloak 的产品),这可能是有意义的,但在微服务架构中,我建议将管理端点移至单独的微服务,这只会让事情变得更简单。
| 归档时间: |
|
| 查看次数: |
913 次 |
| 最近记录: |