SpringSecurity6从入门到上天系列第八篇:SpringSecurity当中的默认登录页面是如何产生的?
作者:mmseoamin日期:2023-12-18

😉😉 欢迎加入我们的学习交流群呀!

✅✅1:这是孙哥suns给大家的福利!

✨✨2:我们免费分享Netty、Dubbo、k8s、Mybatis、Spring等等很多应用和源码级别的高质量视频和笔记资料,你想学的我们这里都有!

🥭🥭3:QQ群:583783824   📚📚  工作微信:BigTreeJava 拉你进微信群,免费领取!

🍎🍎4:本文章内容出自上述:Spring应用课程!💞💞

💞💞5:以上内容,进群免费领取呦~ 💞💞💞💞

SpringSecurity6从入门到上天系列第八篇:SpringSecurity当中的默认登录页面是如何产生的?,第1张

知识铺垫

1:默认加载过滤器

        想要搞明白这个问题,我们需要复习一下SpringSecurity的30多个过滤器,其中标红的是启动时默认加载的一共有15个。这十五个当中和登录有关的一共有四个:UsernamePasswordAuthenticationFilter(处理表单登录)、DefaultLoginPageGeneratingFilter(配置登录页面)、ExceptionTranslationFilter(处理认证授权中的异常)、AuthorizationFilter(对请求进行访问权限处理)

2:调用链条图

SpringSecurity6从入门到上天系列第八篇:SpringSecurity当中的默认登录页面是如何产生的?,第2张

        其中粉色部分就是生成默认登录页面的区域,所以这块是我们需要着重研究的部分!   

一: 登录页面渲染流程

1:大致过程

        我们都知道,之前的文章中也都提到过,当我们在项目中引入SpringSecurity之后,所有的访问接口都会经过SringSecurity过滤器链条的拦截和处理!不论客户端发送的这个url请求在咱们的后台资源中是否存在,都必须经过这个认证检查。如果客户没哟U盾呢个路的话,就会进行强制登录那这个过程大概是个怎么过程呢?

        大概的过程是这样的:

        访问地址 http://localhost:800/hello。不管这个资源咱们的后台服务有没有,都会首先经过一堆的过滤器,这些个过滤器有Web服务自己定义的,有SpringSecurity自己加载的。

        当请求到达AuthorizationFilter 时,检查发现用户未认证,请求被拦截,并抛出AccessDeniedException异常

        抛出的 AccessDeniedException 异常会被 ExceptionTranslationFilter 捕获并启动身份验证

        在这个 Filter中会调用LoginUrlAuthenticationEntryPoint的commence 方法,要求重定向到login页面

        重定向到/login,也就是客户端发送login 请求/login 请求会被过滤器 DefaultLoginPageGeneratingFilter 拦截,并在过滤器中返回默认的登录页面

        所以,真正做用于认证检查的过滤器是AuthorizationFilter这个过滤器。

SpringSecurity6从入门到上天系列第八篇:SpringSecurity当中的默认登录页面是如何产生的?,第3张

        我们知道,所有的重定向都是客户端需要重新发请求,这个时候客户端往后台重新发/login请求,此时呢第一个拦截器依旧不会来接,因为此时只是客户端发送了一个/login请求,而不是点击的账号密码的提交。这个时候请求顺利的到达DefaultLoginPageGeneratingFilter这个过滤器,在这个过滤器当中会生成登录页面并且返回到前端页面。

SpringSecurity6从入门到上天系列第八篇:SpringSecurity当中的默认登录页面是如何产生的?,第4张

2:默认页面的生成核心代码

    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        boolean loginError = this.isErrorPage(request);
        boolean logoutSuccess = this.isLogoutSuccess(request);
        if (!this.isLoginUrlRequest(request) && !loginError && !logoutSuccess) {
            chain.doFilter(request, response);
        } else {
            String loginPageHtml = this.generateLoginPageHtml(request, loginError, logoutSuccess);
            //设置响应内容格式
            response.setContentType("text/html;charset=UTF-8");
            //设置响应长度
            response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
            //获取影响输出流,写到客户端浏览器。
            response.getWriter().write(loginPageHtml);
        }
    }
  private String generateLoginPageHtml(HttpServletRequest request, boolean loginError, boolean logoutSuccess) {
        String errorMsg = "Invalid credentials";
        if (loginError) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                AuthenticationException ex = (AuthenticationException)session.getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
                errorMsg = ex != null ? ex.getMessage() : "Invalid credentials";
            }
        }
        String contextPath = request.getContextPath();
        StringBuilder sb = new StringBuilder();
        sb.append("\n");
        sb.append("\n");
        sb.append("  \n");
        sb.append("    \n");
        sb.append("    \n");
        sb.append("    \n");
        sb.append("    \n");
        sb.append("    Please sign in\n");
        sb.append("    \n");
        sb.append("    \n");
        sb.append("  \n");
        sb.append("  \n");
        sb.append("     \n");
        if (this.formLoginEnabled) {
            sb.append("      
\n"); sb.append(" \n"); String var10001 = createError(loginError, errorMsg); sb.append(var10001 + createLogoutSuccess(logoutSuccess) + "

\n"); sb.append(" \n"); sb.append(" \n"); sb.append("

\n"); sb.append("

\n"); sb.append(" \n"); sb.append(" \n"); sb.append("

\n"); var10001 = this.createRememberMe(this.rememberMeParameter); sb.append(var10001 + this.renderHiddenInputs(request)); sb.append(" \n"); sb.append("
\n"); } Iterator var7; Map.Entry relyingPartyUrlToName; String url; String partyName; if (this.oauth2LoginEnabled) { sb.append(""); sb.append(createError(loginError, errorMsg)); sb.append(createLogoutSuccess(logoutSuccess)); sb.append("\n"); var7 = this.oauth2AuthenticationUrlToClientName.entrySet().iterator(); while(var7.hasNext()) { relyingPartyUrlToName = (Map.Entry)var7.next(); sb.append(" \n"); } sb.append("
"); url = (String)relyingPartyUrlToName.getKey(); sb.append(""); partyName = HtmlUtils.htmlEscape((String)relyingPartyUrlToName.getValue()); sb.append(partyName); sb.append(""); sb.append("
\n"); } if (this.saml2LoginEnabled) { sb.append(""); sb.append(createError(loginError, errorMsg)); sb.append(createLogoutSuccess(logoutSuccess)); sb.append("\n"); var7 = this.saml2AuthenticationUrlToProviderName.entrySet().iterator(); while(var7.hasNext()) { relyingPartyUrlToName = (Map.Entry)var7.next(); sb.append(" \n"); } sb.append("
"); url = (String)relyingPartyUrlToName.getKey(); sb.append(""); partyName = HtmlUtils.htmlEscape((String)relyingPartyUrlToName.getValue()); sb.append(partyName); sb.append(""); sb.append("
\n"); } sb.append("\n"); sb.append(""); return sb.toString(); }

        这样,就完成了将默认的登录页面王客户端浏览器输出。这是硬生生的在Java中拼出了HTML和各种前端样式、JS功能,返回给浏览器,浏览器在进行解析,然后客户输入完毕之后,就可以进行验证了。