Web 開発再入門 #18 ― セキュリティ設定(レスポンス・ヘッダー処理)
fmockup
★ 本ページは工事中 ★
はじめに
Web アプリケーションのサーバー・サイドを開発します。
レスポンス・ヘッダーに著名なパラメーターを設定しておきます。SpringBoot Security で設定するものを参考にしました。
これらにより、ハッカーがブラウザーの挙動を悪用するのを防止します。
また、今回の Web アプリケーションは、SPA として HTML を返却しますので、ブラウザーのキャッシュを無効にしておきます。これにより、ブラウザーは、キャッシュを使用せずに、毎回、Web サーバーから最新の HTML を取得するようになるため、開発段階で頻繫に Vue の HTML を更新する状況において便利です。
SPA:Single Page Application
HTML:Hypr Text Markup Language
レスポンスヘッダーに設定するもの
- 今回、Tomcat にて設定するもの。
レスポンス・ヘッダー
Cache-Control: private, no-store, no-cache, max-age=0, must-revalidate Expires: 0 Set-Cookie: ...; Secure; HttpOnly; Strict-Transport-Security: max-age=31536000; includeSubDomains Pragma: no-cache X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block
- application.properties にて設定するもの。ThymeLeaf で生成するページ用。
レスポンス・ヘッダー
Content-Type: text/html; charset=utf-8
フォルダー・ファイル構成
D:\
└ Developments\
└ Workspace\
└ fmockup\
├ build\
├ sql\
├ src\
│ └ main\
│ ├ java\
│ │ └ cn\
│ │ └ com\
│ │ └ xxxx\
│ │ └ fmockup\
│ │ ├ action\
│ │ ├ controller\
│ │ ├ customizer\
│ │ │ ├ RequestInterceptor.java ← コレ
│ │ │ ├ ServletInitializer.java ← コレ
│ │ │ ├ TomcatConfiguration.java ← コレ
│ │ │ ├ TomcatFilter.java ← コレ
│ │ │ └ WebMvcConfiguration.java ← コレ
│ │ ├ entity\
│ │ ├ mapper\
│ │ ├ response\
│ │ ├ service\
│ │ ├ util\
│ │ ├ validator\
│ │ └ validator_order\
│ └ resources\
├ vue-vite\
└ WinSW.NET-nnn\
ファイルの文字コード
基本的に Eclipse でファイルを作成するので、あまり意識したことがありません。
多分、Unix 改行(LF)なのだと思います。
ファイルの作成
- Eclipse で、ファイル “RequestInterceptor.java”、“ServletInitializer.java”、“TomcatConfiguration.java”、“TomcatFilter.java”、“WebMvcConfiguration.java” を作成する。
RequestInterceptor.java
・・・ package cn.com.xxxx.fmockup.customizer; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; /** * Request Interceptor */ @Component public class RequestInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { // Browser should control cache. response.addHeader("Cache-Control", "private, no-store, no-cache, max-age=0, must-revalidate"); // Browser cache time is zero. response.addHeader("Expires", ""); // Browser should inspect cache. (For old browser) // Browser should keep https conneciton. response.addHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); response.addHeader("Pragma", "no-cache"); // Browser should check content-type (should not check contents characters), prevent XSS. response.addHeader("X-Content-Type-Options", "nosniff"); // Browser should prevent Click-Jacking. <iframe> only can use in same-origin (Ex. https://xxxx_IT_Technologies.com.cn:8080). response.addHeader("X-Frame-Options", "SAMEORIGIN"); // Browser should prevent XSS after found XSS. response.addHeader("X-XSS-Protection", "1; mode=block"); // Response Header (Spring Security Does Not Sets Value) // response.addHeader("Content-Security-Policy", "default-src 'self'"); <- Please Do Not Use It. return; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { return; } }
ServletInitializer.java・・・ package cn.com.xxxx.fmockup.customizer; import java.util.Collections; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.context.annotation.Configuration; import jakarta.servlet.SessionTrackingMode; /** * Servlet Initializer */ @Configuration public class ServletInitializer { public ServletContextInitializer servletContextInitializer() { ServletContextInitializer initializer = servletContext -> { // Cookie (HttpOnly) default is true // servletContext.getSessionCookieConfig().setHttpOnly(true); // servletContext.getSessionCookieConfig().setHttpOnly(false); // Cookie (Secure) default is true // servletContext.getSessionCookieConfig().setSecure(true); // servletContext.getSessionCookieConfig().setSecure(false); // // Remove the JSESSIONID from URL // // <link href="./bootstrap/css/bootstrap.min.css;jsessionid=1E304E1B77F604CA856F7A4E7333FF5E" type="text/css" rel="stylesheet" /> // ------------------------------------------- // This process can remove this jsessoinid // servletContext.setSessionTrackingModes(Collections.singleton(SessionTrackingMode.COOKIE)); }; return initializer; } }
デフォルトで “Set-Cookie: ...; Secure; HttpOnly;” となる。TomcatConfiguration.java・・・ /** * Error Resolver * It Sets Relaxed URL Query Characters Attribute. */ package cn.com.xxxx.fmockup.customizer; import org.apache.catalina.connector.Connector; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class TomcatConfiguration { /* * If a URL is specified as a special character, the Spring Boot does not become an error. * * If this is not specified. Spring boot outputs a Java error stack log. * It gives a hint to attackers, so it is not secured... */ @Bean public TomcatServletWebServerFactory webServerFactory() { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); factory.addConnectorCustomizers((Connector connector) -> { connector.setProperty("relaxedPathChars", "\"<>[\\]^`{|}"); connector.setProperty("relaxedQueryChars", "\"<>[\\]^`{|}"); }); return factory; } }
URL に特殊文字を指定した場合に、Java のスタック・ログを出力して終了してしまう。上記の指定により、そのスタック・ログの出力を抑止する。TomcatFilter.java・・・ package cn.com.xxxx.fmockup.customizer; import java.io.IOException; import org.springframework.stereotype.Component; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; /* * Tomcat Filter */ @Component public class TomcatFilter implements Filter { public void init(FilterConfig filterConfig) throws ServletException { return; } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; String hostFqdn = request.getHeader("host"); if(! hostFqdn.equals(cn.com.xxxx.fmockup.util.PropertyUtil.getHostFqdn())) { response.setStatus(403); } /* // Browser should control cache. response.addHeader("Cache-Control", "private, no-store, no-cache, max-age=0, must-revalidate"); // Browser cache time is zero. response.addHeader("Expires", ""); // Browser should inspect cache. (For old browser) // Browser should keep https conneciton. response.addHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); response.addHeader("Pragma", "no-cache"); // Browser should check content-type (should not check contents characters), prevent XSS. response.addHeader("X-Content-Type-Options", "nosniff"); // Browser should prevent Click-Jacking. <iframe> only can use in same-origin (Ex. https://xxxx_IT_Technologies.com.cn:8080). response.addHeader("X-Frame-Options", "SAMEORIGIN"); // Browser should prevent XSS after found XSS. response.addHeader("X-XSS-Protection", "1; mode=block"); // Response Header (Spring Security Does Not Sets Value) // response.addHeader("Content-Security-Policy", "default-src 'self'"); <- Please Do Not Use It. */ filterChain.doFilter(request, response); return; } @Override public void destroy() { return; } }
HandlerInterceptor を実装(インプリメント)することでレスポンス・ヘッダーを設定する場合と、上記のように Filter を実装(インプリメント)することでレスポンス・ヘッダーを設定する場合があるらしい。WebMvcConfiguration.java・・・ package cn.com.xxxx.fmockup.customizer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * MVC Configuration * It sets screen name. "/**" means catch all request. * */ @Configuration public class WebMvcConfiguration implements WebMvcConfigurer { @Autowired private RequestInterceptor handlerInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(handlerInterceptor).addPathPatterns("/**"); return; } }
URL に “/” を指定した場合に、××××を出力して終了してしまう。上記の指定により、その××××の出力を抑止する。
その他
レスポンス・ヘッダー