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

引言

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

還不瞭解什麼是 Oracle APEX,請閱讀我的另一篇文章:Oracle APEX 系列文章1:Oracle APEX, 讓你秒變全棧開發的黑科技

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

這裏簡單回顧一下各部分組件的作用:

  • 用戶在瀏覽器地址欄裏輸入URL,例如:https://apex.wangfanggang.com... (不要嘗試打開這個網址了,我瞎寫的)
  • 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章