Spring Security模板引擎之异常信息

1,382 阅读2分钟
原文链接: www.spring4all.com

摘要:

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},两者缺一不可。