spring|spring security之 默认登录页源码跟踪
spring security之 默认登录页源码跟踪
? 2021年的最后2个月,立个flag,要把Spring Security和Spring Security OAuth2的应用及主流程源码研究透彻!
? 项目中使用过Spring Security的童鞋都知道,当我们没有单独自定义登录页时,Spring Security自己在初始化的时候会帮我们配置一个默认的登录页,之前一直疑问默认登录页是怎么配置的,今晚特地找了源码跟一下。
springboot项目依赖
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-security
在项目中随意编写一个接口,然后进行访问
@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, Object> sharedObjects = createSharedObjects();
this.http = new HttpSecurity(this.objectPostProcessor, this.authenticationBuilder, sharedObjects);
if (!this.disableDefaults) {
// 默认的配置将会走这个分支
applyDefaultConfiguration(this.http);
ClassLoader classLoader = this.context.getClassLoader();
List 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("\n");
sb.append("\n");
sb.append("\n");
sb.append("\n");
sb.append("\n");
sb.append("\n");
sb.append("\n");
sb.append("Please sign in - 锐客网 \n");
sb.append("\n");
sb.append("\n");
sb.append("\n");
sb.append("\n");
sb.append("\n");
if (this.formLoginEnabled) {
sb.append("\n");
}
if (this.openIdEnabled) {
sb.append("
? 我们发现
generateLoginPageHtml(HttpServletRequest request, boolean loginError, boolean logoutSuccess)
这个方法中使用了最原始的Servlet写html页面的方法,将登录页的html代码写到字符串中写出到前端展示。到这里,我们就大体知道默认登录页面及登出页面是怎么生成的了。【spring|spring security之 默认登录页源码跟踪】? 默认登录页到这里就结束了,有兴趣的可以关注下,接下来会继续写springsecurity的自定义表单认证、授权、会话等内容剖析。距离2022年只剩54天!
推荐阅读
- PMSJ寻平面设计师之现代(Hyundai)
- 太平之莲
- 闲杂“细雨”
- 七年之痒之后
- 深入理解Go之generate
- 由浅入深理解AOP
- Activiti(一)SpringBoot2集成Activiti6
- 期刊|期刊 | 国内核心期刊之(北大核心)
- 生活随笔|好天气下的意外之喜
- 感恩之旅第75天