Spring Security源码解析

1、Spring Security介绍

Spring Security是基于spring的应用程序提供声明式安全保护的安全性框架,它提供了完整的安全性解决方案,能够在web请求级别和方法调用级别处理身份证验证和授权.它充分使用了依赖注入和面向切面的技术。.

2、Spring Security 过滤链

在这里插入图片描述

2.1 SecurityContextPersistenceFilter:

SecurityContextPersistenceFilter有两个主要任务:

  • 在请求到达时处理之前,从SecurityContextRepository中获取安全上下文信息填充到SecurityContextHolder;
  • 在请求处理结束后返回响应时,将SecurityContextHolder中的安全上下文信息保存回SecurityContextRepository,并清空SecurityContextHolder。
    通过SecurityContextPersistenceFilter的这种机制,在整个请求处理过程中,开发人员都可以通过使用SecurityContextHolder获取当前访问用户的安全上下文信息。

缺省情况下,SecurityContextPersistenceFilter使用的SecurityContextRepository是HttpSessionSecurityContextRepository,也就是将安全上下文的信息保存在用户的会话中。
为了解决不同Serlvet容器上,尤其是weblogic上的兼容性问题,此Filter必须在整个request处理过程中被调用最多一次。
该Filter也必须在任何认证机制逻辑发生之前被调用。因为这些认证机制都依赖于SecurityContextHolder所包含的安全上下文对象。

源码解析:

// 确保该Filter在一个request处理过程中最多被调到用一次的机制:
// 一旦该Fitler被调用过,他会在当前request增加该属性值为true,利用此标记
// 可以避免Filter被调用二次。
static final String FILTER_APPLIED = "__spring_security_scpf_applied";
// 安全上下文存储库
private SecurityContextRepository repo;
private boolean forceEagerSessionCreation;
// 默认使用http session 作为安全上下文对象存储
public SecurityContextPersistenceFilter() {
     this(new HttpSessionSecurityContextRepository());
}

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
	    //判断同一请求中是否已经执行过此Filter
        if (request.getAttribute("__spring_security_scpf_applied") != null) {
            chain.doFilter(request, response);
        } else {
            boolean debug = this.logger.isDebugEnabled();
	       // 设置Filter访问标记
            request.setAttribute("__spring_security_scpf_applied", Boolean.TRUE);
            if (this.forceEagerSessionCreation) {
                HttpSession session = request.getSession();
                if (debug && session.isNew()) {
                    this.logger.debug("Eagerly created session: " + session.getId());
                }
            }

            HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
		    // 从安全上下文存储库(缺省是http session)中读取安全上下文对象
            SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
            boolean var13 = false;

            try {
                var13 = true;
                // 设置安全上下文对象到SecurityContextHolder然后才继续Filter chain的调用
                SecurityContextHolder.setContext(contextBeforeChainExecution);
                chain.doFilter(holder.getRequest(), holder.getResponse());
                var13 = false;
            } finally {
                if (var13) {
				     // 当前请求已经被处理完成了,清除SecurityContextHolder并将最新的
					// 安全上下文对象保存回安全上下文存储库(缺省是http session)
                    SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
                    SecurityContextHolder.clearContext();
                    this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
                    request.removeAttribute("__spring_security_scpf_applied");
                    if (debug) {
                        this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
                    }

                }
            }

			// 当前请求已经被处理完成了,清除SecurityContextHolder并将最新的
			// 安全上下文对象保存回安全上下文存储库(缺省是http session)
            SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
            SecurityContextHolder.clearContext();
            this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
			// 移除request中设置的标识
            request.removeAttribute("__spring_security_scpf_applied");
            if (debug) {
                this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
            }
        }
    }

2、AbstractAuthenticationProcessingFilter

是处理Form登陆的过滤器,与Form登陆有关的所有操作都是在该类及其子类中进行的。

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);
				// 用户校验失败调用
                this.unsuccessfulAuthentication(request, response, var8);
                return;
            } catch (AuthenticationException var9) {
                this.unsuccessfulAuthentication(request, response, var9);
                return;
            }

            if (this.continueChainBeforeSuccessfulAuthentication) {
                chain.doFilter(request, response);
            }
		
            this.successfulAuthentication(request, response, chain, authResult);
        }
}


/**
* 登陆失败处理
*/
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
    SecurityContextHolder.clearContext();
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Authentication request failed: " + failed.toString(), failed);
        this.logger.debug("Updated SecurityContextHolder to contain null Authentication");
        this.logger.debug("Delegating to authentication failure handler " + this.failureHandler);
    }

    this.rememberMeServices.loginFail(request, response);
	//表明验证身份信息失败之后调用类 failureHandler的onAuthenticationFailure() 方法。而 failureHandler 是 AuthenticationFailureHandler 的实例变量。可以定义自定义的failureHandler进行失败处理。
    this.failureHandler.onAuthenticationFailure(request, response, failed);
}


	/**
	* 登陆成功处理
	*/
  protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
        }

        SecurityContextHolder.getContext().setAuthentication(authResult);
        this.rememberMeServices.loginSuccess(request, response, authResult);
        if (this.eventPublisher != null) {
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }
		//可以定义自定义的successHandler进行成功处理。
        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }

3、UsernamePasswordAuthenticationFilter

AbstractAuthenticationProcessingFilter的子类,执行此类中的attemptAuthentication方法

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
		// 校验是否为POST请求
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
        	// 获取Reuqest中的用户名与密码
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }

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

            username = username.trim();
            // 根据用户输入的用户名、密码构建了 UsernamePasswordAuthenticationToken,并将其交给 AuthenticationManager 来进行认证处理。
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            // 接下来AuthenticationManager 来进行认证处理
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

UsernamePasswordAuthenticationToken 构造方法

    public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
    	// 此时未做认证,首先设置权限为null
        super((Collection)null);
        this.principal = principal;
        this.credentials = credentials;
        // 没有认证设置为false
        this.setAuthenticated(false);
    }

AuthenticationManager 本身不包含认证逻辑,其核心是用来管理所有的 AuthenticationProvider,通过交由合适的 AuthenticationProvider 来实现认证。

4、ProviderManager

该类是 AuthenticationManager 的实现类

  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();
        // 获取所有的AuthenticationProvider
        Iterator var8 = this.getProviders().iterator();

		// 遍历所有的AuthenticationProvider获取当前Authentication所支持的AuthenticationProvider
        while(var8.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var8.next();
            if (provider.supports(toTest)) {
                if (debug) {
                    logger.debug("Authentication attempt using " + provider.getClass().getName());
                }

                try {
                	// 通过provider来进行认证授权
                    result = provider.authenticate(authentication);
                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (AccountStatusException var13) {
                    this.prepareException(var13, authentication);
                    throw var13;
                } catch (InternalAuthenticationServiceException var14) {
                    this.prepareException(var14, authentication);
                    throw var14;
                } catch (AuthenticationException var15) {
                    lastException = var15;
                }
            }
        }

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

        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;
        }
    }

Spring Security 支持多种认证逻辑,每一种认证逻辑的认证方式其实就是一种 AuthenticationProvider。通过 getProviders() 方法就能获取所有的 AuthenticationProvider,通过 provider.supports() 来判断 provider 是否支持当前的认证逻辑。(也可以自定义AuthenticationProvider实现AuthenticationProvider接口,在配置文件中配置生效,下一篇文章会详细讲解)
当选择好一个合适的 AuthenticationProvider 后,通过 provider.authenticate(authentication) 来让 AuthenticationProvider 进行认证。

传统表单登录的 AuthenticationProvider 主要是由 AbstractUserDetailsAuthenticationProvider 来进行处理的,我们来看下它的 authenticate()方法。
首先通过 retrieveUser() 方法读取到数据库中的用户信息:

// 校验用户信息
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
    protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        this.prepareTimingAttackProtection();

        try {
        	// 读取框架中的UserDetailService,进行用户、角色校验,并返回UserDetails 
            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);
        }
    }

返回到AbstractUserDetailsAuthenticationProvider.authenticate方法的中

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
            return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
        });
        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 = 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);
            // 附加校验 校验密码是否为null
            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();
        }

		// 当以上校验均通过是,创建success的认证,否则抛出异常
        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }

在 createSuccessAuthentication 方法中,我们发现它重新 new 了一个 UsernamePasswordAuthenticationToken,因为到这里认证已经通过了,所以将 authorities 注入进去,并设置 authenticated 为 true,即需要认证。

参考文章:
https://blog.csdn.net/andy_zhang2007/article/details/84726992
https://blog.csdn.net/yuanlaijike/article/details/84703690

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值