😉😉 欢迎加入我们的学习交流群呀!
✅✅1:这是孙哥suns给大家的福利!
✨✨2:我们免费分享Netty、Dubbo、k8s、Mybatis、Spring等等很多应用和源码级别的高质量视频和笔记资料,你想学的我们这里都有!
🥭🥭3:QQ群:583783824 📚📚 工作微信:BigTreeJava 拉你进微信群,免费领取!
🍎🍎4:本文章内容出自上述:Spring应用课程!💞💞
💞💞5:以上内容,进群免费领取呦~ 💞💞💞💞
知识铺垫
1:默认加载过滤器
想要搞明白这个问题,我们需要复习一下SpringSecurity的30多个过滤器,其中标红的是启动时默认加载的一共有15个。这十五个当中和登录有关的一共有四个:UsernamePasswordAuthenticationFilter(处理表单登录)、DefaultLoginPageGeneratingFilter(配置登录页面)、ExceptionTranslationFilter(处理认证授权中的异常)、AuthorizationFilter(对请求进行访问权限处理)
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这个过滤器。
我们知道,所有的重定向都是客户端需要重新发请求,这个时候客户端往后台重新发/login请求,此时呢第一个拦截器依旧不会来接,因为此时只是客户端发送了一个/login请求,而不是点击的账号密码的提交。这个时候请求顺利的到达DefaultLoginPageGeneratingFilter这个过滤器,在这个过滤器当中会生成登录页面并且返回到前端页面。
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("<!DOCTYPE html>\n");sb.append("<html lang=\"en\">\n");sb.append(" <head>\n");sb.append(" <meta charset=\"utf-8\">\n");sb.append(" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n");sb.append(" <meta name=\"description\" content=\"\">\n");sb.append(" <meta name=\"author\" content=\"\">\n");sb.append(" <title>Please sign in</title>\n");sb.append(" <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n");sb.append(" <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n");sb.append(" </head>\n");sb.append(" <body>\n");sb.append(" <div class=\"container\">\n");if (this.formLoginEnabled) {sb.append(" <form class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.authenticationUrl + "\">\n");sb.append(" <h2 class=\"form-signin-heading\">Please sign in</h2>\n");String var10001 = createError(loginError, errorMsg);sb.append(var10001 + createLogoutSuccess(logoutSuccess) + " <p>\n");sb.append(" <label for=\"username\" class=\"sr-only\">Username</label>\n");sb.append(" <input type=\"text\" id=\"username\" name=\"" + this.usernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n");sb.append(" </p>\n");sb.append(" <p>\n");sb.append(" <label for=\"password\" class=\"sr-only\">Password</label>\n");sb.append(" <input type=\"password\" id=\"password\" name=\"" + this.passwordParameter + "\" class=\"form-control\" placeholder=\"Password\" required>\n");sb.append(" </p>\n");var10001 = this.createRememberMe(this.rememberMeParameter);sb.append(var10001 + this.renderHiddenInputs(request));sb.append(" <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n");sb.append(" </form>\n");}Iterator var7;Map.Entry relyingPartyUrlToName;String url;String partyName;if (this.oauth2LoginEnabled) {sb.append("<h2 class=\"form-signin-heading\">Login with OAuth 2.0</h2>");sb.append(createError(loginError, errorMsg));sb.append(createLogoutSuccess(logoutSuccess));sb.append("<table class=\"table table-striped\">\n");var7 = this.oauth2AuthenticationUrlToClientName.entrySet().iterator();while(var7.hasNext()) {relyingPartyUrlToName = (Map.Entry)var7.next();sb.append(" <tr><td>");url = (String)relyingPartyUrlToName.getKey();sb.append("<a href=\"").append(contextPath).append(url).append("\">");partyName = HtmlUtils.htmlEscape((String)relyingPartyUrlToName.getValue());sb.append(partyName);sb.append("</a>");sb.append("</td></tr>\n");}sb.append("</table>\n");}if (this.saml2LoginEnabled) {sb.append("<h2 class=\"form-signin-heading\">Login with SAML 2.0</h2>");sb.append(createError(loginError, errorMsg));sb.append(createLogoutSuccess(logoutSuccess));sb.append("<table class=\"table table-striped\">\n");var7 = this.saml2AuthenticationUrlToProviderName.entrySet().iterator();while(var7.hasNext()) {relyingPartyUrlToName = (Map.Entry)var7.next();sb.append(" <tr><td>");url = (String)relyingPartyUrlToName.getKey();sb.append("<a href=\"").append(contextPath).append(url).append("\">");partyName = HtmlUtils.htmlEscape((String)relyingPartyUrlToName.getValue());sb.append(partyName);sb.append("</a>");sb.append("</td></tr>\n");}sb.append("</table>\n");}sb.append("</div>\n");sb.append("</body></html>");return sb.toString();}
这样,就完成了将默认的登录页面王客户端浏览器输出。这是硬生生的在Java中拼出了HTML和各种前端样式、JS功能,返回给浏览器,浏览器在进行解析,然后客户输入完毕之后,就可以进行验证了。