springsecurity的登录过程

文章详细探讨了在使用若依框架的前后端分离项目中,如何追踪登录流程中DaoAuthenticationProvider的注册时机,重点介绍了UserDetailsService、PasswordEncoder的角色以及它们在AuthenticationManager中的配置过程。
摘要由CSDN通过智能技术生成
使用若依的前后端分离框架,在梳理登录流程时发现了一部分springsecurity的部分组件没有注解,debug过程中却发现组件已经注册到容器中了,由此引发了想知道该类是在什么时候注册到容器的。

 登录主方法为

SysLoginService.login() 方法,该方法中如下代码注释提示会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager.authenticate(authenticationToken);
public String login(String username, String password, String code, String uuid,boolean scanFlag)
    {
        if(scanFlag){
            // 验证码校验
            validateCaptcha(username, code, uuid);
        }
        // 登录前置校验
        loginPreCheck(username, password);
        // 用户验证
        Authentication authentication = null;
        try
        {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
            AuthenticationContextHolder.setContext(authenticationToken);
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager.authenticate(authenticationToken);
        }
        catch (Exception e)
        {
            if (e instanceof BadCredentialsException)
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            }
            else
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                throw new ServiceException(e.getMessage());
            }
        }
        finally
        {
            AuthenticationContextHolder.clearContext();
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        return tokenService.createToken(loginUser);
    }

认证的核心是各种AuthenticationProvider,这篇博客就来了解一下其中使用的最多的一个DaoAuthenticationProvider。

在此之前,先来了解一下三个类。UserDetails、UserDetailsService以及PasswordEncoder。

通俗的说,UserDetails是用户信息的实体类,UserDetailsService自定义实现,根据用户名密码加载UserDetails,PasswordEncoder就是密码的加密类。

看图,DaoAuthenticationProvider通过UserDetailsService以及PasswordEncoder,将用户名密码替换成UserDetails和Authorities。

接着看一看这个DaoAuthenticationProvider是从哪里配置来的,这个得从WebSecurityConfigurerAdapter说起。

在WebSecurityConfigurerAdapter中,以下代码之前都看过,AuthenticationManager 的由来也都说明过,其中有一个parent被所有的子AuthenticationManager共享,而这个DaoAuthenticationProvider就是包括在parent中。

protected final HttpSecurity getHttp() throws Exception {
        if (http != null) {
            return http;
        }
 
         //获取AuthenticationEventPublisher
        AuthenticationEventPublisher eventPublisher = getAuthenticationEventPublisher();
        localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
 
        //配置parent的AuthenticationManager,可覆盖实现自定义方法
        AuthenticationManager authenticationManager = authenticationManager();
        authenticationBuilder.parentAuthenticationManager(authenticationManager);
 
        Map<Class<?>, Object> sharedObjects = createSharedObjects();
 
        //新建HttpSecurity,并构建一个默认的HttpSecurity
        http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
                sharedObjects);
        if (!disableDefaults) {
            // @formatter:off
            http
                .csrf().and()
                .addFilter(new WebAsyncManagerIntegrationFilter())
                .exceptionHandling().and()
                .headers().and()
                .sessionManagement().and()
                .securityContext().and()
                .requestCache().and()
                .anonymous().and()
                .servletApi().and()
                .apply(new DefaultLoginPageConfigurer<>()).and()
                .logout();
            // @formatter:on
            ClassLoader classLoader = this.context.getClassLoader();
            //spi 加载 AbstractHttpConfigurer的实现类,加入HttpSecurity中
            List<AbstractHttpConfigurer> defaultHttpConfigurers =
                    SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
            for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
                http.apply(configurer);
            }
        }
        //配置HttpSecurity,可自定义实现
        configure(http);
        return http;
    }

    protected AuthenticationManager authenticationManager() throws Exception {
        //如果没有初始化过,执行方法
        if (!authenticationManagerInitialized) {
 
            //configure(AuthenticationManagerBuilder auth)
            //交给子类自定义
            configure(localConfigureAuthenticationBldr);
 
            //如果没有自定义过
            if (disableLocalConfigureAuthenticationBldr) {
                //使用默认的
                authenticationManager = authenticationConfiguration
                        .getAuthenticationManager();
            }
            else {
                //否则使用自定义过的进行构建
                authenticationManager = localConfigureAuthenticationBldr.build();
            }
            authenticationManagerInitialized = true;
        }
        return authenticationManager;
    }

以上代码可以看出,这个parent如果不自定义的话,会使用默认的authenticationConfiguration,而这个如下是spring注入进来的

    @Autowired
    public void setAuthenticationConfiguration(
            AuthenticationConfiguration authenticationConfiguration) {
        this.authenticationConfiguration = authenticationConfiguration;
    }

接下来再看AuthenticationConfiguration 是怎么加到spring容器中的。 

还是@EnableWebSecurity注解,注解了一个@EnableGlobalAuthentication,这个注解import了一个AuthenticationConfiguration。

//默认的情况获取AuthenticationManager
    public AuthenticationManager getAuthenticationManager() throws Exception {
        if (this.authenticationManagerInitialized) {
            return this.authenticationManager;
        }
        AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
        if (this.buildingAuthenticationManager.getAndSet(true)) {
            return new AuthenticationManagerDelegator(authBuilder);
        }
         //放入三个configure,都在当前类中生成,GlobalAuthenticationConfigurerAdapter
        // InitializeUserDetailsBeanManagerConfigurer、InitializeAuthenticationProviderBeanManagerConfigurer
        for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
            authBuilder.apply(config);
        }
 
        authenticationManager = authBuilder.build();
 
        if (authenticationManager == null) {
            authenticationManager = getAuthenticationManagerBean();
        }
 
        this.authenticationManagerInitialized = true;
        return authenticationManager;
    }

 而在这个AuthenticationConfiguration中,会生成如上的AuthenticationManager,这个就是parent。

而在这个AuthenticationManager种,设置了多个globalAuthConfigurers

    @Bean
    public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer(
            ApplicationContext context) {
        return new EnableGlobalAuthenticationAutowiredConfigurer(context);
    }
 
    @Bean
    public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {
        return new InitializeUserDetailsBeanManagerConfigurer(context);
    }
 
    @Bean
    public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(ApplicationContext context) {
        return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
    }

可以看到,当前类中有这么三个Configurer,其中InitializeUserDetailsBeanManagerConfigurer中会生成DaoAuthenticationProvider,要注意的是,这里并不是在这个类中直接生成的,而是如下,先注入了一个InitializeUserDetailsManagerConfigurer,在InitializeUserDetailsManagerConfigurer中创建的。

@Override
    public void init(AuthenticationManagerBuilder auth) throws Exception {
        auth.apply(new InitializeUserDetailsManagerConfigurer());
    }
public void configure(AuthenticationManagerBuilder auth) throws Exception {
            if (auth.isConfigured()) {
                return;
            }
            //从spring中获取UserDetailsService,如果没有,直接返回
            //所以要执行下面的代码,必须要有UserDetailsService
            UserDetailsService userDetailsService = getBeanOrNull(
                    UserDetailsService.class);
            if (userDetailsService == null) {
                return;
            }
           //从spring中获取PasswordEncoder
            PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
            
            UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
 
            //构建DaoAuthenticationProvider
            DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
            provider.setUserDetailsService(userDetailsService);
            if (passwordEncoder != null) {
                provider.setPasswordEncoder(passwordEncoder);
            }
            if (passwordManager != null) {
                provider.setUserDetailsPasswordService(passwordManager);
            }
            provider.afterPropertiesSet();
 
           //将provider放入AuthenticationManagerBuilder中
            auth.authenticationProvider(provider);
        }

到此,DaoAuthenticationProvider的由来就已经清晰了。

再看看逻辑,如下代码,逻辑和之前描述一致。

    protected final UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        prepareTimingAttackProtection();
        try {
            
            //调用UserDetailsService的loadUserByUsername方法,方法需要自定义
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        }
        catch (UsernameNotFoundException ex) {
            mitigateAgainstTimingAttack(authentication);
            throw ex;
        }
        catch (InternalAuthenticationServiceException ex) {
            throw ex;
        }
        catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }
 

 结束!

  • 21
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值