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.