SpringSecurity认证流程浅析(源码层面)

对SpringSecurity源码的理解对使用SpringSecurity可以说是非常重要,这篇博客就根据其源码,浅析一下其认证流程,对于自己实现一些功能会很有帮助。
为了方便理解,先放一张流程图
在这里插入图片描述
可以看到程序运行到核心过滤器AbstractAuthenticationProcessingFilter 抽象类的时候调用.doFilter()方法,这一步可理解为认证的第一步,以下是源码。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        //判断是否需要认证
        if (!this.requiresAuthentication(request, response)) {
            chain.doFilter(request, response);
        } else {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Request is to process authentication");
            }

            Authentication authResult;
            try {
            	//重点代码,尝试认证
                authResult = this.attemptAuthentication(request, response);
                if (authResult == null) {
                    return;
                }

                this.sessionStrategy.onAuthentication(authResult, request, response);
            } catch (InternalAuthenticationServiceException var8) {
                this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
                //请求失败的handler
                this.unsuccessfulAuthentication(request, response, var8);
                return;
            } catch (AuthenticationException var9) {
                this.unsuccessfulAuthentication(request, response, var9);
                return;
            }

            if (this.continueChainBeforeSuccessfulAuthentication) {
                chain.doFilter(request, response);
            }
			//请求成功的handler
            this.successfulAuthentication(request, response, chain, authResult);
        }
    }

由于其没有实现attemptAuthentication,使用的是其子类UsernamePasswordAuthenticationFilter实现的方法。

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 {
        	//从POST请求体中获取用户名和密码参数
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }

            username = username.trim();
            //用用户名和密码构造一个token
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            //使用get方法获得AuthenticationManager来调用authenticate方法认证
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

使用getAuthenticationManager()默认情况下注入 Spring 容器的 AuthenticationManager 是 ProviderManager。以下是ProviderManager的authenticate方法。

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;
        boolean debug = logger.isDebugEnabled();
        Iterator var8 = this.getProviders().iterator();
		
		//ProviderManager将从AuthenticationProvider列表中循环取出其实现类,并尝试认证
        while(var8.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var8.next();
            if (provider.supports(toTest)) {
                if (debug) {
                    logger.debug("Authentication attempt using " + provider.getClass().getName());
                }

                try {
                	//核心代码,认证
                    result = provider.authenticate(authentication);
                    if (result != null) {
                    	//把认证结果复制到刚刚创建的token里
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (InternalAuthenticationServiceException | AccountStatusException var13) {
                    this.prepareException(var13, authentication);
                    throw var13;
                } catch (AuthenticationException var14) {
                    lastException = var14;
                }
            }
        }

这里讲AuthenticationProvider列表中的一个实现,AbstractUserDetailsAuthenticationProvider。以下是其authentic方法。

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		//判断authentication的类型是否为UsernamePasswordAuthenticationToken
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
            return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
        });
        //从token中获取用户名
        String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
        boolean cacheWasUsed = true;
        //从缓存中获取用户
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
            	//若缓存中没有此用户,则调用方法获得user,这里使用的是其子类DaoAuthentication的实现
                user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                this.logger.debug("User '" + username + "' not found");
                if (this.hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                }

                throw var6;
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }

        try {
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        } catch (AuthenticationException var7) {
            if (!cacheWasUsed) {
                throw var7;
            }

            cacheWasUsed = false;
            user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        }

        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }

        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }

下面是DaoAuthentication的retrieveUser方法实现代码。

protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        this.prepareTimingAttackProtection();

        try {
        	//可以看到这里使用UserDetailsService的loadUserByUsername获取User。
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
            } else {
                return loadedUser;
            }
        } catch (UsernameNotFoundException var4) {
            this.mitigateAgainstTimingAttack(authentication);
            throw var4;
        } catch (InternalAuthenticationServiceException var5) {
            throw var5;
        } catch (Exception var6) {
            throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
        }
    }

UserDetailService有多个实现类,其中一个便是InMemoryUserDetailsManager,即是熟悉的配置文件中配置在内存中的用户的获取。

讲到这里SpringSecurity认证流程基本也讲完了,我们可以总结最重要的两点即是从请求中提取用户信息的Filter类以及从本地获取用户信息的UserDetailService接口。若我们要实现自己的业务流程。便可首先从这两个点下手。
继承UsernamePasswordAuthenticationFilter以达到实现自己控制提取请求中的信息,比如前后端分离中提取json中的信息。
实现UserDetailService接口以通过自己想要的方式获取用户,比如通过自己的方法从数据库中获取用户。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Security是一个基于Spring框架的安全框架,它提供了一套完整的安全认证和授权机制。下面是Spring Security的认证流程: 1. 用户访问需要认证的资,Spring Security会拦截请求并重定向到登录页面。 2. 用户输入用户名和密码,提交表单。 3. Spring Security会将表单提交的用户名和密码封装成一个Authentication对象。 4. AuthenticationManager接口会根据Authentication对象中的用户名和密码去调用UserDetailsService接口的实现类获取用户信息。 5. 如果获取到用户信息,则将用户信息封装成一个包含权限信息的Authentication对象返回给AuthenticationManager。 6. AuthenticationManager会将Authentication对象交给AuthenticationProvider接口的实现类进行认证。 7. 如果认证成功,则将认证成功的Authentication对象返回给Spring Security。 8. Spring Security会将认证成功的Authentication对象存储到SecurityContextHolder中,供后续的访问授权使用。 下面是一个简单的Spring Security认证流程的代码示例: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasAnyRole("ADMIN", "USER") .anyRequest().authenticated() .and() .formLogin() .and() .logout().logoutSuccessUrl("/login").permitAll(); } } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值