在本文中,我们将介绍如何使用 Spring MVC 实现一个 简单的登录页面 ,该应用程序在后端使用 Spring Security 进行身份验证。
我们首先定义一个非常简单的登录页面:
<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>
您可以看到,我们只需检查用户名或密码字段是否为空,如果为空,将弹出一个 javascript 弹窗以显示相应的消息。
接下来,让我们将正在使用的消息本地化应用到前端。有以下消息类型,每个都以不同的方式进行本地化:
在这两种情况下,我们需要为要支持的每种语言创建一个 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
Spring MVC 提供了一个 LocaleResolver ,它与 LocaleChangeInterceptor API 一起使用,可根据区域(locale)设置显示不同语言的消息。要配置本地化,我们需要在 MVC 配置中定义以下 bean:
@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; } @Bean public MessageSource messageSource() { ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); messageSource.setBasename("classpath:messages"); messageSource.setUseCodeAsDefaultMessage(true); messageSource.setDefaultEncoding("UTF-8"); messageSource.setCacheSeconds(0); return messageSource; }
这里有一个重要和更高级的注意事项是,我们需要确保 messageSource bean 定义在正确的 Spring 上下文中 — 它将在此上下文中使用。请记住,Web 应用程序具有根上下文,并且有一个(或多个)servlet 上下文。
默认情况下,locale 解析器(resolver)将从 HTTP 头获取区域代码。要强制使用默认语言环境,我们需要在 localeResolver() 上设置它:
@Bean public LocaleResolver localeResolver() { CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver(); cookieLocaleResolver.setDefaultLocale(Locale.ENGLISH); return cookieLocaleResolver; }
该 locale 解析器是一个 CookieLocaleResolver ,这意味着它将客户端 cookie 中的 locale 信息存储起来。 因此,它将在每次登录时以及整个访问过程中记住用户的 locale 设置。
此外,还有一个 SessionLocaleResolver ,可以记住整个会话中的 locale 设置。 要使用此 LocaleResolver ,我们需要使用以下方法替换上述方法:
@Bean public LocaleResolver localeResolver() { SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver(); return sessionLocaleResolver; }
最后,请注意, LocaleChangeInterceptor 将根据通过登录页面的简单链接发送的 lang 参数的值来更改语言环境:
<a href="?lang=en">English</a> | <a href="?lang=es_ES">Spanish</a>
JSP/JSTL API 将用于显示在 jsp 页面获取的本地化消息。要使用 jsp 本地化库,我们应该将以下依赖项添加到 pom.xml 中:
<dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.2-b01</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency>
为了使用 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" />
<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>
如果之前的操作发生失败,将被传递一个错误参数到登录页面。例如,注册表单提交按钮将加载登录页面。 如果注册成功,则在登录表单中显示成功消息,如果失败,则会出现错误消息。
在下面的登录表单示例中,我们通过拦截与 regSucc 和 regError 参数来实现,并根据它们的值显示本地化消息。
<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>
如果由于某种原因登录过程发生失败,Spring Security 将会重定向到登录错误 URL,我们将其指定为 /login.html?error=true 。
因此,类似于如何在页面中显示注册状态,我们需要在登录发生问题的情况下做同样的处理:
<c:if test="${param.error != null}"> <div id="error"> <spring:message code="message.badCredentials"> </spring:message> </div> </c:if>
请注意,我们使用了 标签。这意味着在 Spring MVC 处理期间将生成错误消息。
完整的登录页面包括了 js 验证和附加的状态消息,您可以在 github 项目中找到。
在下面的示例中, logout.html 页面中的 jsp 代码 将检查注销过程中是否有存在错误。
例如,如果自定义注销 handler 在重定向到注销页面之前尝试存储用户数据时发生了持久化异常。 虽然这些错误很罕见,但我们也应该尽可能地处理。
让我们来看看完整的 logout.jsp :
<%@ 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 ,则会显示本地化成功消息。
本文的重点是前端的登录过程,而不是后端。
指令将应用程序的引导到处理登录错误 URL:
authentication-failure-url="/login.html?error=true"
<logout invalidate-session="false" logout-success-url="/logout.html?logSucc=true" delete-cookies="JSESSIONID" />
logout-success-url属性简单地重定向到注销页面,该参数确定注销成功。