2017-12-10 208 views
9

W mojej wiosennej aplikacji do bootowania mam kilka punktów końcowych pod numerem /api/**. Poniżej zamieszczona jest moja konfiguracja aplikacji:Wiosna, aby odróżnić odwiedzających przeglądarkę od wywołań API do punktów końcowych.

@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)); 
     } 
    } 
} 

Dostęp do punktów końcowych za /api/** jest sprawdzana być uwierzytelniony, dlatego kiedy wpisuję w /api/my_endpoint w przeglądarce, otrzymuję błąd z powrotem, co nie jest to, co ja chcieć. Chcę, aby użytkownicy byli obsługiwani pod numerem index.html.

+1

Możesz użyć przechwytywacza lub filtra dla wszystkich żądań, a także sprawdzić odpowiedź i ścieżkę oraz postępować zgodnie z tym, prawda? – DvTr

Odpowiedz

2

Więc ostatecznie rozwiązany ten problem poprzez ustalenie mój config zabezpieczeń:

Mam zwyczaj JWTAuthenticationFilter w którym ja zastąpić metodę unsuccessfulAuthentication:

@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("/"); 
} 

Jak widać, jeśli uwierzytelnienie kończy się niepowodzeniem, przekierowuję użytkownika do "/", który w zamian zostanie przechwycony przez przelicznik zasobów i zostanie dostarczony index.html!

+0

Miło, że rozwiązałeś swój problem. Podaj więcej informacji następnym razem, np. używać niestandardowego filtru uwierzytelniania. – Christian

2

można sprawdzić za X-Requested-With nagłówka:

private boolean isAjax(HttpServletRequest request) { 
    String requestedWithHeader = request.getHeader("X-Requested-With"); 
    return "XMLHttpRequest".equals(requestedWithHeader); 
} 

UPDATE: Może to lepsze podejście do sprawdź nagłówek Accept. Myślę, że prawdopodobieństwo jest o wiele wyższe niż to, że przeglądarki zawierają nagłówek Accept: text/html niż skrypty itp. Zawierają nagłówek X-Requested-With.

Można by utworzyć punkt wejścia uwierzytelniania zwyczaj i przekierować użytkownika jeśli nagłówek Accept: text/html jest obecny:

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); 
    } 

} 

Uwaga:

Jeśli użyć filtru uwierzytelniania niestandardowe (dziedziczona z AbstractAuthenticationProcessingFilter) wówczas punkt wejścia do uwierzytelniania nie zostanie wywołany. Możesz obsłużyć przekierowanie w metodzie unsuccessfulAuthentication() z AbstractAuthenticationProcessingFilter.

Alternatywy:

  • Zastąp standardowe BasicErrorController Spring Boot i obsłużyć przekierowanie z 401 Unauthorized błędów tam.
  • Dlaczego nie tylko zwrócić JSON na wszystkie połączenia /api i html w przeciwnym razie?