spring|spring security 之自定义表单登录源码跟踪
? 上一节我们跟踪了security的默认登录页的源码,可以参考这里:https://www.cnblogs.com/process-h/p/15522267.html 这节我们来看看如何自定义单表认证页及源码跟踪。
【spring|spring security 之自定义表单登录源码跟踪】? 为了实现自定义表单及登录页,我们需要编写自己的WebSecurityConfig
类,继承了WebSecurityConfigurerAdapter
对象,通过重写configure
方法,定义自己的登录页路径及失败跳转的路径。
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests
.antMatchers("/css/**", "/index").permitAll()
.antMatchers("/user/**").hasRole("USER")
)
.formLogin(formLogin ->
formLogin
.loginPage("/login")
.failureUrl("/login-error")
);
}
// @formatter:on@Bean
public UserDetailsService userDetailsService() {
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(userDetails);
}
}
我们通过引入
Thymeleaf
模板来实现跳转@Controller
public class MainController {@RequestMapping("/")
public String root() {
return "redirect:/index";
}@RequestMapping("/index")
public String index() {
return "index";
}@RequestMapping("/user/index")
public String userIndex() {
return "user/index";
}@RequestMapping("/login")
public String login() {
return "login";
}@RequestMapping("/login-error")
public String loginError(Model model) {
model.addAttribute("loginError", true);
return "login";
}}
上一节我们提到了
WebSecurityConfig
类,它会有一个init
方法@Override
public void init(WebSecurity web) throws Exception {
HttpSecurity http = getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
FilterSecurityInterceptor securityInterceptor = http.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
});
}
这里提到了
HttpSecurity
对象,顾名思义,它的作用就是保证Http请求的安全,那么它是如何保证http请求的安全的呢?我们来看看getHttp()方法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;
}// 默认认证事件发布者
public DefaultAuthenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
addMapping(BadCredentialsException.class.getName(), AuthenticationFailureBadCredentialsEvent.class);
addMapping(UsernameNotFoundException.class.getName(), AuthenticationFailureBadCredentialsEvent.class);
addMapping(AccountExpiredException.class.getName(), AuthenticationFailureExpiredEvent.class);
addMapping(ProviderNotFoundException.class.getName(), AuthenticationFailureProviderNotFoundEvent.class);
addMapping(DisabledException.class.getName(), AuthenticationFailureDisabledEvent.class);
addMapping(LockedException.class.getName(), AuthenticationFailureLockedEvent.class);
addMapping(AuthenticationServiceException.class.getName(), AuthenticationFailureServiceExceptionEvent.class);
addMapping(CredentialsExpiredException.class.getName(), AuthenticationFailureCredentialsExpiredEvent.class);
addMapping("org.springframework.security.authentication.cas.ProxyUntrustedException",
AuthenticationFailureProxyUntrustedEvent.class);
addMapping("org.springframework.security.oauth2.server.resource.InvalidBearerTokenException",
AuthenticationFailureBadCredentialsEvent.class);
}
我们来看看
applyDefaultConfiguration
这个方法,在上一节有讲到,这里是给httpSecurity对象配置一些默认的配置,比如默认会开启csrf跨站请求伪造防护,添加WebAsyncManagerIntegrationFilter
过滤器,添加默认的登录页配置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();
}
回到调用
applyDefaultConfiguration()
的主方法这里,执行完if (!this.disableDefaults) {}
分支之后,会调用自身的configure(this.http);
方法,也就是我们自定义的WebSecurityConfig
类中重写的方法,会去执行我们的表单登录配置策略。.formLogin(formLogin ->
formLogin
.loginPage("/login")
.failureUrl("/login-error")
);
@Override
public FormLoginConfigurer loginPage(String loginPage) {
return super.loginPage(loginPage);
}
protected T loginPage(String loginPage) {
setLoginPage(loginPage);
updateAuthenticationDefaults();
this.customLoginPage = true;
return getSelf();
}
点击
.loginPage("/login")
方法,再点击super.loginPage(loginPage);
可以看到登录页已经被重写了,自定义登录页标志也被写成了true。? 自定义表单登录页及源码跟踪就到这里,过程中还发现了跟security最为密切的filter顺序定义,在该
FilterOrderRegistration
类的构造方法中,定义了security中可能会用到的所有filter的顺序,有兴趣的读者自行阅读下。登录相关的源码跟的线条比较粗,接下来该看看认证跟授权的部分了。推荐阅读
- PMSJ寻平面设计师之现代(Hyundai)
- 太平之莲
- 闲杂“细雨”
- 七年之痒之后
- 深入理解Go之generate
- 由浅入深理解AOP
- Activiti(一)SpringBoot2集成Activiti6
- 期刊|期刊 | 国内核心期刊之(北大核心)
- 生活随笔|好天气下的意外之喜
- 感恩之旅第75天