前言
前兩週在把兄弟公司的幾個服務部署到我們公司測試環境伺服器的時候又遇到了不少問題,因為是前後端分離的專案,所以這次也同樣遇到了跨域問題,解決方式也跟上一回的不一樣,這裡就再來分析記錄一下。
登入驗證碼在本地獲取正常但部署到伺服器報空指標異常問題
問題描述:
登入頁面的驗證碼,在本地執行專案的時候可以正常獲取和顯示,但部署到測試環境伺服器上驗證碼圖片卻無法顯示出來,如下圖所示:
追了一下後端伺服器日誌,發現後端報空指標異常,報錯資訊如下所示:
java.lang.NullPointerException: null at sun.awt.FontConfiguration.getVersion(FontConfiguration.java:1264) at sun.awt.FontConfiguration.readFontConfigFile(FontConfiguration.java:219) at sun.awt.FontConfiguration.init(FontConfiguration.java:107) at sun.awt.X11FontManager.createFontConfiguration(X11FontManager.java:774) at sun.font.SunFontManager$2.run(SunFontManager.java:431) at java.security.AccessController.doPrivileged(Native Method) at sun.font.SunFontManager.(SunFontManager.java:376) at sun.awt.FcFontManager.(FcFontManager.java:35) at sun.awt.X11FontManager.(X11FontManager.java:57) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at java.lang.Class.newInstance(Class.java:442) at sun.font.FontManagerFactory$1.run(FontManagerFactory.java:83) at java.security.AccessController.doPrivileged(Native Method) at sun.font.FontManagerFactory.getInstance(FontManagerFactory.java:74) at java.awt.Font.getFont2D(Font.java:491) at java.awt.Font.access$000(Font.java:224) at java.awt.Font$FontAccessImpl.getFont2D(Font.java:228) at sun.font.FontUtilities.getFont2D(FontUtilities.java:180) at sun.java2d.SunGraphics2D.checkFontInfo(SunGraphics2D.java:669) at sun.java2d.SunGraphics2D.getFontInfo(SunGraphics2D.java:830) at sun.java2d.pipe.GlyphListPipe.drawString(GlyphListPipe.java:50) at sun.java2d.SunGraphics2D.drawString(SunGraphics2D.java:2928) at com.lanmei.common.ImageUtil.createimage(ImageUtil.java:77) ......
跟蹤程式碼後發現,在繪製驗證碼圖片時使用了Graphics畫布,Graphics在繪製驗證碼字元時,需要先設定字型,如下所示:
g.setFont(new Font("DejaVu Sans", Font.CENTER_BASELINE, 18));
這就是問題點所在,在windows系統上,執行這行程式碼是完全沒問題的,因為windows系統自帶這種字型,可是我們測試環境伺服器上執行的linux映象系統並沒有這種字型,所以就報空指標異常了。
解決辦法:構建一個帶有指定字型的基礎映象
構建了一個自帶“DejaVu Sans”字型的openjdk映象,使用該映象來執行我們的程式,就能正常獲取和顯示登入驗證碼了,如下圖所示:
注意:關於構建這個映象,有兩種方式可以選擇,一種是建立一個跟原來一樣沒有“DejaVu Sans”字型的基礎openjdk映象,然後再執行新增相應字型的命令,對應的Dockerfile編寫方式如下:
FROM openjdk:8-jdk-alpine ARG JAR_FILE ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone COPY ${JAR_FILE} app.jar RUN apk add --update ttf-dejavu fontconfig EXPOSE 8086 ENTRYPOINT ["java","-jar","/app.jar"]
使用這種方式的缺點在於每次構建映象的時候都要花很多時間去下載我們所需要的字型,效率很低。
另一種方式則是先在我們自己公司用於存放映象的伺服器上專門構建一個帶有“DejaVu Sans”字型的openjdk映象,然後用它來執行我們的專案,這樣一來能夠滿足功能需求,二來是直接從我們自己的映象伺服器上拉取映象、速度也會快很多。
前後端不同域導致session丟失問題
問題描述:
輸入的登入驗證碼沒有錯,可是卻一直報:驗證碼錯誤,登入失敗!
在後端打了一些日誌後,發現如下資訊:
這裡我們登入驗證碼的完整校驗邏輯是這樣的:使用者請求登入頁面時,呼叫後端生成驗證碼的介面,後端生成一個驗證碼返回給前端並將驗證碼字串快取在瀏覽器session中,使用者輸入登入資訊後,後端根據使用者輸入的驗證碼與使用者快取在瀏覽器session中的驗證碼字串進行比對,若一致則校驗通過,否則校驗失敗。
根據日誌可以判斷,這裡session中快取的驗證碼丟失了,導致後端獲取不到。
按F12鍵使用開發者工具檢視獲取驗證碼的請求響應,可以看到如下資訊:
根據上圖可以得知,後端嘗試將驗證碼快取到瀏覽器session中時,由於前後端不同域導致該操作被攔截了。
解決辦法:通過前端nginx設定同域代理轉發請求
在nginx配置檔案中新增如下配置:
location /hello-sso-service { proxy_pass http://xxx.xxx.xxx.xxx:8080/hello-sso-service; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
將與hello-sso-service相關的介面請求代理轉發到對應的後端服務介面,這樣快取驗證碼到session的操作就不會被攔截了。
更新程式碼配置後,重新進行登入,後端服務就能從session中獲取到相應的驗證碼了,如下圖所示:
關於前後端不同域,這次我其實還遇到了另外一個小問題:在退出登入時,需要跳轉到前端登入頁面並且在url中攜帶一些引數請求後端一個介面,也就是說,跳轉的地址組成結構為“前端ip:埠+對應的後端介面”。
針對這個問題,我也是使用nginx進行了如下代理:
location /v1 { proxy_pass http://xxx.xxx.xxx.xxx:8081/v1; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
總結
這段時間在部署這些新服務的過程中,學習了不少前後端服務部署的知識,尤其對於前後端跨域問題有了很深刻的體會。所謂“同域”,就是“協議+主機+埠”都相同,反之則稱之為“跨域”。
解決“跨域”問題較常見的辦法就是使用nginx進行反向代理設定,如上文中提到的兩個例子,通過代理使前後端“同域”,就可以解決相應的session設定失敗及介面請求不通等問題。