spring security中的默認(rèn)登錄頁(yè)源碼跟蹤
2021年的最后2個(gè)月,立個(gè)flag,要把Spring Security和Spring Security OAuth2的應(yīng)用及主流程源碼研究透徹!
項(xiàng)目中使用過(guò)Spring Security的童鞋都知道,當(dāng)我們沒(méi)有單獨(dú)自定義登錄頁(yè)時(shí),Spring Security自己在初始化的時(shí)候會(huì)幫我們配置一個(gè)默認(rèn)的登錄頁(yè),之前一直疑問(wèn)默認(rèn)登錄頁(yè)是怎么配置的,今晚特地找了源碼跟一下。
springboot項(xiàng)目依賴(lài)
<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>
在項(xiàng)目中隨意編寫(xiě)一個(gè)接口,然后進(jìn)行訪(fǎng)問(wèn)
@GetMapping("/")
public String hello() {
return "hello, spring security";
}
在tomcat默認(rèn)端口8080,localhost:8080 下訪(fǎng)問(wèn)該接口,spring security會(huì)幫我們將路徑重定向到默認(rèn)的登錄頁(yè)

那么這個(gè)默認(rèn)頁(yè)是怎么來(lái)的呢?
原來(lái)Spring Security有一個(gè)默認(rèn)的WebSecurityConfigurerAdapter,發(fā)現(xiàn)其中有一個(gè)init方法,于是在這個(gè)方法打了斷點(diǎn),在應(yīng)用啟動(dòng)的時(shí)候進(jìn)行跟蹤。
跟蹤getHttp()方法,this.disableDefaults變量默認(rèn)為false,意味著將會(huì)執(zhí)行applyDefaultConfiguration(this.http);方法。查看applyDefaultConfiguration方法
public void init(WebSecurity web) throws Exception {
// 首先配置security要攔截的哪些http請(qǐng)求
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) {
// 默認(rèn)的配置將會(huì)走這個(gè)分支
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)方法,發(fā)現(xiàn)http對(duì)象new了一個(gè)DefaultLoginPageConfigurer對(duì)象屬性,
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類(lèi)定義,發(fā)現(xiàn)它在初始化的同時(shí),它也初始化了自己的2個(gè)私有成員變量,分別是DefaultLoginPageGeneratingFilter默認(rèn)登錄頁(yè)面生成Filter,DefaultLogoutPageGeneratingFilter默認(rèn)登錄頁(yè)面Filter, 名字起得很好,見(jiàn)名知意,我們馬山知道這2個(gè)類(lèi)的含義。
查看DefaultLoginPageGeneratingFilter的類(lèi)成員變量,發(fā)現(xiàn)定義了一系列跟登錄有關(guān)的成員變量,包括登錄、登錄等路徑,默認(rèn)的登錄頁(yè)面路徑是"/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;
.....
再結(jié)合類(lèi)名思考,發(fā)現(xiàn)是個(gè)Filter類(lèi),那么它們應(yīng)該都會(huì)重新Filter的doFilter(ServletRequest request, ServletResponse response, FilterChain chain)方法,我們查看一下DefaultLoginPageConfigurer類(lèi)的``doFilter方法,果然,在doFilter`方法中發(fā)現(xiàn)了生成默認(rèn)登錄頁(yè)面的方法。
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 判斷當(dāng)前的請(qǐng)求是否被認(rèn)證通過(guò)
boolean loginError = isErrorPage(request);
boolean logoutSuccess = isLogoutSuccess(request);
if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
// 當(dāng)前請(qǐng)求認(rèn)證失敗的話(huà),將會(huì)執(zhí)行這個(gè)分支
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();
}
我們發(fā)現(xiàn)generateLoginPageHtml(HttpServletRequest request, boolean loginError, boolean logoutSuccess)這個(gè)方法中使用了最原始的Servlet寫(xiě)html頁(yè)面的方法,將登錄頁(yè)的html代碼寫(xiě)到字符串中寫(xiě)出到前端展示。到這里,我們就大體知道默認(rèn)登錄頁(yè)面及登出頁(yè)面是怎么生成的了。
默認(rèn)登錄頁(yè)到這里就結(jié)束了,有興趣的可以關(guān)注下,接下來(lái)會(huì)繼續(xù)寫(xiě)springsecurity的自定義表單認(rèn)證、授權(quán)、會(huì)話(huà)等內(nèi)容剖析。距離2022年只剩54天!
到此這篇關(guān)于spring security之 默認(rèn)登錄頁(yè)源碼跟蹤的文章就介紹到這了,更多相關(guān)spring security登錄頁(yè)源碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java模擬ATM功能(控制臺(tái)連接Mysql數(shù)據(jù)庫(kù))
這篇文章主要介紹了java模擬ATM功能,控制臺(tái)連接Mysql數(shù)據(jù)庫(kù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05
Java方法參數(shù)傳遞如何實(shí)現(xiàn)
這篇文章主要介紹了Java方法參數(shù)傳遞如何實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05
Mybatis自動(dòng)創(chuàng)建表和更新表結(jié)構(gòu)
這篇文章主要介紹了Mybatis自動(dòng)創(chuàng)建表和更新表結(jié)構(gòu)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06
Java BigDecimal解決double精度丟失的問(wèn)題
我們?cè)谌粘i_(kāi)發(fā)中, 有很多時(shí)候會(huì)遇到小數(shù)(double類(lèi)型)精確計(jì)算,本文主要介紹了Java BigDecimal解決double精度丟失的問(wèn)題,具有一定的參考價(jià)值,感興趣的可以了解一下2023-11-11
AgileBoot?項(xiàng)目?jī)?nèi)統(tǒng)一的錯(cuò)誤碼設(shè)計(jì)分析
這篇文章主要為大家介紹了AgileBoot?項(xiàng)目?jī)?nèi)統(tǒng)一的錯(cuò)誤碼設(shè)計(jì)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
java基本教程之線(xiàn)程休眠 java多線(xiàn)程教程
本文對(duì)javaThread中sleep()方法進(jìn)行介紹,sleep() 的作用是讓當(dāng)前線(xiàn)程休眠,即當(dāng)前線(xiàn)程會(huì)從“運(yùn)行狀態(tài)”進(jìn)入到“休眠(阻塞)狀態(tài)”,大家參考使用吧2014-01-01

