spring security中的默认登录页源码跟踪

​2021年的最后2个月,立个flag,要把Spring Security和Spring Security OAuth2的应用及主流程源码研究透彻!

​项目中使用过Spring Security的童鞋都知道,当我们没有单独自定义登录页时,Spring Security自己在初始化的时候会帮我们配置一个默认的登录页,之前一直疑问默认登录页是怎么配置的,今晚特地找了源码跟一下。

springboot项目依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>

在项目中随意编写一个接口,然后进行访问

@GetMapping("/")
public String hello() {
   return "hello, spring security";
}

在tomcat默认端口8080,localhost:8080 下访问该接口,spring security会帮我们将路径重定向到默认的登录页

​那么这个默认页是怎么来的呢?
原来Spring Security有一个默认的WebSecurityConfigurerAdapter,发现其中有一个init方法,于是在这个方法打了断点,在应用启动的时候进行跟踪。

​跟踪getHttp()方法,this.disableDefaults变量默认为false,意味着将会执行applyDefaultConfiguration(this.http);方法。查看applyDefaultConfiguration方法

public void init(WebSecurity web) throws Exception {
    // 首先配置security要拦截的哪些http请求
   HttpSecurity http = getHttp();
   web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
      FilterSecurityInterceptor securityInterceptor = http.getSharedObject(FilterSecurityInterceptor.class);
      web.securityInterceptor(securityInterceptor);
   });
}

protected final HttpSecurity getHttp() throws Exception {
		if (this.http != null) {
			return this.http;
		}
		AuthenticationEventPublisher eventPublisher = getAuthenticationEventPublisher();
		this.localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
		AuthenticationManager authenticationManager = authenticationManager();
		this.authenticationBuilder.parentAuthenticationManager(authenticationManager);
		Map<Class<?>, Object> sharedObjects = createSharedObjects();
		this.http = new HttpSecurity(this.objectPostProcessor, this.authenticationBuilder, sharedObjects);
		if (!this.disableDefaults) {
            // 默认的配置将会走这个分支
			applyDefaultConfiguration(this.http);
			ClassLoader classLoader = this.context.getClassLoader();
			List<AbstractHttpConfigurer> defaultHttpConfigurers = SpringFactoriesLoader
					.loadFactories(AbstractHttpConfigurer.class, classLoader);
			for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
				this.http.apply(configurer);
			}
		}
		configure(this.http);
		return this.http;
	}

查看applyDefaultConfiguration(this.http)方法,发现http对象new了一个DefaultLoginPageConfigurer对象属性,

private void applyDefaultConfiguration(HttpSecurity http) throws Exception {
   http.csrf();
   http.addFilter(new WebAsyncManagerIntegrationFilter());
   http.exceptionHandling();
   http.headers();
   http.sessionManagement();
   http.securityContext();
   http.requestCache();
   http.anonymous();
   http.servletApi();
   http.apply(new DefaultLoginPageConfigurer<>());
   http.logout();
}

​查看DefaultLoginPageConfigurer类定义,发现它在初始化的同时,它也初始化了自己的2个私有成员变量,分别是DefaultLoginPageGeneratingFilter默认登录页面生成Filter,DefaultLogoutPageGeneratingFilter默认登录页面Filter, 名字起得很好,见名知意,我们马山知道这2个类的含义。

​查看DefaultLoginPageGeneratingFilter的类成员变量,发现定义了一系列跟登录有关的成员变量,包括登录、登录等路径,默认的登录页面路径是"/login"

public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {

   public static final String DEFAULT_LOGIN_PAGE_URL = "/login";

   public static final String ERROR_PARAMETER_NAME = "error";

   private String loginPageUrl;

   private String logoutSuccessUrl;

   private String failureUrl;

   private boolean formLoginEnabled;
    .....

​再结合类名思考,发现是个Filter类,那么它们应该都会重新Filter的doFilter(ServletRequest request, ServletResponse response, FilterChain chain)方法,我们查看一下DefaultLoginPageConfigurer类的``doFilter方法,果然,在doFilter`方法中发现了生成默认登录页面的方法。

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    // 判断当前的请求是否被认证通过
   boolean loginError = isErrorPage(request);
   boolean logoutSuccess = isLogoutSuccess(request);
   if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
       // 当前请求认证失败的话,将会执行这个分支
      String loginPageHtml = generateLoginPageHtml(request, loginError, logoutSuccess);
      response.setContentType("text/html;charset=UTF-8");
      response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
      response.getWriter().write(loginPageHtml);
      return;
   }
   chain.doFilter(request, response);
}

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(WebAttributes.AUTHENTICATION_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");
      sb.append(createError(loginError, errorMsg) + 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");
      sb.append(createRememberMe(this.rememberMeParameter) + renderHiddenInputs(request));
      sb.append("        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n");
      sb.append("      </form>\n");
   }
   if (this.openIdEnabled) {
      sb.append("      <form name=\"oidf\" class=\"form-signin\" method=\"post\" action=\"" + contextPath
            + this.openIDauthenticationUrl + "\">\n");
      sb.append("        <h2 class=\"form-signin-heading\">Login with OpenID Identity</h2>\n");
  ......
   return sb.toString();
}

​我们发现generateLoginPageHtml(HttpServletRequest request, boolean loginError, boolean logoutSuccess)这个方法中使用了最原始的Servlet写html页面的方法,将登录页的html代码写到字符串中写出到前端展示。到这里,我们就大体知道默认登录页面及登出页面是怎么生成的了。

​默认登录页到这里就结束了,有兴趣的可以关注下,接下来会继续写springsecurity的自定义表单认证、授权、会话等内容剖析。距离2022年只剩54天!

到此这篇关于spring security之 默认登录页源码跟踪的文章就介绍到这了,更多相关spring security登录页源码内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • spring security自定义登录页面

    在项目中我们肯定不能使用Spring自己生成的登录页面,而要用我们自己的登录页面,下面讲一下如何自定义登录页面,先看下配置 <sec:http auto-config="true"> <sec:intercept-url pattern="/app.jsp" access="ROLE_SERVICE"/> <sec:intercept-url pattern="/**" access="

  • SpringSecurity 默认表单登录页展示流程源码

    SpringSecurity 默认表单登录页展示流程源码 本篇主要讲解 SpringSecurity提供的默认表单登录页 它是如何展示的的流程, 涉及 1.FilterSecurityInterceptor, 2.ExceptionTranslationFilc,xmccmc,ter , 3.DefaultLoginPageGeneratingFilter 过滤器, 并且简单介绍了 AccessDecisionManager 投票机制  1.准备工作(体验SpringSecurity默认表单认证

  • spring security中的默认登录页源码跟踪

    ​2021年的最后2个月,立个flag,要把Spring Security和Spring Security OAuth2的应用及主流程源码研究透彻! ​项目中使用过Spring Security的童鞋都知道,当我们没有单独自定义登录页时,Spring Security自己在初始化的时候会帮我们配置一个默认的登录页,之前一直疑问默认登录页是怎么配置的,今晚特地找了源码跟一下. springboot项目依赖 <dependency> <groupId>org.springframewo

  • 详解Spring Security中的HttpBasic登录验证模式

    一.HttpBasic模式的应用场景 HttpBasic登录验证模式是Spring Security实现登录验证最简单的一种方式,也可以说是最简陋的一种方式.它的目的并不是保障登录验证的绝对安全,而是提供一种"防君子不防小人"的登录验证. 就好像是我小时候写日记,都买一个带小锁头的日记本,实际上这个小锁头有什么用呢?如果真正想看的人用一根钉子都能撬开.它的作用就是:某天你的父母想偷看你的日记,拿出来一看还带把锁,那就算了吧,怪麻烦的. 举一个我使用HttpBasic模式的进行登录验证的

  • 详解Spring Security中获取当前登录用户的详细信息的几种方法

    目录 在Bean中获取用户信息 在Controller中获取用户信息 通过 Interface 获取用户信息 在JSP页面中获取用户信息 在Bean中获取用户信息 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (!(authentication instanceof AnonymousAuthenticationToken)) { String currentU

  • Spring Security 实现用户名密码登录流程源码详解

    目录 引言 探究 登录流程 校验 用户信息保存 引言 你在服务端的安全管理使用了 Spring Security,用户登录成功之后,Spring Security 帮你把用户信息保存在 Session 里,但是具体保存在哪里,要是不深究你可能就不知道, 这带来了一个问题,如果用户在前端操作修改了当前用户信息,在不重新登录的情况下,如何获取到最新的用户信息? 探究 无处不在的 Authentication 玩过 Spring Security 的小伙伴都知道,在 Spring Security 中

  • Spring Security 基于URL的权限判断源码解析

    目录 1. FilterSecurityInterceptor 源码阅读 2. 自定义基于url的授权 1. FilterSecurityInterceptor 源码阅读 org.springframework.security.web.access.intercept.FilterSecurityInterceptor 通过过滤器实现对HTTP资源进行安全处理. 该安全拦截器所需的 SecurityMetadataSource 类型为 FilterInvocationSecurityMetad

  • 详解Spring Security如何配置JSON登录

    spring security用了也有一段时间了,弄过异步和多数据源登录,也看过一点源码,最近弄rest,然后顺便搭oauth2,前端用json来登录,没想到spring security默认居然不能获取request中的json数据,谷歌一波后只在stackoverflow找到一个回答比较靠谱,还是得要重写filter,于是在这里填一波坑. 准备工作 基本的spring security配置就不说了,网上一堆例子,只要弄到普通的表单登录和自定义UserDetailsService就可以.因为需

  • 图解Spring Security 中用户是如何实现登录的

    1. 前言 欢迎阅读Spring Security 实战干货系列文章,在集成Spring Security安全框架的时候我们最先处理的可能就是根据我们项目的实际需要来定制注册登录了,尤其是Http登录认证.根据以前的相关文章介绍,Http登录认证由过滤器UsernamePasswordAuthenticationFilter 进行处理.我们只有把这个过滤器搞清楚才能做一些定制化.今天我们就简单分析它的源码和工作流程. 2. UsernamePasswordAuthenticationFilter

  • Spring Security 自定义短信登录认证的实现

    自定义登录filter 上篇文章我们说到,对于用户的登录,security通过定义一个filter拦截login路径来实现的,所以我们要实现自定义登录,需要自己定义一个filter,继承AbstractAuthenticationProcessingFilter,从request中提取到手机号和验证码,然后提交给AuthenticationManager: public class SmsAuthenticationFilter extends AbstractAuthenticationPro

  • Spring Security中用JWT退出登录时遇到的坑

    最近有个粉丝提了个问题,说他在Spring Security中用JWT做退出登录的时无法获取当前用户,导致无法证明"我就是要退出的那个我",业务失败!经过我一番排查找到了原因,而且这个错误包括我自己的大部分人都犯过. Session会话 之所以要说Session会话,是因为Spring Security默认配置就是有会话的,所以当你登录以后Session就会由服务端保持直到你退出登录.只要Session保持住,你的请求只要进入服务器就可以从 ServletRequest 中获取到当前的

  • Spring Security实现用户名密码登录详解

    目录 环境 用户名密码登录 E-R图 POM依赖 配置文件 Mapper Service设计 HTML Controller 启动 完整代码 环境 JDK 1.8 Spring Boot 2.3.0.RELEASE Maven 3.6.1 H2 数据库 用户名密码登录 首先,我们用 Spring Security 实现用户输入用户名密码登录验证并获取相应权限. E-R图 完整建表语句 因为是测试程序,所以用H2数据库来测试.SQL脚本在resouces/db目录下,项目启动后会自动初始化脚本,无

随机推荐