Spring Security原理與應用

来源:https://www.cnblogs.com/free-wings/archive/2018/07/14/9308592.html
-Advertisement-
Play Games

Spring Security是什麼 Spring Security是一個能夠為基於Spring的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組可以在Spring應用上下文中配置的Bean(註:包括認證與許可權獲取、配置、處理相關實例),充分利用了Spring IoC,DI(控制 ...


Spring Security是什麼

Spring Security是一個能夠為基於Spring的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組可以在Spring應用上下文中配置的Bean(註:包括認證與許可權獲取、配置、處理相關實例),充分利用了Spring IoC,DI(控制反轉Inversion of Control ,DI:Dependency Injection 依賴註入)和AOP(面向切麵編程)(註:代理增強類)功能,為應用系統提供聲明式的安全訪問控制功能,減少了為企業系統安全控制編寫大量重覆代碼的工作。

核心類庫與認證流程

核心驗證器

AuthenticationManager

該對象提供了認證方法的入口,接收一個Authentiaton對象作為參數;

public interface AuthenticationManager {
	Authentication authenticate(Authentication authentication)
			throws AuthenticationException;
}

驗證邏輯

AuthenticationManager 接收 Authentication 對象作為參數,並通過 authenticate(Authentication) 方法對其進行驗證;AuthenticationProvider實現類用來支撐對 Authentication 對象的驗證動作;UsernamePasswordAuthenticationToken實現了 Authentication主要是將用戶輸入的用戶名和密碼進行封裝,並供給 AuthenticationManager 進行驗證;驗證完成以後將返回一個認證成功的 Authentication 對象;

ProviderManager

它是 AuthenticationManager 的一個實現類,提供了基本的認證邏輯和方法;它包含了一個 List<AuthenticationProvider> 對象,通過 AuthenticationProvider 介面來擴展出不同的認證提供者(當Spring Security預設提供的實現類不能滿足需求的時候可以擴展AuthenticationProvider 覆蓋supports(Class<?> authentication) 方法);

實現邏輯

public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		//#1.獲取當前的Authentication的認證類型
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		Authentication result = null;
		boolean debug = logger.isDebugEnabled();
		//#2.遍歷所有的providers使用supports方法判斷該provider是否支持當前的認證類型,不支持的話繼續遍歷
		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}

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

			try {
				#3.支持的話調用providerauthenticat方法認證
				result = provider.authenticate(authentication);

				if (result != null) {
					#4.認證通過的話重新生成Authentication對應的Token
					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) {
			// Allow the parent to try.
			try {
				#5.如果#1 沒有驗證通過,則使用父類型AuthenticationManager進行驗證
				result = parent.authenticate(authentication);
			}
			catch (ProviderNotFoundException e) {
				// ignore as we will throw below if no other exception occurred prior to
				// calling parent and the parent
				// may throw ProviderNotFound even though a provider in the child already
				// handled the request
			}
			catch (AuthenticationException e) {
				lastException = e;
			}
		}
		#6. 是否擦敏感信息
		if (result != null) {
			if (eraseCredentialsAfterAuthentication
					&& (result instanceof CredentialsContainer)) {
				// Authentication is complete. Remove credentials and other secret data
				// from authentication
				((CredentialsContainer) result).eraseCredentials();
			}

			eventPublisher.publishAuthenticationSuccess(result);
			return result;
		}

		// Parent was null, or didn't authenticate (or throw an exception).

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

		prepareException(lastException, authentication);

		throw lastException;
	}
說明:
  1. 遍歷所有的 Providers,然後依次執行該 Provider 的驗證方法
    • 如果某一個 Provider 驗證成功,則跳出迴圈不再執行後續的驗證;
    • 如果驗證成功,會將返回的 result 既 Authentication 對象進一步封裝為 Authentication Token; 比如 UsernamePasswordAuthenticationToken、RememberMeAuthenticationToken 等;這些 Authentication Token 也都繼承自 Authentication 對象;
  2. 如果 #1 沒有任何一個 Provider 驗證成功,則試圖使用其 parent Authentication Manager 進行驗證;
  3. 是否需要擦除密碼等敏感信息;

Authentication

Authentication對象中的主要方法

public interface Authentication extends Principal, Serializable {
	//#1.許可權集合,可使用AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_ADMIN")返回字元串許可權集合
	Collection<? extends GrantedAuthority> getAuthorities();
	//#2.用戶名密碼認證時可以理解為密碼
	Object getCredentials();
	//#3.認證時包含的一些信息。
	Object getDetails();
	//#4.用戶名密碼認證時可理解時用戶名
	Object getPrincipal();
	#5.是否被認證,認證為true	
	boolean isAuthenticated();
	#6.設置是否能被認證
	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;

AuthenticationProvider

ProviderManager 通過 AuthenticationProvider 擴展出更多的驗證提供的方式;而 AuthenticationProvider 本身也就是一個介面,從類圖中我們可以看出它的實現類AbstractUserDetailsAuthenticationProvider AbstractUserDetailsAuthenticationProvider的子類DaoAuthenticationProvider DaoAuthenticationProvider Spring Security中一個核心的Provider,對所有的資料庫提供了基本方法和入口。

AbstractUserDetailsAuthenticationProvider

AbstractUserDetailsAuthenticationProviderDaoAuthenticationProvider提供了基本的認證方法;

實現邏輯

public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				messages.getMessage(
						"AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));

		// Determine username
		String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
				: authentication.getName();

		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);

		if (user == null) {
			cacheWasUsed = false;

			try {
				#1.獲取用戶信息由子類實現即DaoAuthenticationProvider
				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 {
			#2.前檢查由DefaultPreAuthenticationChecks類實現(主要判斷當前用戶是否鎖定,過期,凍結User介面)
			preAuthenticationChecks.check(user);
			#3.子類實現
			additionalAuthenticationChecks(user,
					(UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException exception) {
			if (cacheWasUsed) {
				// There was a problem, so try again after checking
				// we're using latest data (i.e. not from the cache)
				cacheWasUsed = false;
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
				preAuthenticationChecks.check(user);
				additionalAuthenticationChecks(user,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			else {
				throw exception;
			}
		}
		#4.檢測用戶密碼是否過期對應#2 User介面
		postAuthenticationChecks.check(user);

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

		Object principalToReturn = user;

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

		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

說明:

AbstractUserDetailsAuthenticationProvider主要實現了AuthenticationProvider的介面方法authenticate 並提供了相關的驗證邏輯;

  1. 獲取用戶返回UserDetailsAbstractUserDetailsAuthenticationProvider定義了一個抽象的方法
    protected abstract UserDetails retrieveUser(String username,
      UsernamePasswordAuthenticationToken authentication)
      throws AuthenticationException;
    
  2. 三步驗證工作
    1. preAuthenticationChecks
    2. additionalAuthenticationChecks(抽象方法,子類實現)
    3. postAuthenticationChecks

    3.將已通過驗證的用戶信息封裝成 UsernamePasswordAuthenticationToken 對象並返回;該對象封裝了用戶的身份信息,以及相應的許可權信息,相關源碼如下:

    protected Authentication createSuccessAuthentication(Object principal,
     Authentication authentication, UserDetails user) {
     UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken( principal, authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities()));
    result.setDetails(authentication.getDetails());

     return result; }

DaoAuthenticationProvider

DaoAuthenticationProvider主要做了以下事情

  1. 對用戶身份進行加密操作;
     #1.可直接返回BCryptPasswordEncoder,也可以自己實現該介面使用自己的加密演算法
    核心方法
    String encode(CharSequence rawPassword);

    boolean matches(CharSequence rawPassword, String encodedPassword); private PasswordEncoder passwordEncoder;
  2. 實現了 AbstractUserDetailsAuthenticationProvider 兩個抽象方法,
    1. 獲取用戶信息的擴展點
      protected final UserDetails retrieveUser(String username,
           UsernamePasswordAuthenticationToken authentication)
           throws AuthenticationException {
       UserDetails loadedUser;
      
       try {
           loadedUser = this.getUserDetailsService().loadUserByUsername(username);
       }
      

      主要是通過註入UserDetailsService介面對象,並調用其介面方法 loadUserByUsername(String username) 獲取得到相關的用戶信息。UserDetailsService介面非常重要。

    2. 實現 additionalAuthenticationChecks 的驗證方法(主要驗證密碼);

UserDetailsService

UserDetailsService是一個介面,提供了一個方法

public interface UserDetailsService {
 UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

通過用戶名 username 調用方法 loadUserByUsername 返回了一個UserDetails介面對象(對應AbstractUserDetailsAuthenticationProvider的三步驗證方法);

UserDetails

public interface UserDetails extends Serializable {
 #1.許可權集合
 Collection<? extends GrantedAuthority> getAuthorities();
 #2.密碼	
 String getPassword();
 #3.用戶民
 String getUsername();
 #4.用戶是否過期
 boolean isAccountNonExpired();
 #5.是否鎖定	
 boolean isAccountNonLocked();
 #6.用戶密碼是否過期	
 boolean isCredentialsNonExpired();
 #7.賬號是否可用(可理解為是否刪除)
 boolean isEnabled();
}

JdbcDaoImpl

Spring 為UserDetailsService預設提供了一個實現類 org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl

JdbcDaoImpl的子類(實現了UserDetailsManager):

JdbcUserDetailsManager

該實現類主要是提供基於JDBC對 User 進行增、刪、查、改的方法

public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsManager,
		GroupManager {
	// ~ Static fields/initializers
	// =====================================================================================

	// UserDetailsManager SQL
	#1.定義了一些列對資料庫操作的語句
	public static final String DEF_CREATE_USER_SQL = "insert into users (username, password, enabled) values (?,?,?)";
	public static final String DEF_DELETE_USER_SQL = "delete from users where username = ?";
	public static final String DEF_UPDATE_USER_SQL = "update users set password = ?, enabled = ? where username = ?";
	public static final String DEF_INSERT_AUTHORITY_SQL = "insert into authorities (username, authority) values (?,?)";
	public static final String DEF_DELETE_USER_AUTHORITIES_SQL = "delete from authorities where username = ?";
	public static final String DEF_USER_EXISTS_SQL = "select username from users where username = ?";
	public static final String DEF_CHANGE_PASSWORD_SQL = "update users set password = ? where username = ?";

說明:

UserDetailsService介面作為橋梁,是DaoAuthenticationProvier與特定用戶信息來源進行解耦的地方,UserDetailsServiceUserDetailsUserDetailsManager所構成;UserDetailsUserDetailsManager各司其責,一個是對基本用戶信息進行封裝,一個是對基本用戶信息進行管理;

特別註意UserDetailsServiceUserDetails以及UserDetailsManager都是可被用戶自定義的擴展點,我們可以繼承這些介面提供自己的讀取用戶來源和管理用戶的方法,比如我們可以自己實現一個 與特定 ORM 框架,比如 Mybatis 或者 Hibernate,相關的UserDetailsServiceUserDetailsManager

UserDetailsManager的另一個實現類:

InMemoryUserDetailsManager

該實現類主要是提供基於記憶體對 User 進行增、刪、查、改的方法

public class InMemoryUserDetailsManager implements UserDetailsManager { 
  protected final Log logger = LogFactory.getLog(getClass());
  private final Map<String, MutableUserDetails> users = new HashMap<String, MutableUserDetails>();
  private AuthenticationManager authenticationManager;

  public InMemoryUserDetailsManager() {
  }

  public InMemoryUserDetailsManager(Collection<UserDetails> users) {
	  for (UserDetails user : users) {
		createUser(user);
	  }
  }`

時序圖

 

 轉載編輯自 http://niocoder.com/2018/01/02/Spring-Security%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B8%80-Spring-Security%E8%AE%A4%E8%AF%81%E8%BF%87%E7%A8%8B/

Spring Security項目案例(GitHub地址)

https://github.com/Xiaobai0419/xiaobai

 

 

 

 

















 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 聖杯模式是Javascript中用來實現繼承的一種方法,它的簡單形式如下所示 這種聖杯模式的本質在於,中間生成了一個對象,起到了隔離的作用,今後為Son.prototype添加屬性時,全部都會加在這個對象裡面,所以不會對父級產生影響。 而向上查找是沿著__proto__查找,可以順利查找到父級的屬性 ...
  • 宿主對象即瀏覽器提供的對象,主要包括DOM對象和BOM對象。 一、DOM源起 1.SGML、XML和XHTML SGML(標準通用標記語言)是定義使用標簽來表示數據的標記語言的語法。 - 標簽由一個小於號和一個大於號之間的文本組成,如<title> - 標簽分為起始標簽和結束標簽,分別表示一個特定區 ...
  • 一、跨站腳本攻擊(XSS) 跨站腳本攻擊是指通過存在安全漏洞的Web網站註冊用戶的瀏覽器運行非法的HTML標簽或JavaScript進行的一種攻擊。動態創建的HTML部分有可能隱藏著安全漏洞。就這樣,當攻擊者編寫腳本,設下陷阱,用戶在自己的瀏覽器上運行時,一不小心就會受到被動攻擊。 跨站腳本攻擊有可 ...
  • 公司這幾天項目很緊張,然後一直有各種亂七八糟的事,突然說要整個搜索的功能,第一時間想到的就是用php的模糊搜索,但是性能耗的很大,並且調取出的數據的速度賊慢,在百度上找到一個通過js來搜索的功能分享給大家。 這個是頁面 出來後的效果: 頁面代碼: js代碼 php只做了輸出數據所以在這裡就不放出來了 ...
  • Web Worker SharedWorker Service Worker ...
  • 1.DOM簡介 當網頁被載入時,瀏覽器會創建頁面的文檔對象模型,即DOM(Document Object Model)。 2.DOM操作HTML 2.1 改變HTML輸出流 不要在文檔載入完成之後使用document.write()。會覆蓋該文檔 2.2 尋找元素 通過id找到HTML元素 通過標簽 ...
  • 中介者模式 標簽 : 設計模式 初識中介者模式 定義 用一個中介對象來封裝一系列的對象交互。中介者使得各對象不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立的改變它們之間的交互。 結構和說明 ![image_1cichf9j215a4eatf87cma7sm9.png 86.7kB][1] Me ...
  • 一、阻塞IO與非阻塞IO Linux網路IO模型(5種) (1)阻塞IO模型 所有文件操作都是阻塞的,以套接字介面為例,在進程空間中調用recvfrom,系統調用直到數據包到達且被覆制到應用進程緩衝區或發生錯誤時才返回,期間會一直等待(阻塞)。模型如圖: (2)非阻塞IO模型 recvfrom從應用 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...