Source tracking of default login page of spring security

Source tracking of default login page of spring security

In the last two months of 2021, set up a flag to   Spring Security   and   Spring Security OAuth2   The application and main process source code are thoroughly studied!

All the children's shoes who have used Spring Security in the project know that when we don't customize the login page separately, Spring Security will help us configure a default login page during initialization. I've been wondering how to configure the default login page before. I specially looked for the source code tonight.

springboot project dependencies

<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>

Write an interface freely in the project and then access it

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

On tomcat default port 8080,   localhost:8080   When accessing this interface, spring security will help us redirect the path to the default login page

So how does this default page come from?

It turns out that Spring Security has a default   WebSecurityConfigurerAdapter  , Found one of them   init   Method, so a breakpoint is made in this method to track when the application starts.

Follow   getHttp()   method,   this.disableDefaults   The variable defaults to false, which means that it will be executed   applyDefaultConfiguration(this.http);   method. see   applyDefaultConfiguration   method

 

public void init(WebSecurity web) throws Exception {
    // First, configure which http requests security will intercept
   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) {
            // The default configuration will take this branch
			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;
	}

see   applyDefaultConfiguration(this.http)   Method, discovery   http   Object new has a   DefaultLoginPageConfigurer   Object properties,

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();
}

  see   DefaultLoginPageConfigurer   Class definition. It is found that while initializing, it also initializes its own two private member variables, namely   DefaultLoginPageGeneratingFilter   The default login page generates a Filter,   DefaultLogoutPageGeneratingFilter   The default login page is Filter. The name is very good. See the meaning of the name. We Mashan know the meaning of these two classes.

Check   DefaultLoginPageGeneratingFilter   Found and defined a series of member variables related to login, including login, login and other paths. The default login page path is   "/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;
    .....

Considering the class name, it is found that it is a Filter class, so they should all Filter again   doFilter(ServletRequest request, ServletResponse response, FilterChain chain)   Method, let's take a look   DefaultLoginPageConfigurer   Class ` ` dofilter   Method, sure enough, in   The method of generating the default login page is found in the dofilter ` method.

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    // Judge whether the current request is authenticated
   boolean loginError = isErrorPage(request);
   boolean logoutSuccess = isLogoutSuccess(request);
   if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
       // If the current request for authentication fails, this branch will be executed
      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();
}

         We found   generateLoginPageHtml(HttpServletRequest request, boolean loginError, boolean logoutSuccess)   In this method, the most original method of writing html page by Servlet is used, and the html code of the login page is written into a string and written to the front-end display. At this point, we will generally know how the default login page and logout page are generated.

Keywords: Java Spring Back-end

Added by Vizionz on Thu, 11 Nov 2021 05:20:19 +0200