All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.summerb.webappboilerplate.security.rest.RestExceptionTranslator Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright 2015-2024 Sergey Karpushin
 *
 * 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.summerb.webappboilerplate.security.rest;

import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;
import org.springframework.web.servlet.LocaleContextResolver;
import org.summerb.i18n.exceptions.dto.ExceptionInfo;
import org.summerb.i18n.exceptions.dto.GenericServerErrorResult;
import org.summerb.security.api.CurrentUserNotFoundException;
import org.summerb.security.api.dto.NotAuthorizedResult;
import org.summerb.security.api.exceptions.NotAuthorizedException;
import org.summerb.spring.security.SecurityMessageCodes;
import org.summerb.utils.DtoBase;
import org.summerb.utils.exceptions.ExceptionUtils;
import org.summerb.validation.ValidationException;
import org.summerb.webappboilerplate.utils.exceptions.translator.ExceptionTranslator;
import org.summerb.webappboilerplate.utils.json.JsonResponseWriter;
import org.summerb.webappboilerplate.utils.json.JsonResponseWriterGsonImpl;

/**
 * Request filter that helps to format and gracefully communicate server errors to client.
 *
 * 

Normally it's added to Spring security filter chain, i.e. like this: * *

 * 	<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
 * 		<sec:filter-chain-map request-matcher="ant">
 * 			<sec:filter-chain pattern="/static/**" filters="none" />
 * 			<sec:filter-chain pattern="/login/invalid-session" filters="none" />
 * 			<sec:filter-chain pattern="/rest/**"
 * 				filters="ipToMdcContext, securityContextFilter, restLogoutFilter, restLoginFilter, rememberMeFilter, servletApiFilter, anonFilter,
 * 				restSessionMgmtFilter, restExceptionTranslator, filterSecurityInterceptor" />
 * 			<sec:filter-chain pattern="/**"
 * 				filters="ipToMdcContext, securityContextFilter, logoutFilter, formLoginFilter, rememberMeFilter, requestCacheFilter,
 * 	             servletApiFilter, anonFilter, sessionMgmtFilter, exceptionTranslator, filterSecurityInterceptor" />
 * 		</sec:filter-chain-map>
 * 	</bean>
 * 
* * @author sergeyk */ public class RestExceptionTranslator extends GenericFilterBean { protected Logger log = LoggerFactory.getLogger(getClass()); public static final String X_TRANSLATE_AUTHORIZATION_ERRORS = "X-TranslateAuthorizationErrors"; protected AuthenticationTrustResolver authenticationTrustResolver; protected JsonResponseWriter jsonResponseHelper; protected ExceptionTranslator exceptionTranslator; protected LocaleContextResolver localeContextResolver; public RestExceptionTranslator() { jsonResponseHelper = new JsonResponseWriterGsonImpl(); authenticationTrustResolver = new AuthenticationTrustResolverImpl(); } public RestExceptionTranslator( JsonResponseWriter jsonResponseHelper, AuthenticationTrustResolver authenticationTrustResolver) { this.jsonResponseHelper = jsonResponseHelper; this.authenticationTrustResolver = authenticationTrustResolver; } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { chain.doFilter(request, response); log.trace("Chain processed normally"); } catch (Exception ex) { log.info("Unhandled exception while processing REST query", ex); DtoBase result = determineFailureResult(ex, request, response); jsonResponseHelper.writeResponseBody(result, response); } } protected DtoBase determineFailureResult( Exception ex, HttpServletRequest request, HttpServletResponse response) { // first see if it is FVE ValidationException fve = ExceptionUtils.findExceptionOfType(ex, ValidationException.class); if (fve != null) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); return fve.getErrorDescriptionObject(); } boolean translateAuthErrors = Boolean.TRUE.equals(Boolean.valueOf(request.getHeader(X_TRANSLATE_AUTHORIZATION_ERRORS))); GenericServerErrorResult ret = null; if (translateAuthErrors) { ret = new GenericServerErrorResult(buildUserMessage(ex, request), new ExceptionInfo(ex)); } NotAuthorizedException naex = ExceptionUtils.findExceptionOfType(ex, NotAuthorizedException.class); if (naex != null) { response.setStatus(HttpServletResponse.SC_FORBIDDEN); return ret != null ? ret : naex.getResult(); } AuthenticationException ae = ExceptionUtils.findExceptionOfType(ex, AuthenticationException.class); if (ae != null) { // NOTE: See how we did that in AuthenticationFailureHandlerImpl... // Looks like we need to augment our custom RestLoginFilter so it // will put username to request response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return ret != null ? ret : new NotAuthorizedResult("(username not resolved)", SecurityMessageCodes.AUTH_FATAL); } AccessDeniedException ade = ExceptionUtils.findExceptionOfType(ex, AccessDeniedException.class); if (ade != null) { if (authenticationTrustResolver.isAnonymous( SecurityContextHolder.getContext().getAuthentication())) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return ret != null ? ret : new NotAuthorizedResult(getCurrentUser(null), SecurityMessageCodes.LOGIN_REQUIRED); } response.setStatus(HttpServletResponse.SC_FORBIDDEN); return ret != null ? ret : new NotAuthorizedResult(getCurrentUser(null), SecurityMessageCodes.ACCESS_DENIED); } CurrentUserNotFoundException cunfe = ExceptionUtils.findExceptionOfType(ex, CurrentUserNotFoundException.class); if (cunfe != null) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return ret != null ? ret : new NotAuthorizedResult(getCurrentUser(null), SecurityMessageCodes.LOGIN_REQUIRED); } // TBD: Do we really need to send whole stack trace to client ??? I think we // should do it only during development response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return new GenericServerErrorResult(buildUserMessage(ex, request), new ExceptionInfo(ex)); } protected String buildUserMessage(Exception ex, HttpServletRequest request) { String userMessage = exceptionTranslator.buildUserMessage(ex, localeContextResolver.resolveLocale(request)); if (!StringUtils.hasText(userMessage)) { userMessage = ExceptionUtils.getAllMessagesRaw(ex); } return userMessage; } protected String getCurrentUser(Authentication optionalAuthentication) { Authentication auth = optionalAuthentication; if (auth == null) { auth = SecurityContextHolder.getContext().getAuthentication(); } if (auth != null) { return auth.getName(); } return SecurityMessageCodes.ANONYMOUS; } public AuthenticationTrustResolver getAuthenticationTrustResolver() { return authenticationTrustResolver; } public void setAuthenticationTrustResolver( AuthenticationTrustResolver authenticationTrustResolver) { this.authenticationTrustResolver = authenticationTrustResolver; } public ExceptionTranslator getExceptionTranslator() { return exceptionTranslator; } @Autowired public void setExceptionTranslator(ExceptionTranslator exceptionTranslator) { this.exceptionTranslator = exceptionTranslator; } public LocaleContextResolver getLocaleContextResolver() { return localeContextResolver; } @Autowired public void setLocaleContextResolver(LocaleContextResolver localeContextResolver) { this.localeContextResolver = localeContextResolver; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy