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. 消息本地化
接下来我们来处理前端消息的本地化。消息分为两类,处理方式也不同:
- 表单提交前由前端直接展示的消息,使用 JSP/JSTL 本地化机制(见第 4.3 节)
- 表单提交后,由 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 提供了 LocaleResolver
和 LocaleChangeInterceptor
来支持多语言切换。我们需要在 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
做如下修改:
- 添加标签库声明:
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
- 指定消息资源文件:
<fmt:setBundle basename="messages" />
- 将消息绑定到变量中:
<fmt:message key="message.password" var="noPass" />
<fmt:message key="message.username" var="noUser" />
- 修改 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 应用实现一个登录页面,涵盖了登录校验、错误提示和消息本地化等内容。
在下一篇文章中,我们将继续实现完整的注册流程,最终构建一个可用于生产的登录注册系统。