接續〈宣告式基本驗證〉。
如果需要自訂登入的畫面,以及登入錯誤時的頁面,則可以改用容器所提供表單(Form)驗證。要將之前的基本驗證改為表單驗證的話,可以在 web.xml 中修改 <login-config>
的設定:
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.html</form-login-page>
<form-error-page>/error.html</form-error-page>
</form-login-config>
</login-config>
在 <auth-method>
的設定從 BASIC
改為 FORM
。由於使用表單網頁進行登入,所以必須告訴容器,登入頁面是哪個?登入失敗的頁面又是哪個?這是由 <form-login-page>
及 <form-error-page>
來設定,設定時注意必須以斜線開始,也就是從應用程式根目錄開始的 URL 路徑。
再來就可以設計自己的表單頁面,但必須注意!表單發送的 URL 必須是 j_security_check
,發送名稱的請求參數必須是 j_username
,發送密碼的請求參數必須是 j_password
,在 Servlet 3.1 中,要進一步於密碼欄位上加上 autocomplete="off"
。以下是個簡單的示範:
<!DOCTYPE html>
<html>
<head>
<title>登入</title>
<meta charset="UTF-8">
</head>
<body>
<form action="j_security_check" method="post">
名稱:<input type="text" name="j_username"><br>
密碼:<input type="password" name="j_password" autocomplete="off"><br>
<input type="submit" value="送出">
</form>
</body>
</html>
一但使用者驗證成功,HttpSession
的逾時或失效(呼叫 invalidate()
方法),使用者就相當於登出。
除了基本驗證與表單驗證之外,在 <auth-method>
中還可以設定 DIGEST
或 CLIENT-CERT
。
DIGEST
即所謂「摘要驗證」,瀏覽器也會出現對話方塊輸入名稱、密碼,而後透過 Authorization
標頭傳送,只不過並非使用 BASE64 來編碼名稱、密碼。瀏覽器會直接傳送名稱,但對密碼則先進行(MD5)摘要演算(非加密),得到理論上唯一且不可逆的 字串再傳送,伺服端根據名稱從後端取得密碼,以同樣的方式作摘要演算,再比對瀏覽器送來的摘要字串是否符合,如果符合就驗證成功。由於網路上傳送時並不是 真正的密碼,而是不可逆的摘要,密碼不會被得知,理論上比較安全一些。
CLIENT-CERT
也是用對話方塊的方式來輸入名稱與密碼,因為使用 PKC(Public Key Certificate)作加密,可保證資料傳送時的機密性及完整性,但客戶端需要安裝憑證(Certificate),在一般使用者及應用程式之間並不常採用。
在身份驗證的四種方式中,BASIC
、FORM
、DIGEST
都無法保證資料的機密性與完整性(DIGEST
比較安全一點,但這個機制畢竟不是加密)。CLIENT-CERT
利用 PKC 加密,但客戶端要安裝憑證,比較不適用於一般使用者及應用程式之間的資料傳送。
通常 Web 應用程式要在傳輸過程中保護資料,會採用 HTTP over SSL,就就是俗稱的 HTTPS。在 HTTPS 中,伺服端會提供憑證來證明自己的身份及提供加密用的公鑰,而瀏覽器會利用公鑰加密資訊再傳送給伺服端,伺服端再用對應的私鑰進行解密以取得資訊,客戶端本身不用安裝憑證,因此是在保護資料傳送上是最常採用的方式。
如果要使用 HTTPS 來傳輸資料,則只要在 web.xml 中需要安全傳輸的 <security-contraint>
中設定:
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
<transport-guarantee>
預設值是 NONE
,還可以設定的值是 CONFIDENTIAL
或 INTEGRAL
, 正如其名稱所表達的,CONFIDENTIAL
在保證資料的機密性,也就是資料不可被未經驗證、授權的其他人看到,而 INTEGRAL
在保證完整性,也就是資料不可以被第三方修改。事實上,無論設定 CONFIDENTIAL
或 INTEGRAL
,都可以保證機密性與完整性,只是大家慣例上都設定 CONFIDENTIAL
。
可以為之前的表單驗證設定使用 HTTPS:
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
// 略...
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin</web-resource-name>
<url-pattern>/admin/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Manager</web-resource-name>
<url-pattern>/manager/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
<role-name>manager</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
// 略...
</web-app>
就 Web 應用程式來說,只要這樣設定就夠了!若伺服器有支援 SSL 且安裝好憑證,例如,在 Tomcat 中,可以在 server.xml 中找到以下註解:
<!-- 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">
<SSLHostConfig>
<Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
type="RSA" />
</SSLHostConfig>
</Connector>
-->
將 Connector
部份的註解去除,並設定好你的憑證,當你請求受保護的資源時,伺服器會要求瀏覽器重新導向使用 HTTPS。
你也可以設定支援 HTTP/2 的 Connector
:
<!-- 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>
-->