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
.
In a Spring Boot application, we expose a REST API endpoint by using the @RequestMapping annotation in the controller class. For getting these endpoints, there are three options: an event listener, Spring Boot Actuator, or the Swagger library.
The @API annotations as per the documentation states “The annotation @Api is used to configure the whole API, and apply to all public methods of a class unless overridden by @APIMethod”. Note the words unless overridden. Often you find that you casually go ahead and mark a class with @API.
Spring RestController annotation is used to create RESTful web services using Spring MVC. Spring RestController takes care of mapping request data to the defined request handler method. Once response body is generated from the handler method, it converts it to JSON or XML response.
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);
}
}
Note:
If you use a custom authentication filter (inherited from AbstractAuthenticationProcessingFilter
) then the authentication entry point won't be called. You can handle the redirect in the unsuccessfulAuthentication()
method of AbstractAuthenticationProcessingFilter
.
Alternatives:
BasicErrorController
of Spring Boot and handle redirect of 401 Unauthorized
errors there./api
calls and html otherwise?If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With