org.summerb.webappboilerplate.security.rest.RestExceptionTranslator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of summerb-webboilerplate Show documentation
Show all versions of summerb-webboilerplate Show documentation
A very opinionated view on building blocks of simple web
applications
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