# 基于 Glassfish 表單的身份驗證示例
> 原文: [https://javatutorial.net/glassfish-form-based-authentication-example](https://javatutorial.net/glassfish-form-based-authentication-example)
在本教程中,我將向您展示如何使用內置的 Glassfish 身份驗證機制通過用戶登錄創建基于 Web 的應用程序。

如今,身份驗證已廣泛用于所有類型的 Web 應用程序中。 從網上商店,低谷社交媒體和要求用戶登錄的其他服務。Glassfish 帶有集成的身份驗證和授權機制,可讓用戶登錄并維護應用程序中的會話。 我們將利用此功能并構建和應用程序,以允許用戶注冊和登錄以訪問授權的內容。 最后,我將向您展示如何創建注銷功能以允許用戶終止會話。
這是一個完整的 Java EE 教程,它要求 DB 設置,Glassfish 配置,身份驗證后端邏輯的開發以及創建前端部分。 我將使用 Glassfish 4,MySQL 數據庫和 JSF 2.2 來構建前端。 該代碼也可以在 Payara 4 Server 和其他數據庫(MSSQL,Oracle 等)上運行,而無需進行任何修改。
## 項目描述
我們將創建一個簡單的 Web 應用程序,其中包含一個注冊表單,一個登錄表單和一個私人頁面。 用戶登錄后即可訪問該私人頁面。 表單中的所有字段都將根據錯誤的用戶輸入進行驗證。
## 創建數據庫表
在繼續之前,請確保已使用數據庫配置了 Glassfish 服務器。 如果尚未執行此操作,請先閱讀[如何使用 MySQL 配置 Glassfish 4](https://javatutorial.net/configure-glassfish-mysql)。 當然,您可以使用您喜歡的任何其他數據庫來學習本教程。 您不僅限于 MySQL。
您可以在多個位置存儲用戶憑據,例如數據庫,文件,LDAP 等。在本教程中,我們將使用數據庫存儲用戶名和密碼。 在 Glassfish 中,這稱為 JDBC 安全領域。 創建安全領域需要兩張表 - 第一個用于存儲用戶憑據,第二個用于將特定用戶映射到角色。 Glassfish 并未預定義角色,這意味著我們可以創建自己的角色 - 管理員,用戶,主持人,查看者等。為了使本教程盡可能簡短,我將只實現一個角色:用戶
第一個表稱為`users`,具有以下字段:
* `email` – 唯一的主要字段。 我們將使用用戶的電子郵件地址作為用戶名,如果您愿意,可以使用簡單的舊用戶名
* `password` – SHA-256 編碼的用戶密碼。 切勿將用戶密碼存儲為純文本格式 - 不好??
* `name` – 用戶名
Glassfishe 的安全領域只有用戶名和密碼字段為必填項。 我在此處添加了名稱,以說明您可以在同一表單中為用戶添加其他信息
```java
CREATE TABLE `users` (
`email` varchar(255) NOT NULL,
`password` varchar(64) NOT NULL,
`name` varchar(30) NOT NULL,
PRIMARY KEY (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
```
`user_groups`表具有 2 個必填字段:
* `email` - 這是用戶名
* `groupname` – 存儲用戶的角色,例如管理員,用戶,主持人,查看者等。
```java
CREATE TABLE `user_groups` (
`email` VARCHAR(255) NOT NULL,
`groupname` VARCHAR(32) NOT NULL,
PRIMARY KEY (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
```
## 項目結構
在開始實現項目的后端部分之前,我想向您展示項目結構。 這樣,您將知道所有文件所在的位置

您可以在 GitHub 上找到完整的源代碼,網址為 [https://github.com/JavaTutorialNetwork/Tutorials/tree/master/GlassfishFormBasedAuthentication](https://github.com/JavaTutorialNetwork/Tutorials/tree/master/GlassfishFormBasedAuthentication)
## 項目依賴關系(`pom.xml`文件)
這是純 Java EE 7 的實現,除了 Java 企業 API 本身之外,我們在項目中不需要其他依賴項。 該 API 已包含在 Glassfish 中,因此您應將依賴項標記為`provided`
```java
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.javatutorial.tutorials</groupId>
<artifactId>GlassfishFormBasedAuthentication</artifactId>
<version>1</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>authexample</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<webXml>src/main/webapp/WEB-INF/web.xml</webXml>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
```
## 用戶和組實體
在數據庫中添加表`users`和`user_groups`后,必須為這兩個表創建 JPA 實體。 實體會將數據庫表映射到 Java 對象,因此您可以使用實體對象中的 getter 和 setter 方法輕松地修改或獲取數據庫。 使用`@Entity`注解將 Java 類標記為實體。 該類還必須實現`Serializable`接口。
這是**用戶實體**的代碼
```java
package net.javatutorial.tutorials.gfauthexample.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
@Entity
@NamedQueries({
@NamedQuery(name = "findUserById", query = "SELECT u FROM User u WHERE u.email = :email")
})
@Table(name="users")
public class User implements Serializable {
private static final long serialVersionUID = -5892169641074303723L;
@Id
@Column(name="email", nullable=false, length=255)
private String email;
@Column(name="password", nullable=false, length=64)
private String password;
@Column(name="name", nullable=false, length=30)
private String name;
public User(){}
public User(String email, String password, String name) {
this.email = email;
this.password = password;
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
```
在第 14 行,定義了`NamedQuery "findUserById"`。 我們稍后將在項目中使用此查詢來:
* 驗證注冊期間提供的電子郵件是否尚未用于其他注冊
* 登錄后顯示用戶名和電子郵件
在第 11 行,用戶實體通過`@Table`注釋映射到數據庫表`users`
在第 18 行,`@Id`注釋用于將“電子郵件”表示為主字段
`@Column`注解用于將字段從 Java 類映射到表中的字段
**團體實體**
```java
package net.javatutorial.tutorials.gfauthexample.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="user_groups")
public class Group implements Serializable {
private static final long serialVersionUID = 1528447384986169065L;
public static final String USERS_GROUP = "users";
@Id
@Column(name="email", nullable=false, length=255)
private String email;
@Column(name="groupname", nullable=false, length=32)
private String groupname;
public Group() {}
public Group(String email, String groupname) {
this.email = email;
this.groupname = groupname;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getGroupname() {
return groupname;
}
public void setGroupname(String groupname) {
this.groupname = groupname;
}
}
```
字段`groupname`用于給定用戶角色。 該表是一個映射表,用戶 ID 映射到某個角色。 為了使此示例簡單,在本教程中創建的所有帳戶都將具有“用戶”角色。 您可以根據需要添加更多角色。 角色不是預定義的,您可以隨意命名。 角色之間的區別在于實現。 例如,如果您具有“管理員”角色,則將要賦予它比“用戶”更多的特權。 您必須以編程方式限制一個角色,并以編程方式為另一個角色提供更多選擇,并在您自己的代碼中進行管理。
## `persistence.xml`文件
```java
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="tutorialsPU" transaction-type="JTA">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>jdbc/tutorialsDS</jta-data-source>
<class>net.javatutorial.tutorials.gfauthexample.entity.User</class>
<class>net.javatutorial.tutorials.gfauthexample.entity.Group</class>
<properties>
<!-- tables will be created only if they do not exist. Use for production
<property name="eclipselink.ddl-generation" value="create-tables"/>
-->
<!-- will first drop the existing table, and then create the new table. Use for development
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
-->
<property name="eclipselink.logging.level" value="INFO"/>
</properties>
</persistence-unit>
</persistence>
```
在此文件中,我們指定持久層屬性。 首先,我們創建一個命名的持久性單元`tutorialsPU`。 我們將在 EJB 中使用此持久性單元為`EntityManager`創建隔離的`PersistenceContext`。 其次,我們指定將用于此持久性單元的數據源。 數據源在 Glassfish 管理控制臺中配置(有關更多詳細信息,請閱讀[如何使用 MySQL 配置 Glassfish 4](https://javatutorial.net/configure-glassfish-mysql))。 第三,我們列出要包含在持久性單元中的實體類。 在我們的例子中,這是我們在上一步中創建的`User`和`Group`類。
我還包括了兩個其他有用的屬性。 您可以根據需要將其注釋掉:
如果數據庫表不存在,此屬性將生成數據庫表
```java
<property name="eclipselink.ddl-generation" value="create-tables"/>
```
部署應用程序時,此屬性將擦除現有表,并根據您的實體類創建新的(空)表。 您可以將其用于開發
```java
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
```
## 編寫業務層 – `UserEJB`
在這一步中,我們將編寫一個 Enterprise Java Bean(EJB),以在數據庫中插入新用戶并對其進行查詢。 EJB 通常用作持久層(數據庫實體)和表示層(托管 Bean 和 JSF 頁面)之間的中介。
```java
package net.javatutorial.tutorials.gfauthexample.ejb;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import net.javatutorial.tutorials.gfauthexample.entity.Group;
import net.javatutorial.tutorials.gfauthexample.entity.User;
import net.javatutorial.tutorials.gfauthexample.utils.AuthenticationUtils;
@Stateless
public class UserEJB {
@PersistenceContext(unitName="tutorialsPU")
private EntityManager em;
public User createUser(User user) {
try {
user.setPassword(AuthenticationUtils.encodeSHA256(user.getPassword()));
} catch (Exception e) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
e.printStackTrace();
}
Group group = new Group();
group.setEmail(user.getEmail());
group.setGroupname(Group.USERS_GROUP);
em.persist(user);
em.persist(group);
return user;
}
public User findUserById(String id) {
TypedQuery<User> query = em.createNamedQuery("findUserById", User.class);
query.setParameter("email", id);
User user = null;
try {
user = query.getSingleResult();
} catch (Exception e) {
// getSingleResult throws NoResultException in case there is no user in DB
// ignore exception and return NULL for user instead
}
return user;
}
}
```
請注意,第 16 行的 Bean 被注釋為`@Stateless`。
在第 19 行,`EntityManager`指向`"tutorialsPU"`作為持久性單元,全部通過`@PersistenceContext`注解完成。 我們在`persistence.xml`文件中指定了持久性單元
`createUser`方法首先將純文本密碼編碼為 SHA-256 字符串,然后將兩個對象(用戶和組)存儲到數據庫中。
如果在數據庫中找不到該用戶,則`findUserById`方法將返回一個`User`對象或`null` 請注意此處`try-catch`的用法。 如果未找到具有給定 ID(電子郵件)的用戶,則對`query.getSingleResult()`的調用將引發`NoResultException`。 我們將忽略該異常,而是返回一個`null`用戶。 稍后,我們將在托管 Bean 中處理`null`。
實用程序將密碼編碼為 SHA-256 的方法
```java
package net.javatutorial.tutorials.gfauthexample.utils;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.xml.bind.DatatypeConverter;
public final class AuthenticationUtils {
/**
* Returns SHA-256 encoded string
* @param password - the string to be encoded
* @return SHA-256 encoded string
* @throws UnsupportedEncodingException if UTF-8 is not supported by the system
* @throws NoSuchAlgorithmException if SHA-256 is not supported by the system
*/
public static String encodeSHA256(String password)
throws UnsupportedEncodingException, NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(password.getBytes("UTF-8"));
byte[] digest = md.digest();
return DatatypeConverter.printBase64Binary(digest).toString();
}
}
```
## 配置 Glassfish 安全領域
我們需要在 Glassfish 中創建和配置一個新的 JDBC 安全領域。
在`localhost:4848`打開 Glassfish 管理控制臺。 打開“配置 -> 服務器配置 -> 安全 -> 領域”,然后單擊“新建…”按鈕以創建新的安全領域。

Glassfish 服務器配置安全領域

創建新的 JDBC 安全領域
填寫以下字段:
* `Name ` – `jdbc-realm`
* `Class Name` – `com.sun.enterprise.security.auth.realm.jdbc.JDBCRealm`
* `JAAS Context` – `jdbcRealm`
* `JNDI` – `jdbc/tutorialsDS`
* `User Table` – 用戶
* `User Name Column` – 電子郵件
* `Password Column` - 密碼
* `Group Table` – user_groups
* `Group Table User Name Column` – 電子郵件
* `Group Name Column` – 組名
* `Password Encryption Algorithm` - 無(我們通過編程方式對密碼進行加密)
* `Digest Algorithm` – SHA-256
* `Encoding` – Base64
## `glassfish-web.xml`文件
```java
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
<glassfish-web-app error-url="">
<security-role-mapping>
<role-name>users</role-name>
<group-name>users</group-name>
</security-role-mapping>
<class-loader delegate="true" />
<jsp-config>
<property name="keepgenerated" value="true">
<description>Keep a copy of the generated servlet class' java code.</description>
</property>
</jsp-config>
<parameter-encoding default-charset="UTF-8" />
</glassfish-web-app>
```
我們必須在`glassfish-web.xml`文件中指定`security-role-mapping`,并添加角色名稱和組名稱
## `web.xml`文件
```java
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>Form Based Auth Example</display-name>
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
300
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>signin.xhtml</welcome-file>
</welcome-file-list>
<!-- SECURITY -->
<login-config>
<auth-method>FORM</auth-method>
<realm-name>jdbc-realm</realm-name>
<form-login-config>
<form-login-page>/signin.xhtml</form-login-page>
<form-error-page>/signin.xhtml</form-error-page>
</form-login-config>
</login-config>
<security-role>
<description/>
<role-name>users</role-name>
</security-role>
<security-constraint>
<display-name>Restricted to users</display-name>
<web-resource-collection>
<web-resource-name>Restricted Access</web-resource-name>
<url-pattern>/user/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>users</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
</web-app>
```
`web.xml`部署描述符也需要進行一些更改。 我們將在此處配置安全規則。
首先,我們配置會話超時。 這是使用戶會話保持活動狀態的時間。 當會話超時時,用戶不必再次登錄。 該值以分鐘為單位。 300 表示用戶會話將持續 5 個小時。
其次,我們將`welcome-file`設置為`signin.xhtml`。 當用戶打開應用程序時,將顯示登錄屏幕。
在`<login-config>`中設置以下內容:
* `auth-method`是`FORM` – 用戶使用 Web 表單輸入用戶名和密碼
* `realm-name`是`jdbc-realm` – 這是我們在上一步中配置的安全領域的名稱
* `form-login-page`和`form-error-page`是帶有登錄表單的 xhtml 文件和在發生登錄錯誤時顯示的 xhtml 文件(我們都指向`signin.xhtml`,因為在此文件中進行了錯誤處理)
我們只有一個安全角色–用戶
在`<security-constraints>`標簽中,我們為特定用戶角色定義了受限資源。 換句話說 – `user`目錄中的每個文件都需要登錄才能訪問。 我們將在其中放置私有 XHTML 文件。
## 創建展示層

注冊表單
### 注冊表單頁面
在注冊表單中,我們要求提供用戶名,電子郵件地址和密碼。 我們還要求用戶確認密碼。 我們將驗證電子郵件,密碼和確認密碼字段中的用戶輸入。
首先讓我們看一下 JSF XHTML 文件和`ManagedBean`
```java
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>register</title>
</h:head>
<h:body>
<h:form>
<!-- register a PostValidateEvent -->
<f:event listener="#{registerView.validatePassword}"
type="postValidate" />
<h3>Create new account</h3>
<h:panelGrid columns="2">
<h:outputLabel for="name">Name:</h:outputLabel>
<h:inputText id="name" value="#{registerView.name}" required="true"
requiredMessage="Please enter your name" maxlength="30"></h:inputText>
<h:outputLabel for="email">E-Mail:</h:outputLabel>
<h:inputText id="email" value="#{registerView.email}" required="true"
requiredMessage="Please enter your e-mail address">
<f:validator validatorId="emailValidator" />
</h:inputText>
<h:outputLabel for="password">Password:</h:outputLabel>
<h:inputSecret id="password" value="#{registerView.password}"
required="true" requiredMessage="Please enter password"
validatorMessage="Password must contain atleast one lowercase character,
uppercase character, a digit and it's length must be between 6 and 20 characters">
<f:validateRegex pattern="((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20})" />
</h:inputSecret>
<h:outputLabel for="confirmpassword">Confirm password:</h:outputLabel>
<h:inputSecret id="confirmpassword"
value="#{registerView.confirmPassword}" required="true"
requiredMessage="Please confirm your password"></h:inputSecret>
<h:commandButton action="#{registerView.register}" value="Register"></h:commandButton>
</h:panelGrid>
</h:form>
<br />
<br />
<h:link value="I already have an account" outcome="signin" />
</h:body>
</html>
```
注冊視圖
```java
package net.javatutorial.tutorials.gfauthexample.managedbeans;
import java.io.Serializable;
import java.util.logging.Logger;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.event.ComponentSystemEvent;
import javax.inject.Inject;
import net.javatutorial.tutorials.gfauthexample.ejb.UserEJB;
import net.javatutorial.tutorials.gfauthexample.entity.User;
@ManagedBean
@SessionScoped
public class RegisterView implements Serializable {
private static final long serialVersionUID = 1685823449195612778L;
private static Logger log = Logger.getLogger(RegisterView.class.getName());
@Inject
private UserEJB userEJB;
private String name;
private String email;
private String password;
private String confirmPassword;
public void validatePassword(ComponentSystemEvent event) {
FacesContext facesContext = FacesContext.getCurrentInstance();
UIComponent components = event.getComponent();
// get password
UIInput uiInputPassword = (UIInput) components.findComponent("password");
String password = uiInputPassword.getLocalValue() == null ? "" : uiInputPassword.getLocalValue().toString();
String passwordId = uiInputPassword.getClientId();
// get confirm password
UIInput uiInputConfirmPassword = (UIInput) components.findComponent("confirmpassword");
String confirmPassword = uiInputConfirmPassword.getLocalValue() == null ? ""
: uiInputConfirmPassword.getLocalValue().toString();
// Let required="true" do its job.
if (password.isEmpty() || confirmPassword.isEmpty()) {
return;
}
if (!password.equals(confirmPassword)) {
FacesMessage msg = new FacesMessage("Confirm password does not match password");
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
facesContext.addMessage(passwordId, msg);
facesContext.renderResponse();
}
if (userEJB.findUserById(email) != null) {
FacesMessage msg = new FacesMessage("User with this e-mail already exists");
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
facesContext.addMessage(passwordId, msg);
facesContext.renderResponse();
}
}
public String register() {
User user = new User(email, password, name);
userEJB.createUser(user);
log.info("New user created with e-mail: " + email + " and name: " + name);
return "regdone";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getConfirmPassword() {
return confirmPassword;
}
public void setConfirmPassword(String confirmPassword) {
this.confirmPassword = confirmPassword;
}
}
```
### 用戶輸入驗證
驗證規則:
* 所有字段均為必填項(不允許使用任何空字段)
* 合法的郵件地址
* 密碼必須至少包含 1 個小寫字符,1 個大寫字符,1 個數字
* 密碼長度必須在 6 到 20 個字符之間
* 確認密碼必須等于密碼
* 用戶電子郵件應該是唯一的(不用于其他注冊)

用戶輸入表單驗證
有 3 種可能的方法來驗證 JSF 2 中的字段。我將演示全部 3 種方法。
**使用`JSFvalidateRegex`**
我們使用此方法驗證密碼字段。 我們要做的就是插入具有給定正則表達式模式的`f:validateRegex`標簽。 如果您想了解更多有關正則表達式的信息,請閱讀[基本 Java 正則表達式](https://javatutorial.net/basic-java-regular-expressions)教程
```java
<f:validateRegex pattern="((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20})" />
```
我們還在`validatorMessage`屬性內的 JSF 文件中指定了錯誤消息
**在系統事件(`PostValidateEvent`)中驗證**
確認密碼字段通過名為`PostValidateEvent`的系統事件進行驗證。
我們在 XHTML 文件中注冊了一個新事件:
```java
<f:event listener="#{registerView.validatePassword}" type="postValidate" />
```
用戶單擊“注冊”按鈕時,將觸發`RegisterView` bean 中的`public void validatePassword(ComponentSystemEvent event)`方法
**使用自定義驗證程序類**
對于電子郵件驗證,我們使用一個自定義類,該類實現`javax.faces.validator.Validator`接口
`Validator`類必須實現一個稱為`validate`的方法
```java
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException
```
在此方法中,我們根據正則表達式模式驗證用戶輸入,如果該模式不正確,我們將拋出`ValidatorException`
```java
package net.javatutorial.tutorials.gfauthexample.validators;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.FacesValidator;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;
@FacesValidator("emailValidator")
public class EmailValidator implements Validator {
private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-]+(\\."
+ "[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*" + "(\\.[A-Za-z]{2,})$";
private Pattern pattern;
private Matcher matcher;
public EmailValidator() {
pattern = Pattern.compile(EMAIL_PATTERN);
}
@Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
matcher = pattern.matcher(value.toString());
if (!matcher.matches()) {
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Invalid e-mail address", null));
}
}
}
```
我們必須使用以下行在 XHTML 文件中包含給定字段的自定義驗證器
```java
<f:validator validatorId="emailValidator" />
```
**檢查電子郵件是否已在另一個注冊中使用**
為此,我們不需要驗證器。 您可以在`ManagedBean` – `RegisterView`中使用類似這樣的簡單檢查
```java
if (userEJB.findUserById(email) != null) { // email is already used }
```
## 成功注冊
如果用戶在注冊表單中輸入正確的數據,則將創建一個新的`User`對象并將其插入數據庫中。
```java
User user = new User(email, password, name);
userEJB.createUser(user);
```
請注意,我們還在`UserEJB#createUser`方法內為該用戶創建了`Group`對象
```java
Group group = new Group();
group.setEmail(user.getEmail());
group.setGroupname(Group.USERS_GROUP);
em.persist(user);
em.persist(group);
```
數據庫條目如下所示:

用戶表中的條目

用戶組表中的條目
最后,用戶被重定向到`regdone.xhtml`頁面

`regdone.xhtml`頁面
```java
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html" >
<h:head>
<title>Registration done</title>
</h:head>
<h:body>
<h3>Your account has been successfully created</h3>
<br/><br/>
<h:link value="Sign in with your new account" outcome="signin" />
</h:body>
</html>
```
## 登錄表單
在此表單中,我們需要用戶的電子郵件地址和密碼。 表單針對空白字段進行了驗證。 在`LoginView`中檢查用戶名和密碼的有效性。

登錄表單
如果用戶輸入了有效的登錄數據,則會創建一個新的`java.security.Principal`對象。 我們還將用戶添加到`externalContext`的會話地圖中
```java
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
Map<String, Object> sessionMap = externalContext.getSessionMap();
sessionMap.put("User", user);
```
`signin.xhtml`文件
```java
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>login</title>
</h:head>
<h:body>
<h:form>
<h3>Please Sign In</h3>
<h:panelGrid columns="2">
<h:outputLabel for="email">E-Mail:</h:outputLabel>
<h:inputText id="email" value="#{loginView.email}" required="true"
requiredMessage="Please enter your e-mail address"></h:inputText>
<h:outputLabel for="password">Password:</h:outputLabel>
<h:inputSecret id="password" value="#{loginView.password}"
required="true" requiredMessage="Please enter password"></h:inputSecret>
<h:commandButton action="#{loginView.login}" value="Login"></h:commandButton>
</h:panelGrid>
</h:form>
<br />
<br />
<h:link value="Create new account" outcome="register" />
</h:body>
</html>
```
`LoginView ManagedBean`
```java
package net.javatutorial.tutorials.gfauthexample.managedbeans;
import java.io.Serializable;
import java.security.Principal;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import net.javatutorial.tutorials.gfauthexample.ejb.UserEJB;
import net.javatutorial.tutorials.gfauthexample.entity.User;
@ManagedBean
@SessionScoped
public class LoginView implements Serializable {
private static final long serialVersionUID = 3254181235309041386L;
private static Logger log = Logger.getLogger(LoginView.class.getName());
@Inject
private UserEJB userEJB;
private String email;
private String password;
private User user;
public String login() {
FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
try {
request.login(email, password);
} catch (ServletException e) {
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Login failed!", null));
return "signin";
}
Principal principal = request.getUserPrincipal();
this.user = userEJB.findUserById(principal.getName());
log.info("Authentication done for user: " + principal.getName());
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
Map<String, Object> sessionMap = externalContext.getSessionMap();
sessionMap.put("User", user);
if (request.isUserInRole("users")) {
return "/user/privatepage?faces-redirect=true";
} else {
return "signin";
}
}
public String logout() {
FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
try {
this.user = null;
request.logout();
// clear the session
((HttpSession) context.getExternalContext().getSession(false)).invalidate();
} catch (ServletException e) {
log.log(Level.SEVERE, "Failed to logout user!", e);
}
return "/signin?faces-redirect=true";
}
public User getAuthenticatedUser() {
return user;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
```
最后,用戶被重定向到`/user/privatepage.xhtml`

私人頁面
## 登出功能
注銷很簡單。 我們只需要執行注銷請求并使會話無效
```java
public String logout() {
FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
try {
this.user = null;
request.logout();
// clear the session
((HttpSession) context.getExternalContext().getSession(false)).invalidate();
} catch (ServletException e) {
log.log(Level.SEVERE, "Failed to logout user!", e);
}
return "/signin?faces-redirect=true";
}
```
## 故障排除
對安全代碼進行故障排除非常困難。 大多數組件不會引發有意義的異常。 這樣做是故意進行的,以防止黑客在嘗試破解應用程序的安全性時獲得有用的信息。 不幸的是,我們也沒有獲得這些有用的信息。 如果發現一些問題,請嘗試遵循例外。 兩次檢查您的安全領域配置,并檢查所有密碼編碼是否正確。
- JavaTutorialNetwork 中文系列教程
- Java 基礎
- Java 概述
- 在 Ubuntu 上安裝 Java 8 JDK
- Java Eclipse 教程
- Eclipse 快捷方式
- 簡單的 Java 示例
- Java 基本類型
- Java 循環
- Java 數組
- Java 讀取文件示例
- Java 對象和類教程
- 什么是面向對象編程(OOP)
- Java 封裝示例
- Java 接口示例
- Java 繼承示例
- Java 抽象示例
- Java 多態示例
- Java 中的方法重載與方法覆蓋
- Java 控制流語句
- Java 核心
- 如何在 Windows,Linux 和 Mac 上安裝 Maven
- 如何使用 Maven 配置文件
- 如何將自定義庫包含到 Maven 本地存儲庫中
- 如何使用 JUnit 進行單元測試
- 如何使用 Maven 運行 JUnit 測試
- 如何在 Java 中使用 Maven 創建子模塊
- 如何使用 Maven 創建 Java JAR 文件
- 如何使用 Maven 創建 Java WAR 文件
- JVM 解釋
- Java 內存模型解釋示例
- 捕獲 Java 堆轉儲的前 3 種方法
- Java 垃圾收集
- Java 互斥量示例
- Java 信號量示例
- Java 并行流示例
- Java 線程同步
- Java 線程池示例
- Java ThreadLocal示例
- Java 中的活鎖和死鎖
- Java Future示例
- Java equals()方法示例
- Java Lambda 表達式教程
- Java Optional示例
- Java 11 HTTP 客戶端示例
- Java 類加載器介紹
- Java 枚舉示例
- Java hashCode()方法示例
- 如何測試獨立的 Java 應用程序
- SWING JFrame基礎知識,如何創建JFrame
- Java SWING JFrame布局示例
- 在JFrame上顯示文本和圖形
- 與JFrame交互 – 按鈕,監聽器和文本區域
- 如何使用 Maven 創建 Java JAR 文件
- Java Collection新手指南
- 選擇合適的 Java 集合
- Java ArrayList示例
- Java LinkedList示例
- Java HashSet示例
- Java TreeSet示例
- Java LinkedHashSet示例
- Java EnumSet示例
- Java ConcurrentHashSet示例
- Java HashMap示例
- Java LinkedHashMap示例
- Java TreeMap示例
- Java EnumMap示例
- Java WeakHashMap示例
- Java IdentityHashMap示例
- Java SortedMap示例
- Java ConcurrentMap示例
- Java Hashtable示例
- Java 中ArrayList和LinkedList之間的區別
- Java HashMap迭代示例
- Java HashMap內聯初始化
- Java 中HashMap和TreeMap之間的區別
- Java 圖示例
- Java 深度優先搜索示例
- Java 廣度優先搜索示例
- 不同的算法時間復雜度
- Java 序列化示例
- Java 反射示例
- Java 中的弱引用
- Java 8 日期時間 API
- Java 基本正則表達式
- 使用 Java 檢索可用磁盤空間
- Java 生成 MD5 哈希和
- Java 增加內存
- Java 屬性文件示例
- 如何在 Eclipse 上安裝 Java 9 Beta
- Java 9 JShell 示例
- Java 9 不可變列表示例
- Java 9 不可變集示例
- Java 9 不可變映射示例
- Java 單例設計模式示例
- Java 代理設計模式示例
- Java 觀察者設計模式示例
- Java 工廠設計模式
- Java 構建器設計模式
- Java 比較器示例
- Java 發送電子郵件示例
- Java volatile示例
- Java Docker 和 Docker 容器簡介
- 安裝和配置 MySQL 數據庫和服務器以供 Spring 使用
- 如何在 Java 中使用 MySQL 連接器
- 如何使用 Eclipse 調試 Java
- Java EE
- 如何在 Windows 10 中設置JAVA_HOME
- JavaBeans 及其組件簡介
- 如何安裝和配置 Tomcat 8
- 如何在 Tomcat 中部署和取消部署應用程序
- 從 Eclipse 運行 Tomcat
- Java Servlet 示例
- Java Servlet POST 示例
- Servlet 請求信息示例
- Servlet 注解示例
- 使用初始化參數配置 Java Web 應用程序
- Java Servlet 文件上傳
- Java JSP 示例
- Glassfish 啟用安全管理
- 如何使用 MySQL 配置 Glassfish 4
- Java 文件上傳 REST 服務
- Glassfish 和 Jetty 的 Java WebSockets 教程
- 基于 Glassfish 表單的身份驗證示例
- 如何使用 Java EE 和 Angular 構建單頁應用程序
- Spring
- 在 Eclipse 中安裝 Spring STS
- 使用 STS 創建簡單的 Spring Web App
- Spring Web Framework 簡介
- Java Docker 和 Docker 容器簡介
- 在 Spring 中實現控制器
- Spring 中的PathVariable注解
- Spring 中的RequestBody注解
- Spring 中的RequestParam注解
- Spring 攔截器
- Spring IOC
- Java Spring IoC 容器示例
- Spring 中的DispatcherServlet
- Spring 示例中的依賴注入
- 實現 Spring MVC 控制器
- Spring ORM 簡介
- 什么是 DAO 以及如何使用它
- 如何對 DAO 組件進行單元測試
- 如何對控制器和服務執行單元測試
- 安裝和配置 MySQL 數據庫和服務器以供 Spring 使用
- 如何在 Spring 中處理登錄身份驗證
- Spring Security 簡介及其設置
- 如何使用 Spring 創建 RESTful Web 服務
- Spring CSRF 保護
- Spring 中基于 OAuth2 的身份驗證和授權
- Spring Boot 簡介
- Spring MVC 框架介紹
- Spring JDBC 簡介
- 如何 docker 化 Spring 應用程序
- Spring 的@Autowired注解
- Spring AOP 中的核心概念和建議類型
- Sping Bean 簡介
- 如何在 Java 中使用 MySQL 連接器
- 安卓
- 安裝和配置 Android Studio
- 將 Android 設備連接到 Android Studio
- Android 簡介,活動,意圖,服務,布局
- 創建一個簡單的 Android 應用
- 運行和調試 Android 應用程序
- 在虛擬設備上運行 Android 應用程序
- Android 活動示例
- Android 意圖示例
- Android 服務示例
- Android 線性布局示例
- Android 相對布局示例
- Android Web 視圖示例
- Android 列表視圖示例
- Android 網格視圖示例
- 帶有ListAdapter的 Android ListView示例
- Android SQLite 數據庫介紹
- Android SQLite 數據庫示例
- Android 動畫教程
- Android 中的通知
- Android 中的事件處理
- 如何在 Android 中發送帶有附件的電子郵件
- 雜項
- 選擇您的 JAVA IDE:Eclipse,NetBeans 和 IntelliJ IDEA
- Java S3 示例
- 如何在 Ubuntu 上為多個站點配置 Apache
- 如何在 Liferay DXP 中替代現成的(OOTB)模塊
- 簡單的 Git 教程
- 使用 Java 捕獲網絡數據包
- Selenium Java 教程
- 使用特定工作區運行 Eclipse
- 在 Eclipse 中安裝 SVN
- 如何運行 NodeJS 服務器
- SQL 內連接示例
- SQL 左連接示例
- SQL 右連接示例
- SQL 外連接示例
- 樹莓派
- Raspberry Pi 3 規格
- 將 Raspbian 安裝到 SD 卡
- Raspberry Pi 首次啟動
- 遠程連接到 Raspberry Pi
- 建立 Raspberry Pi 遠程桌面連接
- Raspberry Pi Java 教程
- 使用 PWM 的 Raspberry Pi LED 亮度調節
- Raspberry Pi 控制電機速度
- Raspberry Pi 用 Java 控制直流電機的速度和方向