1. 程式人生 > >Oracle APEX 系列文章11:全站啟用 HTTPS,讓你的 APEX 更安全

Oracle APEX 系列文章11:全站啟用 HTTPS,讓你的 APEX 更安全

引言

目前主流的網站都要求 HTTPS 安全訪問,Google Chrome 瀏覽器、微信內建瀏覽器開啟非 HTTPS 的網頁,都會提示不安全。如果做微信端開發,也是必須要 HTTPS 的網址才可以,可見 HTTPS 越來越重要了。

如果你按照鋼哥之前的文章已經搭建好了 Oracle APEX 環境,那麼你的應用架構應該如下圖所示:

這裡簡單回顧一下各部分元件的作用:
* 使用者在瀏覽器位址列裡輸入URL,例如:https://apex.wangfanggang.com/ords/ (不要嘗試開啟這個網址了,我瞎寫的)
* Nginx監聽 HTTP (80) 埠和 HTTPS (443

) 埠,如果請求的是靜態檔案(如:image, js 或者 css),則直接獲取/i/目錄中的內容,對於其他動態請求(如:APEX請求),進一步轉發至後端 Tomcat 伺服器做進一步處理。
* Tomcat 伺服器接收到請求後,會查詢部署在它上面的應用,就是我們之前部署的ORDS應用;
* 如果是 APEX 請求,ORDS 進一步將請求轉發給 APEX (Oracle 資料庫) 進行處理;如果是 ORDS 請求,自身進行處理;

原理比較簡單,而我們要做的就是在 Nginx 層面講 HTTP 請求轉發到 HTTPS 上,進而實現全站 HTTPS 訪問。

申請 SSL 證書

這裡以在阿里雲上購買免費 SSL 證書為例,首先登入阿里雲控制檯,進入安全(雲盾)-> SSL證書(應用安全)

,點選購買證書

進入到選擇購買頁面,提示1年需要五千多大洋,土豪直接點選付款即可。

好吧,我是窮人,只能看看有沒有免費證書。其實是有的,依次點選Symantec -> 1個域名 -> 免費型DV SSL,成功啟用30人:)

接下來回到控制檯,補全剛剛申請的證書資訊。

按照提示補全資訊。

鋼哥提示:由於我們申請的是阿里雲的免費證書,只能作用於一個固定域名,一般我們都不會把主域名用來放置APEX應用,所以這裡可以填寫諸如:apex.xxx.com 的二級域名。如果你想要免費萬用字元域名,可以移步這裡:使用Let’s Encrypt給網站加上免費HTTPS證書

另外需要注意的是,第二步的驗證環節,如果你選擇的是檔案驗證,請一定按照提示把對應的驗證檔案放到你的伺服器上,正常檔案驗證一般不會超過5分鐘,如果長時間沒驗證通過,一定是你操作有問題了。

當你的證書申請通過後,就可以點選下載連結了。

配置 Nginx

將 SSL 證書新增進nginx.conf,按照下載證書頁面的提示配置 Nginx:

我的nginx.conf檔案內容如下:

worker_processes  auto;
worker_rlimit_nofile 10000;

error_log  logs/error.log;

events
{  
   worker_connections  2048;
   #==告訴nginx收到一個新連結通知後接受盡可能多的連結
   multi_accept on;
   #==設定用於複用客戶端執行緒的輪訓方法
   use epoll;
}

http
{  
   include       mime.types;
   default_type  application/octet-stream;
   charset UTF-8;

   log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

   access_log   /etc/nginx/logs/access_log.log main;


   server_tokens off;
   sendfile      on;
   tcp_nopush    on;

   keepalive_timeout  65;
   proxy_connect_timeout 600;
   proxy_send_timeout 600;
   proxy_read_timeout 600;
   send_timeout 600;

    #==設定nginx採用gzip壓縮的形式傳送資料,減少傳送資料量,但會增加請求處理時間及CPU處理時間,需要權衡
    gzip  on;
    #==加vary給代理伺服器使用,針對有的瀏覽器支援壓縮,有個不支援,根據客戶端的HTTP頭來判斷是否需要壓縮
    gzip_vary on;
    gzip_http_version 1.0;
    gzip_types text/plain application/javascript application/x-javascript text/css;
    gzip_min_length  1024;
    gzip_comp_level 3;

    server
    {  listen      443 default_server;
       server_name apex.wangfanggang.com;

       ssl on;
       ssl_certificate      cert/214412416080589.pem;
       ssl_certificate_key  cert/214412416080589.key;
       ssl_session_timeout  5m;
       ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;

        location = /
        {  
           # 預設開啟某個APEX應用
           rewrite ^/(.*) https://apex.wangfanggang.com/ords/f?p=102 redirect;
        }

        location ~* \.(eot|ttf|woff|woff2)$
        {  
            add_header Access-Control-Allow-Origin *;
        }

        location ^~ /i/
        {  
            alias /u01/tomcat/webapps/i/;
        }

        location ^~ /ords/
        {  
           # 將請求轉發到tomcat上
           proxy_pass http://localhost:8080/ords/;
           proxy_redirect off;
           proxy_set_header Host $host;
           proxy_set_header X-Real-IP $remote_addr;
           proxy_set_header X-Forwarded-Proto  $scheme;
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
           client_max_body_size 20m;
        }

    }

    server
    {  
      listen       80 default_server;
      server_name  apex.wangfanggang.com;

      include /etc/nginx/default.d/*.conf;

      location = /
      {  
          # 所有http請求統一重定向到https上
          rewrite ^/(.*) https://apex.wangfanggang.com/ords/f?p=102 redirect;
      }

      location ~* \.(eot|ttf|woff|woff2)$
      {
          add_header Access-Control-Allow-Origin *;
      }

      location ^~ /i/
      {
           alias /u01/tomcat/webapps/i/;
      }

      location ^~ /ords/
      {  
         # 所有http請求統一重定向到https上
         proxy_pass https://apex.wangfanggang.com/ords/;
         proxy_redirect off;
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-Proto  $scheme;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         client_max_body_size 20m;
      }


      error_page 404 /404.html;
      location = /40x.html
      {
      }

      error_page 500 502 503 504 /50x.html;
      location = /50x.html
      {
      }
    }
}

配置完 nginx,別忘了重啟令配置生效。再次在瀏覽器中訪問 APEX 頁面,如果能看到如下介面,恭喜你,你的 SSL 證書生效了!!

配置 Tomcat

鋼哥在配置的時候 SSL 證書時遇到了一個奇怪的問題,就是啟用 SSL 證書後,訪問 APEX 頁面時會發生重定向錯誤(302 error:too_many_redirects),導致無法正常訪問。

經過跟同事幾天的研究,發現除了要在 Nginx 上啟用 SSL 證書以外,還必須在 Tomcat 上也啟用。還是回到阿里雲控制檯證書下載頁面,找到 Tomcat 配置證書部分。

鋼哥提示:特別要注意的是,這裡要選擇JKS格式證書進行安裝,否則會有問題。

在 Tomcat 的server.xml檔案中新增如下內容:

<Valve
   className = "org.apache.catalina.valves.RemoteIpValve"
   remoteIpHeader = "X-Forwarded-For"
   protocolHeader = "X-Forwarded-Proto"
/>

我的server.xml檔案內容:

<?xml version="1.0" encoding="UTF-8"?>

<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->

<!--
  Note:  A "Server" is not itself a "Container", so you may not
     define subcomponents such as "Valves" at this level.
     Documentation at /docs/config/server.html
 -->

<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener"
  />

  <!--
    Security listener. Documentation at /docs/config/listeners.html
    <Listener className="org.apache.catalina.security.SecurityListener"
    />
  -->

  <!--
    APR library loader. Documentation at /docs/apr.html
  -->

  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on"
  />

  <!--
    Prevent memory leaks due to use of particular java/javax APIs
  -->
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"
  />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"
  />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"
  />

  <!--
    Global JNDI resources
     Documentation at /docs/jndi-resources-howto.html
  -->

  <GlobalNamingResources>
    <!--
      Editable user database that can also be used by
      UserDatabaseRealm to authenticate users
    -->

    <Resource
      name        = "UserDatabase" auth="Container"
      type        = "org.apache.catalina.UserDatabase"
      description = "User database that can be updated and saved"
      factory     = "org.apache.catalina.users.MemoryUserDatabaseFactory"
      pathname    = "conf/tomcat-users.xml"
    />
  </GlobalNamingResources>

  <!--
    A "Service" is a collection of one or more "Connectors" that share
       a single "Container" Note:  A "Service" is not itself a "Container",
       so you may not define subcomponents such as "Valves" at this level.
       Documentation at /docs/config/service.html
   -->


  <Service
    name = "Catalina">
    <!--
      The connectors can use a shared executor, you can define one or more named thread pools
    -->

    <!--
      <Executor
        name            = "tomcatThreadPool"
        namePrefix      = "catalina-exec-"
        maxThreads      = "150"
        minSpareThreads = "4"
      />
    -->

    <!--
      A "Connector" represents an endpoint by which requests are received
      and responses are returned. Documentation at :
      Java HTTP Connector: /docs/config/http.html
      Java AJP  Connector: /docs/config/ajp.html
      APR (HTTP/AJP) Connector: /docs/apr.html
      Define a non-SSL/TLS HTTP/1.1 Connector on port 8080
    -->

    <Connector
      port              = "8080"
      protocol          = "HTTP/1.1"
      connectionTimeout = "20000"
      redirectPort      = "8443"
    />

    <!--
      A "Connector" using the shared thread pool
    -->

    <!--
      <Connector
        executor             = "tomcatThreadPool"
        port="8080" protocol = "HTTP/1.1"
        connectionTimeout    = "20000"
        redirectPort         = "8443"
      />
    -->

    <!--
      Define a SSL/TLS HTTP/1.1 Connector on port 8443
      This connector uses the NIO implementation. The default
      SSLImplementation will depend on the presence of the APR/native
      library and the useOpenSSL attribute of the
      AprLifecycleListener.
      Either JSSE or OpenSSL style configuration may be used regardless of
      the SSLImplementation selected. JSSE style configuration is used below.
    -->

    <!--
      <Connector
        port       = "8443"
        protocol   = "org.apache.coyote.http11.Http11NioProtocol"
        maxThreads = "150"
        SSLEnabled = "true">

        <Certificate
          certificateKeystoreFile = "conf/localhost-rsa.jks"
          type                    = "RSA"
        />
      </Connector>
    -->


    <Connector
      port="8443"
      protocol     = "HTTP/1.1"
      SSLEnabled   = "true"
      scheme       = "https"
      secure       = "true"
      keystoreFile = "cert/214412416080589.jks"
      keystorePass = "abc123"
      clientAuth   = "false"
      SSLProtocol  = "TLSv1+TLSv1.1+TLSv1.2"
      ciphers      = "TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256"
    />

    <!--
      Define a SSL/TLS HTTP/1.1 Connector on port 8443 with HTTP/2
      This connector uses the APR/native implementation which always uses
      OpenSSL for TLS.
      Either JSSE or OpenSSL style configuration may be used. OpenSSL style
      configuration is used below.
    -->

    <!--
      <Connector
        port       = "8443"
        protocol   = "org.apache.coyote.http11.Http11AprProtocol"
        maxThreads = "150"
        SSLEnabled = "true" >
        <UpgradeProtocol
          className="org.apache.coyote.http2.Http2Protocol"
        />
        <SSLHostConfig>
          <Certificate
            certificateKeyFile   = "conf/localhost-rsa-key.pem"
            certificateFile      = "conf/localhost-rsa-cert.pem"
            certificateChainFile = "conf/localhost-rsa-chain.pem"
            type                 = "RSA"
          />
        </SSLHostConfig>
      </Connector>
    -->


    <!--
      Define an AJP 1.3 Connector on port 8009
    -->

    <Connector
      port         = "8009"
      protocol     = "AJP/1.3"
      redirectPort = "8443"
    />

    <!--
      An Engine represents the entry point (within Catalina) that processes
      every request.  The Engine implementation for Tomcat stand alone
      analyzes the HTTP headers included with the request, and passes them
      on to the appropriate Host (virtual host).
      Documentation at /docs/config/engine.html
    -->

    <!--
      You should set jvmRoute to support load-balancing via AJP ie :
      <Engine
        name        = "Catalina"
        defaultHost = "localhost"
        jvmRoute    = "jvm1">
    -->

    <Engine
      name        = "Catalina"
      defaultHost = "localhost">

      <!--
        For clustering, please take a look at documentation at:
        /docs/cluster-howto.html  (simple how to)
        /docs/config/cluster.html (reference documentation)
      -->

      <!--
        <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
        />
      -->



      <!--
        Use the LockOutRealm to prevent attempts to guess user passwords
        via a brute-force attack
      -->

      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <!--
          This Realm uses the UserDatabase configured in the global JNDI
          resources under the key "UserDatabase".  Any edits
          that are performed against this UserDatabase are immediately
          available for use by the Realm.
        -->
        <Realm
          className    = "org.apache.catalina.realm.UserDatabaseRealm"
          resourceName = "UserDatabase"
        />
      </Realm>

      <Host
        name       = "localhost"
        appBase    = "webapps"
        unpackWARs = "true"
        autoDeploy = "true">

        <Valve
           className      = "org.apache.catalina.valves.RemoteIpValve"
           remoteIpHeader = "X-Forwarded-For"
           protocolHeader = "X-Forwarded-Proto"
        />

      </Host>
    </Engine>
  </Service>
</Server>

重啟 Tomcat 伺服器,再次訪問 APEX,煩人的重定向問題終於得以解決。

結語

用 HTTPS 協議來安全地訪問你的 APEX 應用,這一點特別是對企業應用特別重要,相信你現在已經掌握瞭如何在 APEX 上全站啟用 SSL 證書。本文如有遺漏或不足的地方也請隨時跟鋼哥交流,讓我們共同學習,共同進步!

王方鋼 | APEX Evangelist
王方鋼 | APEX Evangelist