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

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