😉😉 欢迎加入我们的学习交流群呀!
✅✅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功能,返回给浏览器,浏览器在进行解析,然后客户输入完毕之后,就可以进行验证了。