SpringSecurity认证流程源码解析

前言

经过一周的学习对SpringSecurity框架有了基本的认识,并写出了一个基于JWT认证的小模块,通过对源代码的debug大致了解了此框架的认证流程,并做如下记录。

正文

在开始了解SpringSecurity认证流程之前,先看看在认证过程中涉及到的几个重要类。

org.springframework.security.core.Authentication

Authentication
该接口是认证实体,包含将要认证的用户名,密码和权限等,实际开发中使用其子类UsernamePasswordAuthenticationToken

org.springframework.security.authentication.AuthenticationManager

AuthenticationManager
该接口只定义了一个方法authenticate(),所有的认证请求都需通过该方法,接口的主要实现类为ProviderManager

org.springframework.security.authentication.ProviderManager

这个类主要完成用户的认证,密码对比

org.springframework.security.authentication.AuthenticationProvider

该类提供了对不同认证方式的支持,常用子类是DaoAuthenticationProvider

org.springframework.security.core.userdetails.UserDetailsService

该结构定义了方法loadUserByUsername(String username),完成用户的用户信息的查找

直接子类的调用关系:

直接子类的调用关系

源代码

  • ProviderManager
	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子类,找出可以处理此认证请求的Provider
		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}

			if (debug) {
				logger.debug("Authentication attempt using "
						+ provider.getClass().getName());
			}

			try {
			// 这里进行实际的认证
				result = provider.authenticate(authentication);

				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException e) {
				prepareException(e, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw e;
			}
			catch (InternalAuthenticationServiceException e) {
				prepareException(e, authentication);
				throw e;
			}
			catch (AuthenticationException e) {
				lastException = e;
			}
		}

		if (result == null && parent != null) {
			try {
				result = parentResult = parent.authenticate(authentication);
			}
			// 若AuthenticationProvider子类没有能力进行认证,则认证失败,抛出异常
			catch (ProviderNotFoundException e) {
			}
			catch (AuthenticationException e) {
				lastException = parentException = e;
			}
		}

		if (result != null) {
			if (eraseCredentialsAfterAuthentication
					&& (result instanceof CredentialsContainer)) {
					// 认证成功,删除密码
				((CredentialsContainer) result).eraseCredentials();
			}
			if (parentResult == null) {
				eventPublisher.publishAuthenticationSuccess(result);
			}
			return result;
		}
		if (lastException == null) {
			lastException = new ProviderNotFoundException(messages.getMessage(
					"ProviderManager.providerNotFound",
					new Object[] { toTest.getName() },
					"No AuthenticationProvider found for {0}"));
		}
		if (parentException == null) {
			prepareException(lastException, authentication);
		}

		throw lastException;
	}
  • AbstractUserDetailsAuthenticationProvider
	public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
			// 判断是否是UsernamePasswordAuthenticationToken认证类型
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> 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 {
			// 使用DaoAuthenticationProvider调用UserDetailsService子类获取用户权限
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			catch (UsernameNotFoundException notFound) {
				logger.debug("User '" + username + "' not found");
				// 这里是安全方面的处理,不管是用户不存在还是密码错误,都会抛出密码错误异常
				if (hideUserNotFoundExceptions) {
					throw new BadCredentialsException(messages.getMessage(
							"AbstractUserDetailsAuthenticationProvider.badCredentials",
							"Bad credentials"));
				}
				else {
					throw notFound;
				}
			}

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

		try {
		// 用户状态检查,账户是否冻结,账户是否可用等
			preAuthenticationChecks.check(user);
			// 用户密码检查
			additionalAuthenticationChecks(user,
					(UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException exception) {
			if (cacheWasUsed) {
				cacheWasUsed = false;
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
				preAuthenticationChecks.check(user);
				additionalAuthenticationChecks(user,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			else {
				throw exception;
			}
		}

		postAuthenticationChecks.check(user);

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

		Object principalToReturn = user;

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

		return createSuccessAuthentication(principalToReturn, authentication, user);
	}
  • DaoAuthenticationProvider
	protected final UserDetails retrieveUser(String username,
			UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		prepareTimingAttackProtection();
		try {
		// 获取用户权限信息
			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);
		}
	}
  • SpaceUserDetailsImpl
/**
 * @ProjectName: l-space
 * @Package: website.lhc.lspace.config.security
 * @ClassName: SpaceUserDetails
 * @Author: lhc
 * @Description: 自定义用户认证
 * @Date: 2020/4/5 下午 12:09
 */
@Component
public class SpaceUserDetailsImpl implements UserDetailsService {

    private static final Logger log = LoggerFactory.getLogger(SpaceUserDetailsImpl.class);


    @Autowired
    private SpUserMapper userMapper;

    @Autowired
    private SpMenuMapper menuMapper;

    /**
     * 用户校验,并获取权限
     *
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (!StringUtils.hasText(username)) {
            throw new UsernameNotFoundException("[username]为空");
        }

        SpUser spUser = userMapper.getUserByAccount(username);
        if (spUser == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        if (spUser.getStatus() == UserStatus.LOCKED.getStatus()) {
            throw new LockedException("账户已冻结");
        }
        if (spUser.getStatus() == UserStatus.DISABLE.getStatus()) {
            throw new DisabledException("账户已禁用");
        }

        List<GrantedAuthority> authorities = new ArrayList<>();
        Set<String> permissionSet = menuMapper.listPermissionByUserId(spUser.getUserId());
        for (String s : permissionSet) {
            if (StringUtils.hasText(s)) {
                authorities.add(new SimpleGrantedAuthority(s));
            }
        }
        log.info("user:{}; roles:{}", spUser.toString(), authorities.toString());
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String bCryptPassword = passwordEncoder.encode(spUser.getUserPasswd());
        // 这里需要自定义,实现UserDetails接口接口
        return new SpaceUserDetail(spUser.getUserAccount(), bCryptPassword, spUser.getStatus(), authorities);
    }
}

项目地址:https://github.com/longhaicheng/l-space

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章