New updated version of SecurityConfig

v2
pabloFuente 2025-11-08 19:17:49 +01:00
parent 6425eb8244
commit 36d1f3bd5b
2 changed files with 51 additions and 62 deletions

View File

@ -27,16 +27,19 @@ import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter; import org.springframework.web.filter.CorsFilter;
import io.openvidu.server.rest.ApiRestPathRewriteFilter;
import io.openvidu.server.rest.RequestMappings; import io.openvidu.server.rest.RequestMappings;
@Configuration() @Configuration
@Order(Ordered.LOWEST_PRECEDENCE) @Order(Ordered.LOWEST_PRECEDENCE)
public class SecurityConfig { public class SecurityConfig {
@ -49,55 +52,43 @@ public class SecurityConfig {
@Bean @Bean
@ConditionalOnMissingBean(name = "securityConfigPro") @ConditionalOnMissingBean(name = "securityConfigPro")
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// Configure CORS and CSRF return buildFilterChain(http);
}
protected SecurityFilterChain buildFilterChain(HttpSecurity http) throws Exception {
configureHttpSecurity(http); configureHttpSecurity(http);
applyHeaders(http);
// Configure authorization rules
configureAuthorization(http); configureAuthorization(http);
configureHttpBasic(http);
// Configure HTTP Basic authentication
http.httpBasic(httpBasic -> {});
return http.build(); return http.build();
} }
/**
* Configure CORS and CSRF settings. Can be overridden by subclasses.
*/
protected void configureHttpSecurity(HttpSecurity http) throws Exception { protected void configureHttpSecurity(HttpSecurity http) throws Exception {
http.cors(cors -> {}) // Uses below CorsFilter bean http.cors(cors -> {
.csrf(csrf -> csrf.disable()); // Rely on bean defined below
}).csrf(csrf -> csrf.disable());
}
protected void applyHeaders(HttpSecurity http) throws Exception {
// Default CE configuration does not customize headers
} }
/**
* Configure authorization rules for CE. Can be extended by PRO subclass.
*/
protected void configureAuthorization(HttpSecurity http) throws Exception { protected void configureAuthorization(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> { http.authorizeHttpRequests(auth -> configureAuthorizationRules(auth));
// 1. Public endpoints first (most specific)
configurePublicEndpoints(auth);
// 2. Protected endpoints second (less specific /api/**)
configureProtectedEndpoints(auth);
// 3. WebSocket endpoints last (least specific)
configureWebSocketEndpoints(auth);
});
} }
/** protected void configureAuthorizationRules(AuthorizeHttpRequestsConfigurer<?>.AuthorizationManagerRequestMatcherRegistry auth) {
* Configure public endpoints. Can be overridden by subclasses. configurePublicEndpoints(auth);
* MUST be called BEFORE configureProtectedEndpoints to ensure specific public paths configureProtectedEndpoints(auth);
* are not caught by broader protected patterns like /api/** configureWebSocketEndpoints(auth);
*/ configureDeprecatedApiEndpoints(auth);
protected void configurePublicEndpoints(org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer<?>.AuthorizationManagerRequestMatcherRegistry auth) { }
// Allow CORS preflight requests FIRST
auth.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll();
// Public API endpoints (must come before /api/** pattern) protected void configurePublicEndpoints(AuthorizeHttpRequestsConfigurer<?>.AuthorizationManagerRequestMatcherRegistry auth) {
auth.requestMatchers(HttpMethod.GET, RequestMappings.API + "/config/openvidu-publicurl").permitAll() auth.requestMatchers(HttpMethod.GET, RequestMappings.API + "/config/openvidu-publicurl").permitAll()
.requestMatchers(HttpMethod.HEAD, RequestMappings.API + "/config/openvidu-publicurl").permitAll() .requestMatchers(HttpMethod.HEAD, RequestMappings.API + "/config/openvidu-publicurl").permitAll()
.requestMatchers(HttpMethod.GET, RequestMappings.ACCEPT_CERTIFICATE).permitAll(); .requestMatchers(HttpMethod.GET, RequestMappings.ACCEPT_CERTIFICATE).permitAll();
// Secure recordings depending on OPENVIDU_RECORDING_PUBLIC_ACCESS
if (openviduConf.getOpenViduRecordingPublicAccess()) { if (openviduConf.getOpenViduRecordingPublicAccess()) {
auth.requestMatchers(HttpMethod.GET, RequestMappings.RECORDINGS + "/**").permitAll(); auth.requestMatchers(HttpMethod.GET, RequestMappings.RECORDINGS + "/**").permitAll();
} else { } else {
@ -105,24 +96,29 @@ public class SecurityConfig {
} }
} }
/** protected void configureProtectedEndpoints(AuthorizeHttpRequestsConfigurer<?>.AuthorizationManagerRequestMatcherRegistry auth) {
* Configure protected API endpoints. Can be extended by subclasses.
* MUST be called AFTER configurePublicEndpoints to avoid catching specific public paths.
*/
protected void configureProtectedEndpoints(org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer<?>.AuthorizationManagerRequestMatcherRegistry auth) {
// Protected API endpoints - uses broader patterns so must come AFTER public endpoints
auth.requestMatchers(RequestMappings.API + "/**").hasRole("ADMIN") auth.requestMatchers(RequestMappings.API + "/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.GET, RequestMappings.CDR + "/**").hasRole("ADMIN") .requestMatchers(HttpMethod.GET, RequestMappings.CDR + "/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.GET, RequestMappings.FRONTEND_CE + "/**").hasRole("ADMIN") .requestMatchers(HttpMethod.GET, RequestMappings.FRONTEND_CE + "/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.GET, RequestMappings.CUSTOM_LAYOUTS + "/**").hasRole("ADMIN"); .requestMatchers(HttpMethod.GET, RequestMappings.CUSTOM_LAYOUTS + "/**").hasRole("ADMIN");
} }
/** protected void configureWebSocketEndpoints(AuthorizeHttpRequestsConfigurer<?>.AuthorizationManagerRequestMatcherRegistry auth) {
* Configure WebSocket endpoints. Should be called last to avoid interfering with more specific rules. auth.requestMatchers(RequestMappings.WS_RPC, RequestMappings.WS_INFO).permitAll();
*/ }
protected void configureWebSocketEndpoints(org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer<?>.AuthorizationManagerRequestMatcherRegistry auth) {
// WebSocket endpoints: allow without authentication protected void configureDeprecatedApiEndpoints(AuthorizeHttpRequestsConfigurer<?>.AuthorizationManagerRequestMatcherRegistry auth) {
auth.requestMatchers("/openvidu", "/openvidu/info").permitAll(); if (Boolean.valueOf(environment.getProperty("SUPPORT_DEPRECATED_API"))) {
try {
ApiRestPathRewriteFilter.protectOldPathsCe(auth, openviduConf);
} catch (Exception exception) {
throw new RuntimeException(exception);
}
}
}
protected void configureHttpBasic(HttpSecurity http) throws Exception {
http.httpBasic(Customizer.withDefaults());
} }
@Bean @Bean

View File

@ -17,8 +17,7 @@ import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.web.util.WebUtils; import org.springframework.web.util.WebUtils;
import io.openvidu.server.config.OpenviduConfig; import io.openvidu.server.config.OpenviduConfig;
@ -116,29 +115,23 @@ public class ApiRestPathRewriteFilter implements Filter {
} }
} }
public static void protectOldPathsCe( public static void protectOldPathsCe(
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry conf, AuthorizeHttpRequestsConfigurer<?>.AuthorizationManagerRequestMatcherRegistry conf,
OpenviduConfig openviduConf) throws Exception { OpenviduConfig openviduConf) throws Exception {
conf.requestMatchers("/api/**").hasRole("ADMIN") conf.requestMatchers("/api/**").hasRole("ADMIN")
// /config
.requestMatchers(HttpMethod.GET, "/config/openvidu-publicurl").permitAll() .requestMatchers(HttpMethod.GET, "/config/openvidu-publicurl").permitAll()
.requestMatchers(HttpMethod.GET, "/config/**").hasRole("ADMIN") .requestMatchers(HttpMethod.GET, "/config/**").hasRole("ADMIN")
// /cdr
.requestMatchers(HttpMethod.GET, "/cdr/**").hasRole("ADMIN") .requestMatchers(HttpMethod.GET, "/cdr/**").hasRole("ADMIN")
// /accept-certificate
.requestMatchers(HttpMethod.GET, "/accept-certificate").permitAll() .requestMatchers(HttpMethod.GET, "/accept-certificate").permitAll()
// Dashboard
.requestMatchers(HttpMethod.GET, "/dashboard/**").hasRole("ADMIN"); .requestMatchers(HttpMethod.GET, "/dashboard/**").hasRole("ADMIN");
// Security for recording layouts
conf.requestMatchers("/layouts/**").hasRole("ADMIN"); conf.requestMatchers("/layouts/**").hasRole("ADMIN");
// Security for recorded video files
if (openviduConf.getOpenViduRecordingPublicAccess()) { if (openviduConf.getOpenViduRecordingPublicAccess()) {
conf = conf.requestMatchers("/recordings/**").permitAll(); conf.requestMatchers("/recordings/**").permitAll();
} else { } else {
conf = conf.requestMatchers("/recordings/**").hasRole("ADMIN"); conf.requestMatchers("/recordings/**").hasRole("ADMIN");
} }
} }