In my Spring boot app, I have bunch of endpoints at /api/**
. The following is my App configuration:
@Configuration public class AppConfig extends WebMvcConfigurerAdapter { private class PushStateResourceResolver implements ResourceResolver { private Resource index = new ClassPathResource("/public/index.html"); private List<String> handledExtensions = Arrays.asList("html", "js", "json", "csv", "css", "png", "svg", "eot", "ttf", "woff", "appcache", "jpg", "jpeg", "gif", "ico"); private List<String> ignoredPaths = Arrays.asList("^api\\/.*$"); @Override public Resource resolveResource(HttpServletRequest request, String requestPath, List<? extends Resource> locations, ResourceResolverChain chain) { return resolve(requestPath, locations); } @Override public String resolveUrlPath(String resourcePath, List<? extends Resource> locations, ResourceResolverChain chain) { Resource resolvedResource = resolve(resourcePath, locations); if (resolvedResource == null) { return null; } try { return resolvedResource.getURL().toString(); } catch (IOException e) { return resolvedResource.getFilename(); } } private Resource resolve(String requestPath, List<? extends Resource> locations) { if (isIgnored(requestPath)) { return null; } if (isHandled(requestPath)) { return locations .stream() .map(loc -> createRelative(loc, requestPath)) .filter(resource -> resource != null && resource.exists()).findFirst() .orElseGet(null); } return index; } private Resource createRelative(Resource resource, String relativePath) { try { return resource.createRelative(relativePath); } catch (IOException e) { return null; } } private boolean isIgnored(String path) { return false; // return !ignoredPaths.stream().noneMatch(rgx -> Pattern.matches(rgx, path)); //deliberately made this change for examining the code } private boolean isHandled(String path) { String extension = StringUtils.getFilenameExtension(path); return handledExtensions.stream().anyMatch( ext -> ext.equals(extension)); } } }
The access to the endpoints behind /api/**
is checked to be authenticated, therefore when I type in /api/my_endpoint
in the browser, I get 401 error back, which is not what I want. I want users to be served with index.html
.
3 Answers
Answers 1
You can check for the X-Requested-With
header:
private boolean isAjax(HttpServletRequest request) { String requestedWithHeader = request.getHeader("X-Requested-With"); return "XMLHttpRequest".equals(requestedWithHeader); }
UPDATE: Maybe it's a better approach to check for the Accept
header. I think the probability is much higher that browsers include a Accept: text/html
header than scripts etc. include a X-Requested-With
header.
You could create a custom authentication entry point and redirect the user if the Accept: text/html
header is present:
public class CustomEntryPoint implements AuthenticationEntryPoint { private static final String ACCEPT_HEADER = "Accept"; private final RedirectStrategy redirect = new DefaultRedirectStrategy(); @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { if (isHtmlRequest(request)) { redirect.sendRedirect(request, response, "/"); } else { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized access is not allowed"); } } private boolean isHtmlRequest(HttpServletRequest request) { String acceptHeader = request.getHeader(ACCEPT_HEADER); List<MediaType> acceptedMediaTypes = MediaType.parseMediaTypes(acceptHeader); return acceptedMediaTypes.contains(MediaType.TEXT_HTML); } }
Alternatives:
- Override standard
BasicErrorController
of Spring Boot and handle redirect of401 Unauthorized
errors there. - Why not just return JSON on all
/api
calls and html otherwise?
Answers 2
So, I finally resolved this issue by fixing my security config:
I have a custom JWTAuthenticationFilter
in which I override the unsuccessfulAuthentication
method:
@Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { logger.debug("failed authentication while attempting to access "+ URL_PATH_HELPER.getPathWithinApplication((HttpServletRequest) request)); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.sendRedirect("/"); }
As you can see, if authentication fails, I redirect the user to the "/" which in return will be captured by the resource resolver and index.html
will be served !
Answers 3
Browsers commonly set the "User-Agent" header of the http request.
So you can distinguish these calls using: request.getHeader("User-Agent");
Also see:
0 comments:
Post a Comment