解决Spring AOP 在RememberMeServices 导致的NullPointerException

廖雪峰: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();

    }
复制代码

猜你喜欢

转载自juejin.im/post/7016678541722910734