接續〈宣告式基本驗證〉。
如果需要自訂登入的畫面,以及登入錯誤時的頁面,則可以改用容器所提供表單(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>
-->

