原文: https://javatutorial.net/glassfish-form-based-authentication-example

在本教程中,我将向您展示如何使用内置的 Glassfish 身份验证机制通过用户登录创建基于 Web 的应用程序。

基于 Glassfish 表单的身份验证示例 - 图1

如今,身份验证已广泛用于所有类型的 Web 应用程序中。 从网上商店,低谷社交媒体和要求用户登录的其他服务。Glassfish 带有集成的身份验证和授权机制,可让用户登录并维护应用程序中的会话。 我们将利用此功能并构建和应用程序,以允许用户注册和登录以访问授权的内容。 最后,我将向您展示如何创建注销功能以允许用户终止会话。

这是一个完整的 Java EE 教程,它要求 DB 设置,Glassfish 配置,身份验证后端逻辑的开发以及创建前端部分。 我将使用 Glassfish 4,MySQL 数据库和 JSF 2.2 来构建前端。 该代码也可以在 Payara 4 Server 和其他数据库(MSSQL,Oracle 等)上运行,而无需进行任何修改。

项目描述

我们将创建一个简单的 Web 应用程序,其中包含一个注册表单,一个登录表单和一个私人页面。 用户登录后即可访问该私人页面。 表单中的所有字段都将根据错误的用户输入进行验证。

创建数据库表

在继续之前,请确保已使用数据库配置了 Glassfish 服务器。 如果尚未执行此操作,请先阅读如何使用 MySQL 配置 Glassfish 4。 当然,您可以使用您喜欢的任何其他数据库来学习本教程。 您不仅限于 MySQL。

您可以在多个位置存储用户凭据,例如数据库,文件,LDAP 等。在本教程中,我们将使用数据库存储用户名和密码。 在 Glassfish 中,这称为 JDBC 安全领域。 创建安全领域需要两张表 - 第一个用于存储用户凭据,第二个用于将特定用户映射到角色。 Glassfish 并未预定义角色,这意味着我们可以创建自己的角色 - 管理员,用户,主持人,查看者等。为了使本教程尽可能简短,我将只实现一个角色:用户

第一个表称为users,具有以下字段:

  • email – 唯一的主要字段。 我们将使用用户的电子邮件地址作为用户名,如果您愿意,可以使用简单的旧用户名
  • password – SHA-256 编码的用户密码。 切勿将用户密码存储为纯文本格式 - 不好😉
  • name – 用户名

Glassfishe 的安全领域只有用户名和密码字段为必填项。 我在此处添加了名称,以说明您可以在同一表单中为用户添加其他信息

  1. CREATE TABLE `users` (
  2. `email` varchar(255) NOT NULL,
  3. `password` varchar(64) NOT NULL,
  4. `name` varchar(30) NOT NULL,
  5. PRIMARY KEY (`email`)
  6. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

user_groups表具有 2 个必填字段:

  • email - 这是用户名
  • groupname – 存储用户的角色,例如管理员,用户,主持人,查看者等。
  1. CREATE TABLE `user_groups` (
  2. `email` VARCHAR(255) NOT NULL,
  3. `groupname` VARCHAR(32) NOT NULL,
  4. PRIMARY KEY (`email`)
  5. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

项目结构

在开始实现项目的后端部分之前,我想向您展示项目结构。 这样,您将知道所有文件所在的位置

基于 Glassfish 表单的身份验证示例 - 图2

您可以在 GitHub 上找到完整的源代码,网址为 https://github.com/JavaTutorialNetwork/Tutorials/tree/master/GlassfishFormBasedAuthentication

项目依赖关系(pom.xml文件)

这是纯 Java EE 7 的实现,除了 Java 企业 API 本身之外,我们在项目中不需要其他依赖项。 该 API 已包含在 Glassfish 中,因此您应将依赖项标记为provided

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <groupId>net.javatutorial.tutorials</groupId>
  5. <artifactId>GlassfishFormBasedAuthentication</artifactId>
  6. <version>1</version>
  7. <packaging>war</packaging>
  8. <properties>
  9. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  10. </properties>
  11. <dependencies>
  12. <dependency>
  13. <groupId>javax</groupId>
  14. <artifactId>javaee-api</artifactId>
  15. <version>7.0</version>
  16. <scope>provided</scope>
  17. </dependency>
  18. </dependencies>
  19. <build>
  20. <finalName>authexample</finalName>
  21. <plugins>
  22. <plugin>
  23. <groupId>org.apache.maven.plugins</groupId>
  24. <artifactId>maven-war-plugin</artifactId>
  25. <version>2.3</version>
  26. <configuration>
  27. <webXml>src/main/webapp/WEB-INF/web.xml</webXml>
  28. </configuration>
  29. </plugin>
  30. <plugin>
  31. <groupId>org.apache.maven.plugins</groupId>
  32. <artifactId>maven-compiler-plugin</artifactId>
  33. <version>3.1</version>
  34. <configuration>
  35. <source>1.8</source>
  36. <target>1.8</target>
  37. </configuration>
  38. </plugin>
  39. </plugins>
  40. </build>
  41. </project>

用户和组实体

在数据库中添加表usersuser_groups后,必须为这两个表创建 JPA 实体。 实体会将数据库表映射到 Java 对象,因此您可以使用实体对象中的 getter 和 setter 方法轻松地修改或获取数据库。 使用@Entity注解将 Java 类标记为实体。 该类还必须实现Serializable接口。

这是用户实体的代码

  1. package net.javatutorial.tutorials.gfauthexample.entity;
  2. import java.io.Serializable;
  3. import javax.persistence.Column;
  4. import javax.persistence.Entity;
  5. import javax.persistence.Id;
  6. import javax.persistence.NamedQueries;
  7. import javax.persistence.NamedQuery;
  8. import javax.persistence.Table;
  9. @Entity
  10. @NamedQueries({
  11. @NamedQuery(name = "findUserById", query = "SELECT u FROM User u WHERE u.email = :email")
  12. })
  13. @Table(name="users")
  14. public class User implements Serializable {
  15. private static final long serialVersionUID = -5892169641074303723L;
  16. @Id
  17. @Column(name="email", nullable=false, length=255)
  18. private String email;
  19. @Column(name="password", nullable=false, length=64)
  20. private String password;
  21. @Column(name="name", nullable=false, length=30)
  22. private String name;
  23. public User(){}
  24. public User(String email, String password, String name) {
  25. this.email = email;
  26. this.password = password;
  27. this.name = name;
  28. }
  29. public String getEmail() {
  30. return email;
  31. }
  32. public void setEmail(String email) {
  33. this.email = email;
  34. }
  35. public String getPassword() {
  36. return password;
  37. }
  38. public void setPassword(String password) {
  39. this.password = password;
  40. }
  41. public String getName() {
  42. return name;
  43. }
  44. public void setName(String name) {
  45. this.name = name;
  46. }
  47. }

在第 14 行,定义了NamedQuery "findUserById"。 我们稍后将在项目中使用此查询来:

  • 验证注册期间提供的电子邮件是否尚未用于其他注册
  • 登录后显示用户名和电子邮件

在第 11 行,用户实体通过@Table注释映射到数据库表users

在第 18 行,@Id注释用于将“电子邮件”表示为主字段

@Column注解用于将字段从 Java 类映射到表中的字段

团体实体

  1. package net.javatutorial.tutorials.gfauthexample.entity;
  2. import java.io.Serializable;
  3. import javax.persistence.Column;
  4. import javax.persistence.Entity;
  5. import javax.persistence.Id;
  6. import javax.persistence.Table;
  7. @Entity
  8. @Table(name="user_groups")
  9. public class Group implements Serializable {
  10. private static final long serialVersionUID = 1528447384986169065L;
  11. public static final String USERS_GROUP = "users";
  12. @Id
  13. @Column(name="email", nullable=false, length=255)
  14. private String email;
  15. @Column(name="groupname", nullable=false, length=32)
  16. private String groupname;
  17. public Group() {}
  18. public Group(String email, String groupname) {
  19. this.email = email;
  20. this.groupname = groupname;
  21. }
  22. public String getEmail() {
  23. return email;
  24. }
  25. public void setEmail(String email) {
  26. this.email = email;
  27. }
  28. public String getGroupname() {
  29. return groupname;
  30. }
  31. public void setGroupname(String groupname) {
  32. this.groupname = groupname;
  33. }
  34. }

字段groupname用于给定用户角色。 该表是一个映射表,用户 ID 映射到某个角色。 为了使此示例简单,在本教程中创建的所有帐户都将具有“用户”角色。 您可以根据需要添加更多角色。 角色不是预定义的,您可以随意命名。 角色之间的区别在于实现。 例如,如果您具有“管理员”角色,则将要赋予它比“用户”更多的特权。 您必须以编程方式限制一个角色,并以编程方式为另一个角色提供更多选择,并在您自己的代码中进行管理。

persistence.xml文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <persistence xmlns="http://java.sun.com/xml/ns/persistence"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
  5. http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
  6. version="2.0">
  7. <persistence-unit name="tutorialsPU" transaction-type="JTA">
  8. <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
  9. <jta-data-source>jdbc/tutorialsDS</jta-data-source>
  10. <class>net.javatutorial.tutorials.gfauthexample.entity.User</class>
  11. <class>net.javatutorial.tutorials.gfauthexample.entity.Group</class>
  12. <properties>
  13. <!-- tables will be created only if they do not exist. Use for production
  14. <property name="eclipselink.ddl-generation" value="create-tables"/>
  15. -->
  16. <!-- will first drop the existing table, and then create the new table. Use for development
  17. <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
  18. -->
  19. <property name="eclipselink.logging.level" value="INFO"/>
  20. </properties>
  21. </persistence-unit>
  22. </persistence>

在此文件中,我们指定持久层属性。 首先,我们创建一个命名的持久性单元tutorialsPU。 我们将在 EJB 中使用此持久性单元为EntityManager创建隔离的PersistenceContext。 其次,我们指定将用于此持久性单元的数据源。 数据源在 Glassfish 管理控制台中配置(有关更多详细信息,请阅读如何使用 MySQL 配置 Glassfish 4)。 第三,我们列出要包含在持久性单元中的实体类。 在我们的例子中,这是我们在上一步中创建的UserGroup类。

我还包括了两个其他有用的属性。 您可以根据需要将其注释掉:

如果数据库表不存在,此属性将生成数据库表

  1. <property name="eclipselink.ddl-generation" value="create-tables"/>

部署应用程序时,此属性将擦除现有表,并根据您的实体类创建新的(空)表。 您可以将其用于开发

  1. <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>

编写业务层 – UserEJB

在这一步中,我们将编写一个 Enterprise Java Bean(EJB),以在数据库中插入新用户并对其进行查询。 EJB 通常用作持久层(数据库实体)和表示层(托管 Bean 和 JSF 页面)之间的中介。

  1. package net.javatutorial.tutorials.gfauthexample.ejb;
  2. import java.util.logging.Level;
  3. import java.util.logging.Logger;
  4. import javax.ejb.Stateless;
  5. import javax.persistence.EntityManager;
  6. import javax.persistence.PersistenceContext;
  7. import javax.persistence.TypedQuery;
  8. import net.javatutorial.tutorials.gfauthexample.entity.Group;
  9. import net.javatutorial.tutorials.gfauthexample.entity.User;
  10. import net.javatutorial.tutorials.gfauthexample.utils.AuthenticationUtils;
  11. @Stateless
  12. public class UserEJB {
  13. @PersistenceContext(unitName="tutorialsPU")
  14. private EntityManager em;
  15. public User createUser(User user) {
  16. try {
  17. user.setPassword(AuthenticationUtils.encodeSHA256(user.getPassword()));
  18. } catch (Exception e) {
  19. Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
  20. e.printStackTrace();
  21. }
  22. Group group = new Group();
  23. group.setEmail(user.getEmail());
  24. group.setGroupname(Group.USERS_GROUP);
  25. em.persist(user);
  26. em.persist(group);
  27. return user;
  28. }
  29. public User findUserById(String id) {
  30. TypedQuery<User> query = em.createNamedQuery("findUserById", User.class);
  31. query.setParameter("email", id);
  32. User user = null;
  33. try {
  34. user = query.getSingleResult();
  35. } catch (Exception e) {
  36. // getSingleResult throws NoResultException in case there is no user in DB
  37. // ignore exception and return NULL for user instead
  38. }
  39. return user;
  40. }
  41. }

请注意,第 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 的方法

  1. package net.javatutorial.tutorials.gfauthexample.utils;
  2. import java.io.UnsupportedEncodingException;
  3. import java.security.MessageDigest;
  4. import java.security.NoSuchAlgorithmException;
  5. import javax.xml.bind.DatatypeConverter;
  6. public final class AuthenticationUtils {
  7. /**
  8. * Returns SHA-256 encoded string
  9. * @param password - the string to be encoded
  10. * @return SHA-256 encoded string
  11. * @throws UnsupportedEncodingException if UTF-8 is not supported by the system
  12. * @throws NoSuchAlgorithmException if SHA-256 is not supported by the system
  13. */
  14. public static String encodeSHA256(String password)
  15. throws UnsupportedEncodingException, NoSuchAlgorithmException {
  16. MessageDigest md = MessageDigest.getInstance("SHA-256");
  17. md.update(password.getBytes("UTF-8"));
  18. byte[] digest = md.digest();
  19. return DatatypeConverter.printBase64Binary(digest).toString();
  20. }
  21. }

配置 Glassfish 安全领域

我们需要在 Glassfish 中创建和配置一个新的 JDBC 安全领域。

localhost:4848打开 Glassfish 管理控制台。 打开“配置 -> 服务器配置 -> 安全 -> 领域”,然后单击“新建…”按钮以创建新的安全领域。

基于 Glassfish 表单的身份验证示例 - 图3

Glassfish 服务器配置安全领域

基于 Glassfish 表单的身份验证示例 - 图4

创建新的 JDBC 安全领域

填写以下字段:

  • Namejdbc-realm
  • Class Namecom.sun.enterprise.security.auth.realm.jdbc.JDBCRealm
  • JAAS ContextjdbcRealm
  • JNDIjdbc/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文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!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">
  3. <glassfish-web-app error-url="">
  4. <security-role-mapping>
  5. <role-name>users</role-name>
  6. <group-name>users</group-name>
  7. </security-role-mapping>
  8. <class-loader delegate="true" />
  9. <jsp-config>
  10. <property name="keepgenerated" value="true">
  11. <description>Keep a copy of the generated servlet class' java code.</description>
  12. </property>
  13. </jsp-config>
  14. <parameter-encoding default-charset="UTF-8" />
  15. </glassfish-web-app>

我们必须在glassfish-web.xml文件中指定security-role-mapping,并添加角色名称和组名称

web.xml文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  5. version="3.1">
  6. <display-name>Form Based Auth Example</display-name>
  7. <context-param>
  8. <param-name>javax.faces.PROJECT_STAGE</param-name>
  9. <param-value>Development</param-value>
  10. </context-param>
  11. <context-param>
  12. <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
  13. <param-value>.xhtml</param-value>
  14. </context-param>
  15. <servlet>
  16. <servlet-name>Faces Servlet</servlet-name>
  17. <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
  18. <load-on-startup>1</load-on-startup>
  19. </servlet>
  20. <servlet-mapping>
  21. <servlet-name>Faces Servlet</servlet-name>
  22. <url-pattern>*.xhtml</url-pattern>
  23. </servlet-mapping>
  24. <session-config>
  25. <session-timeout>
  26. 300
  27. </session-timeout>
  28. </session-config>
  29. <welcome-file-list>
  30. <welcome-file>signin.xhtml</welcome-file>
  31. </welcome-file-list>
  32. <!-- SECURITY -->
  33. <login-config>
  34. <auth-method>FORM</auth-method>
  35. <realm-name>jdbc-realm</realm-name>
  36. <form-login-config>
  37. <form-login-page>/signin.xhtml</form-login-page>
  38. <form-error-page>/signin.xhtml</form-error-page>
  39. </form-login-config>
  40. </login-config>
  41. <security-role>
  42. <description/>
  43. <role-name>users</role-name>
  44. </security-role>
  45. <security-constraint>
  46. <display-name>Restricted to users</display-name>
  47. <web-resource-collection>
  48. <web-resource-name>Restricted Access</web-resource-name>
  49. <url-pattern>/user/*</url-pattern>
  50. </web-resource-collection>
  51. <auth-constraint>
  52. <role-name>users</role-name>
  53. </auth-constraint>
  54. <user-data-constraint>
  55. <transport-guarantee>NONE</transport-guarantee>
  56. </user-data-constraint>
  57. </security-constraint>
  58. </web-app>

web.xml部署描述符也需要进行一些更改。 我们将在此处配置安全规则。

首先,我们配置会话超时。 这是使用户会话保持活动状态的时间。 当会话超时时,用户不必再次登录。 该值以分钟为单位。 300 表示用户会话将持续 5 个小时。

其次,我们将welcome-file设置为signin.xhtml。 当用户打开应用程序时,将显示登录屏幕。

<login-config>中设置以下内容:

  • auth-methodFORM – 用户使用 Web 表单输入用户名和密码
  • realm-namejdbc-realm – 这是我们在上一步中配置的安全领域的名称
  • form-login-pageform-error-page是带有登录表单的 xhtml 文件和在发生登录错误时显示的 xhtml 文件(我们都指向signin.xhtml,因为在此文件中进行了错误处理)

我们只有一个安全角色–用户

<security-constraints>标签中,我们为特定用户角色定义了受限资源。 换句话说 – user目录中的每个文件都需要登录才能访问。 我们将在其中放置私有 XHTML 文件。

创建展示层

基于 Glassfish 表单的身份验证示例 - 图5

注册表单

注册表单页面

在注册表单中,我们要求提供用户名,电子邮件地址和密码。 我们还要求用户确认密码。 我们将验证电子邮件,密码和确认密码字段中的用户输入。

首先让我们看一下 JSF XHTML 文件和ManagedBean

  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  2. <html xmlns="http://www.w3.org/1999/xhtml"
  3. xmlns:h="http://java.sun.com/jsf/html"
  4. xmlns:f="http://java.sun.com/jsf/core">
  5. <h:head>
  6. <title>register</title>
  7. </h:head>
  8. <h:body>
  9. <h:form>
  10. <!-- register a PostValidateEvent -->
  11. <f:event listener="#{registerView.validatePassword}"
  12. type="postValidate" />
  13. <h3>Create new account</h3>
  14. <h:panelGrid columns="2">
  15. <h:outputLabel for="name">Name:</h:outputLabel>
  16. <h:inputText id="name" value="#{registerView.name}" required="true"
  17. requiredMessage="Please enter your name" maxlength="30"></h:inputText>
  18. <h:outputLabel for="email">E-Mail:</h:outputLabel>
  19. <h:inputText id="email" value="#{registerView.email}" required="true"
  20. requiredMessage="Please enter your e-mail address">
  21. <f:validator validatorId="emailValidator" />
  22. </h:inputText>
  23. <h:outputLabel for="password">Password:</h:outputLabel>
  24. <h:inputSecret id="password" value="#{registerView.password}"
  25. required="true" requiredMessage="Please enter password"
  26. validatorMessage="Password must contain atleast one lowercase character,
  27. uppercase character, a digit and it's length must be between 6 and 20 characters">
  28. <f:validateRegex pattern="((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20})" />
  29. </h:inputSecret>
  30. <h:outputLabel for="confirmpassword">Confirm password:</h:outputLabel>
  31. <h:inputSecret id="confirmpassword"
  32. value="#{registerView.confirmPassword}" required="true"
  33. requiredMessage="Please confirm your password"></h:inputSecret>
  34. <h:commandButton action="#{registerView.register}" value="Register"></h:commandButton>
  35. </h:panelGrid>
  36. </h:form>
  37. <br />
  38. <br />
  39. <h:link value="I already have an account" outcome="signin" />
  40. </h:body>
  41. </html>

注册视图

  1. package net.javatutorial.tutorials.gfauthexample.managedbeans;
  2. import java.io.Serializable;
  3. import java.util.logging.Logger;
  4. import javax.faces.application.FacesMessage;
  5. import javax.faces.bean.ManagedBean;
  6. import javax.faces.bean.SessionScoped;
  7. import javax.faces.component.UIComponent;
  8. import javax.faces.component.UIInput;
  9. import javax.faces.context.FacesContext;
  10. import javax.faces.event.ComponentSystemEvent;
  11. import javax.inject.Inject;
  12. import net.javatutorial.tutorials.gfauthexample.ejb.UserEJB;
  13. import net.javatutorial.tutorials.gfauthexample.entity.User;
  14. @ManagedBean
  15. @SessionScoped
  16. public class RegisterView implements Serializable {
  17. private static final long serialVersionUID = 1685823449195612778L;
  18. private static Logger log = Logger.getLogger(RegisterView.class.getName());
  19. @Inject
  20. private UserEJB userEJB;
  21. private String name;
  22. private String email;
  23. private String password;
  24. private String confirmPassword;
  25. public void validatePassword(ComponentSystemEvent event) {
  26. FacesContext facesContext = FacesContext.getCurrentInstance();
  27. UIComponent components = event.getComponent();
  28. // get password
  29. UIInput uiInputPassword = (UIInput) components.findComponent("password");
  30. String password = uiInputPassword.getLocalValue() == null ? "" : uiInputPassword.getLocalValue().toString();
  31. String passwordId = uiInputPassword.getClientId();
  32. // get confirm password
  33. UIInput uiInputConfirmPassword = (UIInput) components.findComponent("confirmpassword");
  34. String confirmPassword = uiInputConfirmPassword.getLocalValue() == null ? ""
  35. : uiInputConfirmPassword.getLocalValue().toString();
  36. // Let required="true" do its job.
  37. if (password.isEmpty() || confirmPassword.isEmpty()) {
  38. return;
  39. }
  40. if (!password.equals(confirmPassword)) {
  41. FacesMessage msg = new FacesMessage("Confirm password does not match password");
  42. msg.setSeverity(FacesMessage.SEVERITY_ERROR);
  43. facesContext.addMessage(passwordId, msg);
  44. facesContext.renderResponse();
  45. }
  46. if (userEJB.findUserById(email) != null) {
  47. FacesMessage msg = new FacesMessage("User with this e-mail already exists");
  48. msg.setSeverity(FacesMessage.SEVERITY_ERROR);
  49. facesContext.addMessage(passwordId, msg);
  50. facesContext.renderResponse();
  51. }
  52. }
  53. public String register() {
  54. User user = new User(email, password, name);
  55. userEJB.createUser(user);
  56. log.info("New user created with e-mail: " + email + " and name: " + name);
  57. return "regdone";
  58. }
  59. public String getName() {
  60. return name;
  61. }
  62. public void setName(String name) {
  63. this.name = name;
  64. }
  65. public String getEmail() {
  66. return email;
  67. }
  68. public void setEmail(String email) {
  69. this.email = email;
  70. }
  71. public String getPassword() {
  72. return password;
  73. }
  74. public void setPassword(String password) {
  75. this.password = password;
  76. }
  77. public String getConfirmPassword() {
  78. return confirmPassword;
  79. }
  80. public void setConfirmPassword(String confirmPassword) {
  81. this.confirmPassword = confirmPassword;
  82. }
  83. }

用户输入验证

验证规则:

  • 所有字段均为必填项(不允许使用任何空字段)
  • 合法的邮件地址
  • 密码必须至少包含 1 个小写字符,1 个大写字符,1 个数字
  • 密码长度必须在 6 到 20 个字符之间
  • 确认密码必须等于密码
  • 用户电子邮件应该是唯一的(不用于其他注册)

基于 Glassfish 表单的身份验证示例 - 图6

用户输入表单验证

有 3 种可能的方法来验证 JSF 2 中的字段。我将演示全部 3 种方法。

使用JSFvalidateRegex

我们使用此方法验证密码字段。 我们要做的就是插入具有给定正则表达式模式的f:validateRegex标签。 如果您想了解更多有关正则表达式的信息,请阅读基本 Java 正则表达式教程

  1. <f:validateRegex pattern="((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20})" />

我们还在validatorMessage属性内的 JSF 文件中指定了错误消息

在系统事件(PostValidateEvent)中验证

确认密码字段通过名为PostValidateEvent的系统事件进行验证。

我们在 XHTML 文件中注册了一个新事件:

  1. <f:event listener="#{registerView.validatePassword}" type="postValidate" />

用户单击“注册”按钮时,将触发RegisterView bean 中的public void validatePassword(ComponentSystemEvent event)方法

使用自定义验证程序类

对于电子邮件验证,我们使用一个自定义类,该类实现javax.faces.validator.Validator接口

Validator类必须实现一个称为validate的方法

  1. public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException

在此方法中,我们根据正则表达式模式验证用户输入,如果该模式不正确,我们将抛出ValidatorException

  1. package net.javatutorial.tutorials.gfauthexample.validators;
  2. import java.util.regex.Matcher;
  3. import java.util.regex.Pattern;
  4. import javax.faces.application.FacesMessage;
  5. import javax.faces.component.UIComponent;
  6. import javax.faces.context.FacesContext;
  7. import javax.faces.validator.FacesValidator;
  8. import javax.faces.validator.Validator;
  9. import javax.faces.validator.ValidatorException;
  10. @FacesValidator("emailValidator")
  11. public class EmailValidator implements Validator {
  12. private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-]+(\\."
  13. + "[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*" + "(\\.[A-Za-z]{2,})$";
  14. private Pattern pattern;
  15. private Matcher matcher;
  16. public EmailValidator() {
  17. pattern = Pattern.compile(EMAIL_PATTERN);
  18. }
  19. @Override
  20. public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
  21. matcher = pattern.matcher(value.toString());
  22. if (!matcher.matches()) {
  23. throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Invalid e-mail address", null));
  24. }
  25. }
  26. }

我们必须使用以下行在 XHTML 文件中包含给定字段的自定义验证器

  1. <f:validator validatorId="emailValidator" />

检查电子邮件是否已在另一个注册中使用

为此,我们不需要验证器。 您可以在ManagedBeanRegisterView中使用类似这样的简单检查

  1. if (userEJB.findUserById(email) != null) { // email is already used }

成功注册

如果用户在注册表单中输入正确的数据,则将创建一个新的User对象并将其插入数据库中。

  1. User user = new User(email, password, name);
  2. userEJB.createUser(user);

请注意,我们还在UserEJB#createUser方法内为该用户创建了Group对象

  1. Group group = new Group();
  2. group.setEmail(user.getEmail());
  3. group.setGroupname(Group.USERS_GROUP);
  4. em.persist(user);
  5. em.persist(group);

数据库条目如下所示:

基于 Glassfish 表单的身份验证示例 - 图7

用户表中的条目

基于 Glassfish 表单的身份验证示例 - 图8

用户组表中的条目

最后,用户被重定向到regdone.xhtml页面

基于 Glassfish 表单的身份验证示例 - 图9

regdone.xhtml页面

  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  2. <html xmlns="http://www.w3.org/1999/xhtml"
  3. xmlns:h="http://java.sun.com/jsf/html" >
  4. <h:head>
  5. <title>Registration done</title>
  6. </h:head>
  7. <h:body>
  8. <h3>Your account has been successfully created</h3>
  9. <br/><br/>
  10. <h:link value="Sign in with your new account" outcome="signin" />
  11. </h:body>
  12. </html>

登录表单

在此表单中,我们需要用户的电子邮件地址和密码。 表单针对空白字段进行了验证。 在LoginView中检查用户名和密码的有效性。

基于 Glassfish 表单的身份验证示例 - 图10

登录表单

如果用户输入了有效的登录数据,则会创建一个新的java.security.Principal对象。 我们还将用户添加到externalContext的会话地图中

  1. ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
  2. Map<String, Object> sessionMap = externalContext.getSessionMap();
  3. sessionMap.put("User", user);

signin.xhtml文件

  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  2. <html xmlns="http://www.w3.org/1999/xhtml"
  3. xmlns:h="http://java.sun.com/jsf/html">
  4. <h:head>
  5. <title>login</title>
  6. </h:head>
  7. <h:body>
  8. <h:form>
  9. <h3>Please Sign In</h3>
  10. <h:panelGrid columns="2">
  11. <h:outputLabel for="email">E-Mail:</h:outputLabel>
  12. <h:inputText id="email" value="#{loginView.email}" required="true"
  13. requiredMessage="Please enter your e-mail address"></h:inputText>
  14. <h:outputLabel for="password">Password:</h:outputLabel>
  15. <h:inputSecret id="password" value="#{loginView.password}"
  16. required="true" requiredMessage="Please enter password"></h:inputSecret>
  17. <h:commandButton action="#{loginView.login}" value="Login"></h:commandButton>
  18. </h:panelGrid>
  19. </h:form>
  20. <br />
  21. <br />
  22. <h:link value="Create new account" outcome="register" />
  23. </h:body>
  24. </html>

LoginView ManagedBean

  1. package net.javatutorial.tutorials.gfauthexample.managedbeans;
  2. import java.io.Serializable;
  3. import java.security.Principal;
  4. import java.util.Map;
  5. import java.util.logging.Level;
  6. import java.util.logging.Logger;
  7. import javax.faces.application.FacesMessage;
  8. import javax.faces.bean.ManagedBean;
  9. import javax.faces.bean.SessionScoped;
  10. import javax.faces.context.ExternalContext;
  11. import javax.faces.context.FacesContext;
  12. import javax.inject.Inject;
  13. import javax.servlet.ServletException;
  14. import javax.servlet.http.HttpServletRequest;
  15. import javax.servlet.http.HttpSession;
  16. import net.javatutorial.tutorials.gfauthexample.ejb.UserEJB;
  17. import net.javatutorial.tutorials.gfauthexample.entity.User;
  18. @ManagedBean
  19. @SessionScoped
  20. public class LoginView implements Serializable {
  21. private static final long serialVersionUID = 3254181235309041386L;
  22. private static Logger log = Logger.getLogger(LoginView.class.getName());
  23. @Inject
  24. private UserEJB userEJB;
  25. private String email;
  26. private String password;
  27. private User user;
  28. public String login() {
  29. FacesContext context = FacesContext.getCurrentInstance();
  30. HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
  31. try {
  32. request.login(email, password);
  33. } catch (ServletException e) {
  34. context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Login failed!", null));
  35. return "signin";
  36. }
  37. Principal principal = request.getUserPrincipal();
  38. this.user = userEJB.findUserById(principal.getName());
  39. log.info("Authentication done for user: " + principal.getName());
  40. ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
  41. Map<String, Object> sessionMap = externalContext.getSessionMap();
  42. sessionMap.put("User", user);
  43. if (request.isUserInRole("users")) {
  44. return "/user/privatepage?faces-redirect=true";
  45. } else {
  46. return "signin";
  47. }
  48. }
  49. public String logout() {
  50. FacesContext context = FacesContext.getCurrentInstance();
  51. HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
  52. try {
  53. this.user = null;
  54. request.logout();
  55. // clear the session
  56. ((HttpSession) context.getExternalContext().getSession(false)).invalidate();
  57. } catch (ServletException e) {
  58. log.log(Level.SEVERE, "Failed to logout user!", e);
  59. }
  60. return "/signin?faces-redirect=true";
  61. }
  62. public User getAuthenticatedUser() {
  63. return user;
  64. }
  65. public String getEmail() {
  66. return email;
  67. }
  68. public void setEmail(String email) {
  69. this.email = email;
  70. }
  71. public String getPassword() {
  72. return password;
  73. }
  74. public void setPassword(String password) {
  75. this.password = password;
  76. }
  77. }

最后,用户被重定向到/user/privatepage.xhtml

基于 Glassfish 表单的身份验证示例 - 图11

私人页面

登出功能

注销很简单。 我们只需要执行注销请求并使会话无效

  1. public String logout() {
  2. FacesContext context = FacesContext.getCurrentInstance();
  3. HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
  4. try {
  5. this.user = null;
  6. request.logout();
  7. // clear the session
  8. ((HttpSession) context.getExternalContext().getSession(false)).invalidate();
  9. } catch (ServletException e) {
  10. log.log(Level.SEVERE, "Failed to logout user!", e);
  11. }
  12. return "/signin?faces-redirect=true";
  13. }

故障排除

对安全代码进行故障排除非常困难。 大多数组件不会引发有意义的异常。 这样做是故意进行的,以防止黑客在尝试破解应用程序的安全性时获得有用的信息。 不幸的是,我们也没有获得这些有用的信息。 如果发现一些问题,请尝试遵循例外。 两次检查您的安全领域配置,并检查所有密码编码是否正确。