Merge pull request #13347 from thingsboard/fix/auth-db-error
Handle uncaught auth exceptions
This commit is contained in:
		
						commit
						f03f41fb72
					
				@ -47,6 +47,7 @@ import org.springframework.web.filter.ShallowEtagHeaderFilter;
 | 
			
		||||
import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
 | 
			
		||||
import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
 | 
			
		||||
import org.thingsboard.server.queue.util.TbCoreComponent;
 | 
			
		||||
import org.thingsboard.server.service.security.auth.AuthExceptionHandler;
 | 
			
		||||
import org.thingsboard.server.service.security.auth.jwt.JwtAuthenticationProvider;
 | 
			
		||||
import org.thingsboard.server.service.security.auth.jwt.JwtTokenAuthenticationProcessingFilter;
 | 
			
		||||
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenAuthenticationProvider;
 | 
			
		||||
@ -129,6 +130,9 @@ public class ThingsboardSecurityConfiguration {
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RateLimitProcessingFilter rateLimitProcessingFilter;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private AuthExceptionHandler authExceptionHandler;
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    protected PayloadSizeFilter payloadSizeFilter() {
 | 
			
		||||
        return new PayloadSizeFilter(maxPayloadSizeConfig);
 | 
			
		||||
@ -235,7 +239,8 @@ public class ThingsboardSecurityConfiguration {
 | 
			
		||||
                .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
 | 
			
		||||
                .addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
 | 
			
		||||
                .addFilterBefore(payloadSizeFilter(), UsernamePasswordAuthenticationFilter.class)
 | 
			
		||||
                .addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class);
 | 
			
		||||
                .addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class)
 | 
			
		||||
                .addFilterBefore(authExceptionHandler, buildRestLoginProcessingFilter().getClass());
 | 
			
		||||
        if (oauth2Configuration != null) {
 | 
			
		||||
            http.oauth2Login(login -> login
 | 
			
		||||
                    .authorizationEndpoint(config -> config
 | 
			
		||||
 | 
			
		||||
@ -437,14 +437,7 @@ public abstract class BaseController {
 | 
			
		||||
        } else if (exception instanceof AsyncRequestTimeoutException) {
 | 
			
		||||
            return new ThingsboardException("Request timeout", ThingsboardErrorCode.GENERAL);
 | 
			
		||||
        } else if (exception instanceof DataAccessException) {
 | 
			
		||||
            if (!logControllerErrorStackTrace) { // not to log the error twice
 | 
			
		||||
                log.warn("Database error: {} - {}", exception.getClass().getSimpleName(), ExceptionUtils.getRootCauseMessage(exception));
 | 
			
		||||
            }
 | 
			
		||||
            if (cause instanceof ConstraintViolationException) {
 | 
			
		||||
                return new ThingsboardException(ExceptionUtils.getRootCause(exception).getMessage(), ThingsboardErrorCode.BAD_REQUEST_PARAMS);
 | 
			
		||||
            } else {
 | 
			
		||||
                return new ThingsboardException("Database error", ThingsboardErrorCode.GENERAL);
 | 
			
		||||
            }
 | 
			
		||||
            return new ThingsboardException(exception, ThingsboardErrorCode.DATABASE);
 | 
			
		||||
        } else if (exception instanceof EntityVersionMismatchException) {
 | 
			
		||||
            return new ThingsboardException(exception.getMessage(), exception, ThingsboardErrorCode.VERSION_CONFLICT);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,9 @@ import jakarta.servlet.http.HttpServletRequest;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.apache.commons.lang3.exception.ExceptionUtils;
 | 
			
		||||
import org.hibernate.exception.ConstraintViolationException;
 | 
			
		||||
import org.springframework.boot.web.servlet.error.ErrorController;
 | 
			
		||||
import org.springframework.dao.DataAccessException;
 | 
			
		||||
import org.springframework.http.HttpHeaders;
 | 
			
		||||
import org.springframework.http.HttpStatus;
 | 
			
		||||
import org.springframework.http.HttpStatusCode;
 | 
			
		||||
@ -139,6 +141,8 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand
 | 
			
		||||
                    ThingsboardException thingsboardException = (ThingsboardException) exception;
 | 
			
		||||
                    if (thingsboardException.getErrorCode() == ThingsboardErrorCode.SUBSCRIPTION_VIOLATION) {
 | 
			
		||||
                        handleSubscriptionException((ThingsboardException) exception, response);
 | 
			
		||||
                    } else if (thingsboardException.getErrorCode() == ThingsboardErrorCode.DATABASE) {
 | 
			
		||||
                        handleDatabaseException(thingsboardException.getCause(), response);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        handleThingsboardException((ThingsboardException) exception, response);
 | 
			
		||||
                    }
 | 
			
		||||
@ -148,8 +152,10 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand
 | 
			
		||||
                    handleAccessDeniedException(response);
 | 
			
		||||
                } else if (exception instanceof AuthenticationException) {
 | 
			
		||||
                    handleAuthenticationException((AuthenticationException) exception, response);
 | 
			
		||||
                }  else if (exception instanceof MaxPayloadSizeExceededException) {
 | 
			
		||||
                } else if (exception instanceof MaxPayloadSizeExceededException) {
 | 
			
		||||
                    handleMaxPayloadSizeExceededException(response, (MaxPayloadSizeExceededException) exception);
 | 
			
		||||
                } else if (exception instanceof DataAccessException e) {
 | 
			
		||||
                    handleDatabaseException(e, response);
 | 
			
		||||
                } else {
 | 
			
		||||
                    response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
 | 
			
		||||
                    JacksonUtil.writeValue(response.getWriter(), ThingsboardErrorResponse.of(exception.getMessage(),
 | 
			
		||||
@ -201,6 +207,17 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand
 | 
			
		||||
                JacksonUtil.fromBytes(((HttpClientErrorException) subscriptionException.getCause()).getResponseBodyAsByteArray(), Object.class));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void handleDatabaseException(Throwable databaseException, HttpServletResponse response) throws IOException {
 | 
			
		||||
        ThingsboardErrorResponse errorResponse;
 | 
			
		||||
        if (databaseException instanceof ConstraintViolationException) {
 | 
			
		||||
            errorResponse = ThingsboardErrorResponse.of(ExceptionUtils.getRootCause(databaseException).getMessage(), ThingsboardErrorCode.BAD_REQUEST_PARAMS, HttpStatus.BAD_REQUEST);
 | 
			
		||||
        } else {
 | 
			
		||||
            log.warn("Database error: {} - {}", databaseException.getClass().getSimpleName(), ExceptionUtils.getRootCauseMessage(databaseException));
 | 
			
		||||
            errorResponse = ThingsboardErrorResponse.of("Database error", ThingsboardErrorCode.DATABASE, HttpStatus.INTERNAL_SERVER_ERROR);
 | 
			
		||||
        }
 | 
			
		||||
        writeResponse(errorResponse, response);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void handleAccessDeniedException(HttpServletResponse response) throws IOException {
 | 
			
		||||
        response.setStatus(HttpStatus.FORBIDDEN.value());
 | 
			
		||||
        JacksonUtil.writeValue(response.getWriter(),
 | 
			
		||||
@ -233,4 +250,11 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO: refactor this class to use this method instead of boilerplate JacksonUtil.writeValue(response.getWriter(), ...
 | 
			
		||||
    private void writeResponse(ThingsboardErrorResponse errorResponse, HttpServletResponse response) throws IOException {
 | 
			
		||||
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
 | 
			
		||||
        response.setStatus(errorResponse.getStatus());
 | 
			
		||||
        JacksonUtil.writeValue(response.getWriter(), errorResponse);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,46 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2025 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.service.security.auth;
 | 
			
		||||
 | 
			
		||||
import jakarta.servlet.FilterChain;
 | 
			
		||||
import jakarta.servlet.http.HttpServletRequest;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.springframework.security.core.AuthenticationException;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
import org.springframework.web.filter.OncePerRequestFilter;
 | 
			
		||||
import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
 | 
			
		||||
 | 
			
		||||
@Component
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class AuthExceptionHandler extends OncePerRequestFilter {
 | 
			
		||||
 | 
			
		||||
    private final ThingsboardErrorResponseHandler errorResponseHandler;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
 | 
			
		||||
        try {
 | 
			
		||||
            filterChain.doFilter(request, response);
 | 
			
		||||
        } catch (AuthenticationException e) {
 | 
			
		||||
            throw e;
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            errorResponseHandler.handle(e, response);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -31,7 +31,8 @@ public enum ThingsboardErrorCode {
 | 
			
		||||
    TOO_MANY_UPDATES(34),
 | 
			
		||||
    VERSION_CONFLICT(35),
 | 
			
		||||
    SUBSCRIPTION_VIOLATION(40),
 | 
			
		||||
    PASSWORD_VIOLATION(45);
 | 
			
		||||
    PASSWORD_VIOLATION(45),
 | 
			
		||||
    DATABASE(46);
 | 
			
		||||
 | 
			
		||||
    private int errorCode;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user