廖雪峰:AOP避坑指南
AbstractRemembermeService logger为null导致登录失败
透过现象看原理:详解Spring中Bean的this调用导致AOP失效的原因
揭秘 Spring AOP 失效的罪因,看了都说好!
Spring AOP出现NullPointerException
原因是 AbstractRememberMeServices
有一个被外部调用的final方法,cglib无法代理final方法导致的,而这个方法又使用了this+类变量,而生成cglib代理类不会初始化类变量,所以直接报 NullPointerException
@Override
public final void loginSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication successfulAuthentication) {
if (!rememberMeRequested(request, this.parameter)) {
this.logger.debug("Remember-me login not requested.");
return;
}
onLoginSuccess(request, response, successfulAuthentication);
}
复制代码
解决办法是添加一个init()方法,通过反射获取类变量并赋值
public class LogRememberMeServices extends PersistentTokenBasedRememberMeServices {
@Autowired
private LogRememberMeServices mySelf;
public LogRememberMeServices(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) {
super(key, userDetailsService, tokenRepository);
}
/**
* 填充cglib代理的一些字段,弥补cglib代理类不能代理{@link AbstractRememberMeServices#loginSuccess}方法导致的缺陷
*/
public final void init() throws NoSuchFieldException, IllegalAccessException {
Field parameter = AbstractRememberMeServices.class.getDeclaredField("parameter");
parameter.setAccessible(true);
if(parameter.get(this) ==null){
parameter.set(this,getParameter());
}
Field logger = AbstractRememberMeServices.class.getDeclaredField("logger");
logger.setAccessible(true);
if(logger.get(this) ==null){
logger.set(this, LogFactory.getLog(AbstractRememberMeServices.class));
}
}
@Override
protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) {
return mySelf.AutoLoginCookie(cookieTokens, request, response);
}
/**
* 自定义AOP注解实现登录日志
*/
@Logging(value = LoginLog.class)
public UserDetails AutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
return super.processAutoLoginCookie(cookieTokens, request, response);
}
}
复制代码
然后在手动执行一次init()方法即可
@Bean
public LogRememberMeServices rememberMeServices(UserDetailsService userDetailsService, PersistentTokenRepository persistentTokenRepository) {
LogRememberMeServices rememberMeServices = new LogRememberMeServices(
REMEMBER_ME_KEY, userDetailsService, persistentTokenRepository);
rememberMeServices.setTokenValiditySeconds(60*60*24*30);
return rememberMeServices;
}
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, LoginLimitService loginLimitService
, CaptchaService captchaService, RememberMeServices rememberMeServices
, SocialService socialService, AuthenticationFailureHandler authenticationFailureHandler) throws Exception {
//填充cglib代理的一些字段,弥补cglib代理类不能代理{@link AbstractRememberMeServices#loginSuccess}方法导致的缺陷
if(rememberMeServices instanceof LogRememberMeServices){
((LogRememberMeServices)rememberMeServices).init();
}
return http
.authorizeRequests(authorizeRequests ->
authorizeRequests
.antMatchers("/login/**").permitAll()
.anyRequest().authenticated()
)
.rememberMe()
.key(REMEMBER_ME_KEY)
.rememberMeServices(rememberMeServices).and()
.cors().and()
.csrf(t -> t.ignoringAntMatchers("/captcha/**"))
.formLogin(Customizer.withDefaults())
.logout(Customizer.withDefaults())
.headers().frameOptions().disable().and()
.build();
}
复制代码