Spring Security(学习笔记) --登录认证流程分析源码!

重点标识

认证流程,AuthenticationManager 统筹全局

认证逻辑就是,AuthenticationManager 管理多个AuthenticationProvider,不同的认证方式,采用不同的AuthenticationProvider去进行认证,认证通过则直接结束,如果当前AuthenticationManager 下所有的AuthenticatioProvider都认证不了,则看看AuthenticationManager有没有parent,存在parent,则调用parent ,AuthenticationManager 接着认证,不存在,则直接返回失败。

账号密码登录认证逻辑

在这里插入图片描述

我们知道,在加入Security后,项目启动的时候有一个过滤器叫UserNamePasswordAuthenticationFilter,这个就是账户密码登录的过滤器,我们进入看一下:
大概解释一下这部分源码,首先,看他的登录请求是不是一个POST请求,如果不是,直接返回。

接着,就是获取它的用户名,密码,是不是空的,这部分的获取方式,就是我上一篇写过的,HttpServletRequest获取,有兴趣的同学可以翻一下,这里我随便点进去一个。
request.getParameter(this.usernameParameter);

请求是POST并且账户,密码都不为空的情况下,它会再调用UsernamePasswordAuthenticationToken.unauthenticated(username, password);
顾名思义,就是未认证,这里可以理解为,标识一下,这个请求还没有进行认证。

完了。就通过this.getAuthenticationManager().authenticate(authRequest);开始认证了。

 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String username = this.obtainUsername(request);
            username = username != null ? username.trim() : "";
            String password = this.obtainPassword(request);
            password = password != null ? password : "";
            UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }


好了,我们接着往下看,进入到authenticate这个方法,这个方法就是AuthenticationManager提供的,我们看看它的实现类。

在这里插入图片描述
很明显,他就是ProviderManager了,进去看看:

这个认证方法也大概解释一下,首先,获取到provider进行迭代遍历。
Iterator var9 = this.getProviders().iterator();
紧接着,就是通过 result = provider.authenticate(authentication);来判断,认证能不能通过。
如果通过了,则直接break,跳出去,结束,通不过,再看看下面这个方法

parentResult = this.parent.authenticate(authentication);

这里的重点是parent,就是去他的父类找下一个provider,然后递归调用。
点到parent里面看一下,我们可以看到,它还是AuthenticationManager,那由此我们就可以得到一个结论,一个AuthenticationManager是管理多个provider的,也就是说,认证的方式有多种,只要找到一种适合的,就可以通过了,这也是Spring Security所提供的好处之一,多种认证方式,如常用的用户名,密码登录,也有令牌登录之类的。

    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        AuthenticationException parentException = null;
        Authentication result = null;
        Authentication parentResult = null;
        int currentPosition = 0;
        int size = this.providers.size();
        Iterator var9 = this.getProviders().iterator();

        while(var9.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var9.next();
            if (provider.supports(toTest)) {
                if (logger.isTraceEnabled()) {
                    Log var10000 = logger;
                    String var10002 = provider.getClass().getSimpleName();
                    ++currentPosition;
                    var10000.trace(LogMessage.format("Authenticating request with %s (%d/%d)", var10002, currentPosition, size));
                }

                try {
                    result = provider.authenticate(authentication);
                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (InternalAuthenticationServiceException | AccountStatusException var14) {
                    this.prepareException(var14, authentication);
                    throw var14;
                } catch (AuthenticationException var15) {
                    lastException = var15;
                }
            }
        }

        if (result == null && this.parent != null) {
            try {
                parentResult = this.parent.authenticate(authentication);
                result = parentResult;
            } catch (ProviderNotFoundException var12) {
            } catch (AuthenticationException var13) {
                parentException = var13;
                lastException = var13;
            }
        }

        if (result != null) {
            if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
                ((CredentialsContainer)result).eraseCredentials();
            }

            if (parentResult == null) {
                this.eventPublisher.publishAuthenticationSuccess(result);
            }

            return result;
        } else {
            if (lastException == null) {
                lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
            }

            if (parentException == null) {
                this.prepareException((AuthenticationException)lastException, authentication);
            }

            throw lastException;
        }
    }

验证一下,上面所说的,我们新建一个项目,引入Web依赖和Security依赖即可。

简单配置一下:只需要注入一个SecurityFilterChain,就是Security的过滤器链,然后将AuthenticationManager认证,以及需要认证的用户UserDetailsService 放进去,就可以了。


@Configuration
public class SecurityConfig {


    UserDetailsService userDetailsService(){

        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
        inMemoryUserDetailsManager.createUser(User.withUsername("admin").password("{noop}123").build());
        return inMemoryUserDetailsManager;
    }


    AuthenticationManager authenticationManager(){
        //做数据校验
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
         daoAuthenticationProvider.setUserDetailsService(userDetailsService());
        ProviderManager providerManager = new ProviderManager(daoAuthenticationProvider);

        return providerManager;
    }

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http.authorizeHttpRequests(a -> a.anyRequest().authenticated())
                .authenticationManager(authenticationManager()).formLogin(f->f.permitAll()).csrf(c->c.disable());
        return http.build();

    }
}

启动项目,进行访问,OK的。

这里还有一个点,就是我们在上面说的provider的parent,在这里,我们也可以模拟一下:


@Configuration
public class SecurityConfig {


    UserDetailsService userDetailsService(){

        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
        inMemoryUserDetailsManager.createUser(User.withUsername("admin").password("{noop}123").build());
        return inMemoryUserDetailsManager;
    }


    AuthenticationManager authenticationManager(){
        //做数据校验
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
         daoAuthenticationProvider.setUserDetailsService(userDetailsService());
        ProviderManager providerManager = new ProviderManager(Arrays.asList(daoAuthenticationProvider),parentAuthenticationManager());

        return providerManager;
    }

    private AuthenticationManager parentAuthenticationManager() {
//做数据校验
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(u2());
        ProviderManager providerManager = new ProviderManager(daoAuthenticationProvider);

        return providerManager;
    }


    UserDetailsService u2(){

        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
        inMemoryUserDetailsManager.createUser(User.withUsername("root").password("{noop}123").build());
        return inMemoryUserDetailsManager;
    }

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http.authorizeHttpRequests(a -> a.anyRequest().authenticated())
                .authenticationManager(authenticationManager()).formLogin(f->f.permitAll()).csrf(c->c.disable());
        return http.build();

    }
}
ProviderManager 这个方法的构造器里面,本身就是提供,注入一个AuthenticationProvider,或者多个AuthenticationProvider以及他们的父类。

那按照我们上面的逻辑,第一个AuthenticationManager 认证不通过的时候,他就会去parent里面找,看还有没有其他认证,找到了就通过,找不到就继续调用parent,一直到找到,或者找不到parent了,认证还是不通过,就返回失败了。

结语

学海无涯,发量做舟,马上要秃。。。

  • 13
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Security 是一个开的安全框架,用于保护基于Spring框架构建的应用程序。MyBatis-Plus 是基于 MyBatis 的增强工具,用于简化 MyBatis 操作和提高开发效率。 将 Spring Security 与 MyBatis-Plus 整合主要包括以下几个步骤: 1. 首先,在 Spring Boot 项目的 pom.xml 文件中添加 Spring Security 和 MyBatis-Plus 的依赖。 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>latest version</version> </dependency> ``` 2. 接下来,在项目的配置文件 application.properties 或 application.yml 中配置数据库连接和 MyBatis-Plus 的相关配置。 3. 创建一个用户实体类和对应的 Mapper 接口,使用 MyBatis-Plus 提供的注解和方法完成数据访问操作。 4. 创建一个自定义的 UserDetailsService 实现类,用于从数据库中获取用户信息,实现 loadUserByUsername 方法。 5. 创建一个自定义的 PasswordEncoder 实现类,用于加密和校验用户密码。 6. 配置 Spring Security,创建一个继承自 WebSecurityConfigurerAdapter 的类,并重写 configure 方法,设置用户认证规则、登录配置和访问控制规则。 7. 在 Spring Boot 启动类上使用 @MapperScan 注解,扫描 Mapper 接口。 通过以上步骤,就可以实现 Spring Security 与 MyBatis-Plus 的整合。在实际应用中,还可以根据具体需求进行一些其他配置和扩展,例如添加角色权限控制、自定义登录页面和处理逻辑等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值