71936


現在、WARの内部に ～.hmtl, ～.js などの静的コンテンツを内包するWARを作っています。  
このWARが、リクエストを受け付けた場合に、そのレスポンスとしてWARに内包するコンテンツを返す動きとなっています。  
ここで、WARが返したHTML自体から.jsなどの要求があり、再度、同WARにそれらのリクエストがあがってくるのですが、そのように内部コンテンツから生じるリクエストは無条件に通すようにしたいのですが、うまい方法が分かりません。

※静的コンテンツファイルは、/src/main/webapp/ 配下に置いています。  
※spring-bootを使用しています（便宜上、RestAPIのプロジェクト構成としています）。

[code]

    @Controllerの実装
    
[/code]

[code]

        1   // デフォルトマッピング
        2   @GetMapping
        3   @RequestMapping(value = "/**")
        4   @ResponseBody
        5   String another(HttpServletRequest request) {
        6       return "<HTML>Forbidden</HTML>";
        7   }
        8   
        9   // ターゲットリクエストマッピング
        10  @GetMapping
        11  @RequestMapping(value = "/*")
        12  void index(HttpServletRequest request, HttpServletResponse response) throws IOException {
        13      
        14      response.setContentType("text/html");
        15      response.setCharacterEncoding("UTF-8");
        16      
        17      String uri = request.getRequestURI();
        18      if( uri.equals("/") ) {
        19          
        20          // 実際にはこの地点で認証などの共通処理を実施
        21          
        22          response.getWriter().write(<HTML><HEAD>…(略)…</HTML>);
        23          
        24      } else {
        25          response.getWriter().write("<HTML>Forbidden</HTML>");
        26      }
        27  }
    
[/code]

[code]

    ・リクエストはGETメッセージです。
    ・リクエストは @Controller の @RequestMapping で受けます。
    ・リクエストのURIは、ルートのURI（/）は受け付け、それ以外を拒否するロジックとしています。
    ・WARは、受け付けたリクエストに、静的HTMLのレスポンスを返す仕様です。
    
    上記22行目のHTML内容（成形）
    
[/code]

[code]

            <HTML>
                <HEAD>
                    <TITLE>Sample-Page</TITLE>
                <HEAD>
                <BODY>
                    <H1>Sample-Page</H1>
                    <script src="./js/sample.js"></script>
                </BODY>
            </HTML>
    
[/code]

[code]

    /src/main/webapp/js/sample.js
    
[/code]

[code]

            document.write("<BR>sample.js loaded.");
    
[/code]

上記でリクエストを実行すると、認証などを通過した後に22行目によってHTMLがレスポンスされ、その中から改めて sample.js
が要求されるため、これが結局 Controller に拾われ、結果的に "Forbidden" を返す動きとなってしまいます。  
（上記は、当然といえば当然の動きと言えると思います。。）

仮に、5行目の「another」メソッドなど、RequestMappingをワイルドカードで受ける部分をなくせば、Controller上でMappingのない静的コンテンツはControllerを介することなく無条件に返される動きにはなるのですが、リクエストの受付けに際しては、アクセス制御のため静的コンテンツの要求に応じる前に一旦はJava側（Controller以下）で要求を受け取る必要があるという要件とされています。

WAR自体が返す内部の静的コンテンツからの要求については、無条件に許可するような仕掛けにできればよいのですが、よい実装のアイデアがありません。

※実際には、sample.js のほかに他の .js や .css
など多数あるため、最初にレスポンスするHTMLに予め全てをincludeした形とすることは維持性の観点で大きな難があります。

根本的な構成の問題のような気もするのですが、こうしたケースではどのような実装で対応できるでしょうか。ご教示頂きたいと思います。


CC BY-SA 4.0
2020-11-13T08:46:27.193
71926
2020-11-13T20:59:30.847
42710
question
java spring-boot
クライアントからの要求は特定URIのみ許可し、WARが返した静的な内部コンテンツからの要求は無条件に許可したい


> WARが返した静的な内部コンテンツからの要求は無条件に許可したい

に対しては、サーブレットフィルタでリファラチェックをすることで実現できるかと思います。([完全なコードリンク](https://github.com/yukihane/stackoverflow-
qa/tree/master/jaso71926))

[code]

    @Configuration
    public class MyConfig {
    
        // https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-embedded-container-servlets-filters-listeners-beans
        @Bean
        FilterRegistrationBean<RefererCheckFilter> refererCheckFilter() {
            FilterRegistrationBean<RefererCheckFilter> registration = new FilterRegistrationBean<>(
                new RefererCheckFilter());
            registration.setUrlPatterns(List.of("/js/*"));
            registration.setName("refererCheckFilter");
            registration.setOrder(Ordered.LOWEST_PRECEDENCE);
            return registration;
        }
    
        public static class RefererCheckFilter implements Filter {
    
            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
                HttpServletRequest req = (HttpServletRequest) request;
                String referer = req.getHeader(HttpHeaders.REFERER);
                if (!StringUtils.hasText(referer) || !referer.startsWith("http://localhost:8080")) {
                    HttpServletResponse resp = (HttpServletResponse) response;
                    // https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-error-handling-custom-error-pages
                    // にある通りハンドルされる
                    resp.sendError(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase());
                    return;
                }
    
                chain.doFilter(request, response);
            }
        }
    }
    
[/code]

* * *

ただし、おそらくはWebシステム/サーブレットシステム/Spring
Bootへの誤解が大きくあるため、質問文に書かれている要件や現時点の実装コード自体がいびつです。

一見して違和感がある点を記載してみます:

  * Spring Bootでは一般的に認証はコントローラでなく、それ以前のサーブレットフィルタで実現します。 
    * Spring Boot では特に自前でフィルタ実装するのでなく大抵の場合 Spring Security を利用することになるでしょう。
  * static resourcesについても認証が必要ならそのようにフィルタを設定します。従って「内部コンテンツから生じるリクエストは無条件に通」したい、というような要求は生まれません。 
    * ちなみに前述の回答はリファラでチェックしていますが、このチェック方式は不完全です(リファラは偽装可能です)。
  * 「アクセス制御のため静的コンテンツの要求に応じる前に一旦はJava側（Controller以下）で要求を受け取る必要がある」というのも誤解で、前述の通りフィルタでアクセス制御できるのでコントローラを介する必要はありません。

要するに、認証部分の実装をフィルタで行わないとフレームワークの思想に沿った形で実装できません、ということです。


CC BY-SA 4.0
2020-11-13T20:59:30.847
71936
2020-11-13T20:59:30.847
2808
71926
answer