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 Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...