摘要:
Spring boot以其众多友谊的特性,如零配置、微服务等,吸引了很多的粉丝。而其与Spring Security安全框架的无缝结合,使其具备的安全的特性。在此基础上使用Thymeleaf模板引擎进行渲染,静动态结合,让页面开发更加简单、直观。Spring boot以其众多友谊的特性,如零配置、微服务等,吸引了很多的粉丝。而其与Spring Security安全框架的无缝结合,使其具备的安全的特性。在此基础上使用 Thymeleaf模板引擎进行渲染,静动态结合,让页面开发更加简单、直观。
为了纯粹的讲解本文的核心重点,我采用了最简单的一种设计,通过表单提交登录的用户名和密码是登录接口。在初学的过程中,我也不例外的采用个这种方式。表单设计见下图。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title
</head>
<body>
<form th:action="@{/login}" th:method="post">
<input type="text" name="username" placeholder="用户名" maxlength="200"/>
<input type="password" name="password" placeholder="密码" maxlength="200"/>
<button type="submit">登录</button>
</form>
</body>
</html>
登录成功,完成正常的主页面跳转,这个不存在问题。存在问题的是,登录失败了该咋办呢?我就在考虑,由于thymeleaf的局部刷新操作,登录失败了将登录失败的异常信息显示在登录页面,也就是表单上。于是,我的登录设计又变成了如下图所示的表单。
<form th:action="@{/login}" th:method="post">
<input type="text" name="username" placeholder="用户名" maxlength="200"/>
<input type="password" name="password" placeholder="密码" maxlength="200"/>
<div th:if="${param.error}">
<div class="alert alert-danger"> 用户名或密码错误,请重试!</div>
</div>
<button type="submit">登录</button>
</form>
通过这种方法,当登录表单验证失败时,会该用户显示”用户名或密码错误,请重试”,这当然是比较好的,但是验证失败的情况不仅仅是用户名或密码错误吧,应该还有其它的情形吧?那就是针对Exceptionde 异常捕获信息获取。
如何才能将异常信息返回到Thymeleaf。经过一段时间的调研,发现Spring boot提供了比较完美的解决方案,而其秘密就在Spring Security的配置中。案例Spring Security配置如下图所示。
.formLogin()
.loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
.loginProcessingUrl(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
.failureUrl("/oauth2/login?error")
我在阅读Spring Security源码时,发现springsecurity中`SimpleUrlAuthenticationFailureHandler`的处理异常方法如下:
/**
* Performs the redirect or forward to the {@code defaultFailureUrl} if set, otherwise
* returns a 401 error code.
*
* If redirecting or forwarding, {@code saveException} will be called to cache the
* exception for use in the target view.
*/
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
if (defaultFailureUrl == null) {
logger.debug("No failure URL set, sending 401 Unauthorized error");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Authentication Failed: " + exception.getMessage());
}
else {
//保存异常信息的关键所在
saveException(request, exception);
if (forwardToDestination) {
logger.debug("Forwarding to " + defaultFailureUrl);
request.getRequestDispatcher(defaultFailureUrl)
.forward(request, response);
}
else {
logger.debug("Redirecting to " + defaultFailureUrl);
redirectStrategy.sendRedirect(request, response, defaultFailureUrl);
}
}
}
重点就在saveException函数,而此函数的具体代码如下:
/**
* Caches the {@code AuthenticationException} for use in view rendering.
*
* If {@code forwardToDestination} is set to true, request scope will be used,
* otherwise it will attempt to store the exception in the session. If there is no
* session and {@code allowSessionCreation} is {@code true} a session will be created.
* Otherwise the exception will not be stored.
*/
protected final void saveException(HttpServletRequest request,
AuthenticationException exception) {
if (forwardToDestination) {
request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
}
else {
HttpSession session = request.getSession(false);
if (session != null || allowSessionCreation) {
request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION,
exception);
}
}
}
从这段代码中,我们可以清晰的看到,认证失败的异常信息被保存在Session中,如果我们可以读取Session中的信息那么,之前所遇到的问题就迎刃而解了吧。 事实上,事物的发展从来不是一帆风顺的,解决问题也是类似。我了解到Thymeleaf提供了读取缓存Session的方案,就迫不及待的进行尝试,于是乎,我的登录表单又变成了如下模样。
<form th:action="@{/login}" th:method="post">
<input type="text" name="username" placeholder="用户名" maxlength="200"/>
<input type="password" name="password" placeholder="密码" maxlength="200"/>
<div th:if="${param.error}">
<--这部分是Thymleaf缓存的读取方式。-->
<div class="alert alert-danger" th:text="${session.SPRING_SECURITY_LAST_EXCEPTION.message}"> </div>
</div>
<button type="submit">登录</button>
</form>
这样就可以显示验证失败的具体信息了吗?经过我的测试,我发现,此信息是未定义。也就是说,模样未读取到缓存中的信息。最后从网上找到了一个代码片段:
failureUrl("/oauth2/login?error=true")
总结:
springsecurity整合Thymeleaf模板返回异常信息的方法需要两个必须的步骤:
其一:将登录失败的url设置为”/login?error=true”(即后缀带?error=true),使前端的thymleaf可以读取Session;
其二:Thymeleaf提供的读取缓存中信息的方法${session.SPRING_SECURITY_LAST_EXCEPTION.message},两者缺一不可。