1. 概述

本文将展示如何使用 Spring MVC 实现一个简单的登录页面,该应用的认证逻辑由后端的 Spring Security 处理。

关于如何使用 Spring Security 进行登录认证的详细配置,请参考这篇文章,它深入讲解了相关的配置和实现细节。

2. 登录页面

我们先定义一个非常基础的登录页面

<html>
<head></head>
<body>
   <h1>Login</h1>
   <form name='f' action="login" method='POST'>
      <table>
         <tr>
            <td>User:</td>
            <td><input type='text' name='username' value=''></td>
         </tr>
         <tr>
            <td>Password:</td>
            <td><input type='password' name='password' /></td>
         </tr>
         <tr>
            <td><input name="submit" type="submit" value="submit" /></td>
         </tr>
      </table>
  </form>
</body>
</html>

接下来,我们在客户端添加一个简单的校验逻辑,确保在提交表单前用户名和密码不为空。这里我们使用原生 JavaScript(当然你也可以用 jQuery):

<script type="text/javascript">
function validate() {
    if (document.f.username.value == "" && document.f.password.value == "") {
        alert("Username and password are required");
        document.f.username.focus();
        return false;
    }
    if (document.f.username.value == "") {
        alert("Username is required");
        document.f.username.focus();
        return false;
    }
    if (document.f.password.value == "") {
    alert("Password is required");
    document.f.password.focus();
        return false;
    }
}
</script>

如你所见,我们只是简单地检查了用户名和密码是否为空,如果为空就弹出提示框。虽然简单粗暴,但效果立竿见影 ✅

3. 消息本地化

接下来我们来处理前端消息的本地化。消息分为两类,处理方式也不同:

  1. 表单提交前由前端直接展示的消息,使用 JSP/JSTL 本地化机制(见第 4.3 节)
  2. 表单提交后,由 Spring MVC 处理并返回的消息,使用 Spring MVC 本地化机制(见第 4.2 节)

3.1. message.properties 文件

无论是哪种本地化方式,我们都需要为每种语言创建对应的 message.properties 文件,命名规则为:messages_[localeCode].properties

例如,要支持英文和西班牙文,我们创建以下两个文件:

  • messages_en.properties
  • messages_es_ES.properties

当然,英文的默认文件也可以命名为 messages.properties

这些文件放在项目的 classpath 下(如 src/main/resources),内容如下:

message.username=Username required
message.password=Password required
message.unauth=Unauthorized access!!
message.badCredentials=Invalid username or password
message.sessionExpired=Session timed out
message.logoutError=Sorry, error login out
message.logoutSucc=You logged out successfully

3.2. 配置 Spring MVC 本地化支持

Spring MVC 提供了 LocaleResolverLocaleChangeInterceptor 来支持多语言切换。我们需要在 MVC 配置类中添加如下配置:

@Override
public void addInterceptors(InterceptorRegistry registry) {
    LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
    localeChangeInterceptor.setParamName("lang");
    registry.addInterceptor(localeChangeInterceptor);
}

@Bean
public LocaleResolver localeResolver() {
    CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver();
    return cookieLocaleResolver;
}

默认情况下,LocaleResolver 会从 HTTP 请求头中获取语言设置。如果你想设置默认语言,可以这样:

@Bean
public LocaleResolver localeResolver() {
    CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver();
    cookieLocaleResolver.setDefaultLocale(Locale.ENGLISH);
    return cookieLocaleResolver;
}

这里使用的是 CookieLocaleResolver,它会将语言信息保存在客户端 Cookie 中,用户下次访问时依然保持语言设置。

当然你也可以使用 SessionLocaleResolver,它会将语言设置保存在 Session 中:

@Bean
public LocaleResolver localeResolver() {
    SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
    return sessionLocaleResolver;
}

另外,通过 lang 参数可以手动切换语言,例如:

<a href="?lang=en">English</a> |
<a href="?lang=es_ES">Spanish</a>

3.3. JSP/JSTL 本地化支持

JSP/JSTL 主要用于在 JSP 页面中显示本地化消息。要使用它们,需要在 pom.xml 中添加以下依赖:

<dependency>
    <groupId>jakarta.servlet.jsp</groupId>
    <artifactId>jakarta.servlet.jsp-api</artifactId>
    <version>3.1.1</version>
</dependency>
<dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
    <version>6.0.0</version>
</dependency>

4. 显示错误消息

4.1. 登录校验错误

为了让 JSP/JSTL 能显示本地化消息,我们需要对 login.jsp 做如下修改:

  1. 添加标签库声明:
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
  1. 指定消息资源文件:
<fmt:setBundle basename="messages" />
  1. 将消息绑定到变量中:
<fmt:message key="message.password" var="noPass" />
<fmt:message key="message.username" var="noUser" />
  1. 修改 JavaScript 校验逻辑,使用本地化消息:
<script type="text/javascript">
function validate() {
    if (document.f.username.value == "" && document.f.password.value == "") {
        alert("${noUser} and ${noPass}");
    document.f.username.focus();
    return false;
    }
    if (document.f.username.value == "") {
    alert("${noUser}");
    document.f.username.focus();
    return false;
     }
     if (document.f.password.value == "") {
    alert("${noPass}");
    document.f.password.focus();
    return false;
     }
}
</script>

4.2. 登录前的错误

有时登录页面会携带一些参数,比如注册成功后跳转过来。我们可以通过这些参数判断是否显示成功或失败消息:

<c:if test="${param.regSucc == true}">
    <div id="status">
    <spring:message code="message.regSucc">    
        </spring:message>
    </div>
</c:if>
<c:if test="${param.regError == true}">
    <div id="error">
        <spring:message code="message.regError">   
        </spring:message>
    </div>
</c:if>

4.3. 登录安全错误

如果登录失败,Spring Security 会跳转到错误页面,比如:/login.html?error=true

我们可以在页面中这样处理:

<c:if test="${param.error != null}">
    <div id="error">
        <spring:message code="message.badCredentials">   
        </spring:message>
    </div>
</c:if>

注意这里使用的是 <spring:message> 标签,表示消息是在 Spring MVC 处理时生成的。

完整的登录页面代码(包括 JS 校验和错误处理)可以在 GitHub 项目 中找到。

4.4. 登出错误

在登出页面中,我们可以通过如下代码判断是否有登出错误:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="sec"
    uri="http://www.springframework.org/security/tags"%>
<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<c:if test="${not empty SPRING_SECURITY_LAST_EXCEPTION}">
    <div id="error">
        <spring:message code="message.logoutError">    
        </spring:message>
    </div>
</c:if>
<c:if test="${param.logSucc == true}">
    <div id="success">
    <spring:message code="message.logoutSucc">    
        </spring:message>
    </div>
</c:if>
<html>
<head>
<title>Logged Out</title>
</head>
<body>    
    <a href="login.html">Login</a>
</body>
</html>

同样,如果 logSucc=true,则显示成功登出的消息。

5. Spring Security 配置

本篇文章重点在前端,后端配置只做简要说明。完整配置请参考这篇文章

5.1. 登录失败跳转

<form-login> 中配置登录失败跳转路径:

authentication-failure-url="/login.html?error=true"

5.2. 登出成功跳转

<logout 
  invalidate-session="false" 
  logout-success-url="/logout.html?logSucc=true" 
  delete-cookies="JSESSIONID" />

logout-success-url 表示登出成功后跳转的页面,并附带成功参数。

6. 总结

本文展示了如何为 Spring Security 应用实现一个登录页面,涵盖了登录校验、错误提示和消息本地化等内容。

在下一篇文章中,我们将继续实现完整的注册流程,最终构建一个可用于生产的登录注册系统。


原始标题:Baeldung

» 下一篇: Baeldung Weekly 34