From 1074ee86827c53a31dbfe6cb3e4e757010734308 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 29 Apr 2024 19:58:24 +0200 Subject: [PATCH 1/4] fix with security matchers --- .../ThingsboardSecurityConfiguration.java | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index eda8d3e89d..03069861ac 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -72,12 +72,11 @@ public class ThingsboardSecurityConfiguration { public static final String JWT_TOKEN_HEADER_PARAM_V2 = "Authorization"; public static final String JWT_TOKEN_QUERY_PARAM = "token"; - public static final String WEBJARS_ENTRY_POINT = "/webjars/**"; public static final String DEVICE_API_ENTRY_POINT = "/api/v1/**"; public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login"; public static final String PUBLIC_LOGIN_ENTRY_POINT = "/api/auth/login/public"; public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token"; - protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/webjars/**", "/api/license/**", "/api/images/public/**"}; + protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[]{"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/webjars/**", "/api/license/**", "/api/images/public/**"}; public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**"; public static final String WS_ENTRY_POINT = "/api/ws/**"; public static final String MAIL_OAUTH2_PROCESSING_ENTRY_POINT = "/api/admin/mail/oauth2/code"; @@ -153,7 +152,7 @@ public class ThingsboardSecurityConfiguration { protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception { List pathsToSkip = new ArrayList<>(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS)); pathsToSkip.addAll(Arrays.asList(WS_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT, - PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT, MAIL_OAUTH2_PROCESSING_ENTRY_POINT, + PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, MAIL_OAUTH2_PROCESSING_ENTRY_POINT, DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT)); SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT); JwtTokenAuthenticationProcessingFilter filter @@ -201,7 +200,31 @@ public class ThingsboardSecurityConfiguration { } @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + @Order(1) + public SecurityFilterChain noAuthFilterChain(HttpSecurity http) throws Exception { + http.headers(headers -> headers + .cacheControl(config -> {}) + .frameOptions(config -> {}).disable()) + .cors(cors -> {}) + .csrf(AbstractHttpConfigurer::disable) + .exceptionHandling(config -> {}) + .securityMatchers(config -> config + .requestMatchers( + DEVICE_API_ENTRY_POINT, // Device HTTP Transport API + FORM_BASED_LOGIN_ENTRY_POINT, // Login end-point + PUBLIC_LOGIN_ENTRY_POINT, // Public login end-point + TOKEN_REFRESH_ENTRY_POINT, // Token refresh end-point + MAIL_OAUTH2_PROCESSING_ENTRY_POINT, // Mail oauth2 code processing url + DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT, // Device connectivity certificate (public) + WS_ENTRY_POINT) // WebSocket API End-points + .requestMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS)) // static resources, user activation and password reset end-points + .authorizeHttpRequests(config -> config.anyRequest().permitAll()); + return http.build(); + } + + @Bean + @Order(2) + SecurityFilterChain authFilterChain(HttpSecurity http) throws Exception { http.headers(headers -> headers .cacheControl(config -> {}) .frameOptions(config -> {}).disable()) @@ -209,19 +232,8 @@ public class ThingsboardSecurityConfiguration { .csrf(AbstractHttpConfigurer::disable) .exceptionHandling(config -> {}) .sessionManagement(config -> config.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(config -> config - .requestMatchers(WEBJARS_ENTRY_POINT).permitAll() // Webjars - .requestMatchers(DEVICE_API_ENTRY_POINT).permitAll() // Device HTTP Transport API - .requestMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point - .requestMatchers(PUBLIC_LOGIN_ENTRY_POINT).permitAll() // Public login end-point - .requestMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point - .requestMatchers(MAIL_OAUTH2_PROCESSING_ENTRY_POINT).permitAll() // Mail oauth2 code processing url - .requestMatchers(DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT).permitAll() // Device connectivity certificate (public) - .requestMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points - .requestMatchers(WS_ENTRY_POINT).permitAll() // Protected WebSocket API End-points - .requestMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated()) // Protected API End-points - .formLogin(form -> form - .loginPage("/login").permitAll()) + .securityMatcher(TOKEN_BASED_AUTH_ENTRY_POINT) // Protected API End-points + .authorizeHttpRequests(config -> config.anyRequest().authenticated()) .exceptionHandling(config -> config.accessDeniedHandler(restAccessDeniedHandler)) .addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) From 0ffd0cca40f4c2f4e82f325697c6dde799ffbc0e Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 29 Apr 2024 22:58:11 +0200 Subject: [PATCH 2/4] added corresponding tests and refactoring --- .../ThingsboardSecurityConfiguration.java | 39 +++++++++---------- .../server/controller/AuthControllerTest.java | 6 +++ 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index 03069861ac..898ce26883 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -202,12 +202,7 @@ public class ThingsboardSecurityConfiguration { @Bean @Order(1) public SecurityFilterChain noAuthFilterChain(HttpSecurity http) throws Exception { - http.headers(headers -> headers - .cacheControl(config -> {}) - .frameOptions(config -> {}).disable()) - .cors(cors -> {}) - .csrf(AbstractHttpConfigurer::disable) - .exceptionHandling(config -> {}) + configureCommonHttpSecurity(http) .securityMatchers(config -> config .requestMatchers( DEVICE_API_ENTRY_POINT, // Device HTTP Transport API @@ -225,21 +220,8 @@ public class ThingsboardSecurityConfiguration { @Bean @Order(2) SecurityFilterChain authFilterChain(HttpSecurity http) throws Exception { - http.headers(headers -> headers - .cacheControl(config -> {}) - .frameOptions(config -> {}).disable()) - .cors(cors -> {}) - .csrf(AbstractHttpConfigurer::disable) - .exceptionHandling(config -> {}) - .sessionManagement(config -> config.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .securityMatcher(TOKEN_BASED_AUTH_ENTRY_POINT) // Protected API End-points - .authorizeHttpRequests(config -> config.anyRequest().authenticated()) - .exceptionHandling(config -> config.accessDeniedHandler(restAccessDeniedHandler)) - .addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) - .addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) - .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class) - .addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class) - .addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class); + configureCommonHttpSecurity(http) + .securityMatcher(TOKEN_BASED_AUTH_ENTRY_POINT); // Protected API End-points if (oauth2Configuration != null) { http.oauth2Login(login -> login .authorizationEndpoint(config -> config @@ -253,6 +235,21 @@ public class ThingsboardSecurityConfiguration { return http.build(); } + private HttpSecurity configureCommonHttpSecurity(HttpSecurity http) throws Exception { + return http.headers(headers -> headers + .cacheControl(config -> {}) + .frameOptions(config -> {}).disable()) + .cors(cors -> {}) + .csrf(AbstractHttpConfigurer::disable) + .exceptionHandling(config -> {}) + .sessionManagement(config -> config.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class) + .addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class); + } + @Bean @ConditionalOnMissingBean(CorsFilter.class) public CorsFilter corsFilter(@Autowired MvcCorsProperties mvcCorsProperties) { diff --git a/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java index 5365c9fbec..1cf389a93d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java @@ -167,4 +167,10 @@ public class AuthControllerTest extends AbstractControllerTest { .andExpect(jsonPath("$.message", is("Password must be no more than 72 characters in length."))); } + + @Test + public void testGetPageWithoutRedirect() throws Exception { + doGet("/login").andExpect(status().isOk()); + doGet("/home").andExpect(status().isOk()); + } } From 8b135ba6ff3a1887ce5c8b0b37b3f16d107250a9 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 30 Apr 2024 08:19:05 +0200 Subject: [PATCH 3/4] added missing config --- .../server/config/ThingsboardSecurityConfiguration.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index 898ce26883..f83f335970 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -221,7 +221,8 @@ public class ThingsboardSecurityConfiguration { @Order(2) SecurityFilterChain authFilterChain(HttpSecurity http) throws Exception { configureCommonHttpSecurity(http) - .securityMatcher(TOKEN_BASED_AUTH_ENTRY_POINT); // Protected API End-points + .securityMatcher(TOKEN_BASED_AUTH_ENTRY_POINT) // Protected API End-points + .authorizeHttpRequests(config -> config.anyRequest().authenticated()); if (oauth2Configuration != null) { http.oauth2Login(login -> login .authorizationEndpoint(config -> config @@ -241,8 +242,8 @@ public class ThingsboardSecurityConfiguration { .frameOptions(config -> {}).disable()) .cors(cors -> {}) .csrf(AbstractHttpConfigurer::disable) - .exceptionHandling(config -> {}) .sessionManagement(config -> config.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .exceptionHandling(config -> config.accessDeniedHandler(restAccessDeniedHandler)) .addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class) From 888028ede4449ac560f419e376e14478e9a7e51d Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 30 Apr 2024 15:18:00 +0200 Subject: [PATCH 4/4] Reverted some changes and added anyRequest permitAll --- .../ThingsboardSecurityConfiguration.java | 50 +++++++------------ 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index f83f335970..6c90e6e27d 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -200,10 +200,16 @@ public class ThingsboardSecurityConfiguration { } @Bean - @Order(1) - public SecurityFilterChain noAuthFilterChain(HttpSecurity http) throws Exception { - configureCommonHttpSecurity(http) - .securityMatchers(config -> config + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.headers(headers -> headers + .cacheControl(config -> {}) + .frameOptions(config -> {}).disable()) + .cors(cors -> {}) + .csrf(AbstractHttpConfigurer::disable) + .exceptionHandling(config -> {}) + .sessionManagement(config -> config.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(config -> config + .requestMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points (webjars included) .requestMatchers( DEVICE_API_ENTRY_POINT, // Device HTTP Transport API FORM_BASED_LOGIN_ENTRY_POINT, // Login end-point @@ -211,18 +217,15 @@ public class ThingsboardSecurityConfiguration { TOKEN_REFRESH_ENTRY_POINT, // Token refresh end-point MAIL_OAUTH2_PROCESSING_ENTRY_POINT, // Mail oauth2 code processing url DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT, // Device connectivity certificate (public) - WS_ENTRY_POINT) // WebSocket API End-points - .requestMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS)) // static resources, user activation and password reset end-points - .authorizeHttpRequests(config -> config.anyRequest().permitAll()); - return http.build(); - } - - @Bean - @Order(2) - SecurityFilterChain authFilterChain(HttpSecurity http) throws Exception { - configureCommonHttpSecurity(http) - .securityMatcher(TOKEN_BASED_AUTH_ENTRY_POINT) // Protected API End-points - .authorizeHttpRequests(config -> config.anyRequest().authenticated()); + WS_ENTRY_POINT).permitAll() // Protected WebSocket API End-points + .requestMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected API End-points + .anyRequest().permitAll()) + .exceptionHandling(config -> config.accessDeniedHandler(restAccessDeniedHandler)) + .addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class) + .addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class); if (oauth2Configuration != null) { http.oauth2Login(login -> login .authorizationEndpoint(config -> config @@ -236,21 +239,6 @@ public class ThingsboardSecurityConfiguration { return http.build(); } - private HttpSecurity configureCommonHttpSecurity(HttpSecurity http) throws Exception { - return http.headers(headers -> headers - .cacheControl(config -> {}) - .frameOptions(config -> {}).disable()) - .cors(cors -> {}) - .csrf(AbstractHttpConfigurer::disable) - .sessionManagement(config -> config.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .exceptionHandling(config -> config.accessDeniedHandler(restAccessDeniedHandler)) - .addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) - .addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) - .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class) - .addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class) - .addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class); - } - @Bean @ConditionalOnMissingBean(CorsFilter.class) public CorsFilter corsFilter(@Autowired MvcCorsProperties mvcCorsProperties) {