①權限管理:一般指根據系統設置的安全策略或者安全規則,用戶可以訪問而且只能訪問自己被授權的資源,不多不少。權限管理幾乎出現在任何系統里面,只要有用戶和密碼的系統。
②權限管理分類:
訪問權限:管理員有增刪改查權限,普通用戶只有查詢權限。
數據權限:管理員可以看到所有員工信息,但是員工只能看到自己的信息。
身份認證,就是判斷一個用戶是否為合法用戶的處理過程。最常用的簡單身份認證方式是系統通過核對用戶輸入的用戶名和密碼,看其是否與系統中存儲的該用戶的用戶名和密碼一致,來判斷用戶身份是否正確。例如:密碼登錄,手機短信驗證、三方授權等。
Apache Shiro是一個強大且易用的Java安全框架,能夠非常清晰的處理認證、授權、管理會話以及密碼加密。使用Shiro的易于理解的API,您可以快速、輕松地獲得任何應用程序,從最小的移動應用程序到最大的網絡和企業應用程序。
1.簡單的身份驗證,支持多種數據源
2.對角色的簡單授權,支持細粒度的授權(方法)
3.支持一級緩存,以提升應用程序的性能
4.內置基于POJO的企業會話管理,適用于web及非web環境
5.非常簡單的API加密
6.不跟任何框架綁定,可以獨立運行
Subject:主體,代表了當前“用戶”,這個用戶不一定是一個具體的人,與當前應用交互的任何東西都是Subject,如爬蟲、機器人等;即一個抽象概念;所有Subject都綁定到SecurityManager,與Subject的所有交互都會委托給SecurityManager;可以把Subject認為是一個門面;SecurityManager才是實際的執行者。
SecurityManager:安全管理器,即所有與安全有關的操作都會與SecurityManager交互,且它管理著所有Subject;可以看出它是shiro的核心, SecurityManager相當于spring mvc中的dispatcherServlet前端控制器。
Realm:域,shiro從Realm獲取安全數據(如用戶、角色、權限),就是說SecurityManager要驗證用戶身份,那么它需要從Realm獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應的角色/權限進行驗證用戶是否能進行操作;可以把Realm看成DataSource,即安全數據源。
1)通過ini配置文件創建securityManager
2)調用subject.login方法主體提交認證,提交的token
3)securityManager進行認證,securityManager最終由ModularRealmAuthenticator進行認證。
4)ModularRealmAuthenticator調用IniRealm(給realm傳入token) 去ini配置文件中查詢用戶信息
5)IniRealm根據輸入的token(UsernamePasswordToken)從 shiro.ini查詢用戶信息,根據賬號查詢用戶信息(賬號和密碼)
如果查詢到用戶信息,就給ModularRealmAuthenticator返回用戶信息(賬號和密碼)
如果查詢不到,就給ModularRealmAuthenticator返回null
6)ModularRealmAuthenticator接收IniRealm返回Authentication認證信息;
如果返回的認證信息是null,ModularRealmAuthenticator拋出異常(org.apache.shiro.authc.UnknownAccountException)
如果返回的認證信息不是null(說明inirealm找到了用戶),對IniRealm返回用戶密碼 (在ini文件中存在)和 token中的密碼 進行對比,如果不一致拋出異常(org.apache.shiro.authc.IncorrectCredentialsException)
1)對subject進行授權,調用方法isPermitted("permission串")
2)SecurityManager執行授權,通過ModularRealmAuthorizer執行授權
3)ModularRealmAuthorizer執行realm(自定義的Realm)從數據庫查詢權限數據
調用realm的授權方法:doGetAuthorizationInfo
4)realm從數據庫查詢權限數據,返回ModularRealmAuthorizer
5)ModularRealmAuthorizer調用PermissionResolver進行權限串比對
6)如果比對后,isPermitted中"permission串"在realm查詢到權限數據中,說明用戶訪問permission串有權限,否則 沒有權限,拋出異常。
1.@RequiresAuthentication : 表示當前Subject已經通過login進行了身份驗證;即 Subject.isAuthenticated() 返回 true
2.@RequiresUser : 表示當前Subject 已經身份驗證或者通過記住我登錄的
3.@RequiresGuest : 表示當前Subject沒有身份驗證或通過記住我登陸過,即是游客身份
4.@RequiresRoles(value = { “admin”, “user” }, logical = Logical.AND) : 表示當前 Subject 需要角色 admin和user
5.@RequiresPermissions(value = { “user:a”, “user:b” }, logical = Logical.OR) : 表示當前 Subject 需要權限 user:a 或 user:b
Spring Security在架構上將認證與授權分離,并提供了擴展點。它是一個輕量級的安全框架,它確保基于Spring的應用程序提供身份驗證和授權支持。它與Spring MVC有很好地集成 ,并配備了流行的安全算法實現捆綁在一起。
1.客戶端發起一個請求,進入 Security 過濾器鏈。
2.當到 LogoutFilter 的時候判斷是否是登出路徑,如果是登出路徑則到 logoutHandler ,如果登出成功則到 logoutSuccessHandler 登出成功處理,如果登出失敗則由 ExceptionTranslationFilter ;如果不是登出路徑則直接進入下一個過濾器。
3.當到 UsernamePasswordAuthenticationFilter 的時候判斷是否為登錄路徑,如果是,則進入該過濾器進行登錄操作,如果登錄失敗則到 AuthenticationFailureHandler 登錄失敗處理器處理,如果登錄成功則到 AuthenticationSuccessHandler 登錄成功處理器處理,如果不是登錄請求則不進入該過濾器。
4.當到 FilterSecurityInterceptor 的時候會拿到 uri ,根據 uri 去找對應的鑒權管理器,鑒權管理器做鑒權工作,鑒權成功則到 Controller 層否則到 AccessDeniedHandler 鑒權失敗處理器處理。
1)Shiro比Spring Security更容易使用,也就是實現上簡單一些,同時基本的授權認證Shiro也基本夠用
2)Spring Security社區支持度更高,Spring社區的親兒子,支持力度和更新維護上有優勢,同時和Spring這一套的結合較好。
3)Shiro功能強大、且 簡單、靈活。是Apache 下的項目比較可靠,且不跟任何的框架或者容器綁定,可以獨立運行。
SpringSecurity中提供了專業的方式來解決預檢請求所面臨的問題:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
? ? @Override
? ? protected void configure(HttpSecurity http) throws Exception {
? ? ? ? http.authorizeRequests()
? ? ? ? ? ? ? ? .anyRequest().authenticated()
? ? ? ? ? ? ? ? .and()
? ? ? ? ? ? ? ? .httpBasic()
? ? ? ? ? ? ? ? .and()
? ? ? ? ? ? ? ? // 開啟跨域配置
? ? ? ? ? ? ? ? .cors()
? ? ? ? ? ? ? ? .configurationSource(corsConfigurationSource())
? ? ? ? ? ? ? ? .and()
? ? ? ? ? ? ? ? .csrf().disable();
? ? }
? ? CorsConfigurationSource corsConfigurationSource() {
? ? ? ? // 提供CorsConfiguration實例,并配置跨域信息
? ? ? ? CorsConfiguration corsConfiguration = new CorsConfiguration();
? ? ? ? corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
? ? ? ? corsConfiguration.setAllowedMethods(Arrays.asList("*"));
? ? ? ? corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:8081"));
? ? ? ? corsConfiguration.setMaxAge(3600L);
? ? ? ? UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
? ? ? ? source.registerCorsConfiguration("/**", corsConfiguration);
? ? ? ? return source;
? ? }
}
cors()方法開啟了對CorsConfigurer的配置,其最重要的方法就是configure方法:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
? ? @Override
? ? protected void configure(HttpSecurity http) throws Exception {
? ? ? ? http.authorizeRequests()
? ? ? ? ? ? ? ? .anyRequest().authenticated()
? ? ? ? ? ? ? ? .and()
? ? ? ? ? ? ? ? .httpBasic()
? ? ? ? ? ? ? ? .and()
? ? ? ? ? ? ? ? // 開啟跨域配置
? ? ? ? ? ? ? ? .cors()
? ? ? ? ? ? ? ? .configurationSource(corsConfigurationSource())
? ? ? ? ? ? ? ? .and()
? ? ? ? ? ? ? ? .csrf().disable();
? ? }
? ? CorsConfigurationSource corsConfigurationSource() {
? ? ? ? // 提供CorsConfiguration實例,并配置跨域信息
? ? ? ? CorsConfiguration corsConfiguration = new CorsConfiguration();
? ? ? ? corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
? ? ? ? corsConfiguration.setAllowedMethods(Arrays.asList("*"));
? ? ? ? corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:8081"));
? ? ? ? corsConfiguration.setMaxAge(3600L);
? ? ? ? UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
? ? ? ? source.registerCorsConfiguration("/**", corsConfiguration);
? ? ? ? return source;
? ? }
}
拿到CorsFilter之后,調用http.addFilter方法將其添加到spring security過濾器鏈中,在過濾器鏈構建之前,會先對所有的過濾器進行排序,排序的依據在FilterOrderRegistration中已經定義好了:
FilterOrderRegistration() {
? ? Step order = new Step(INITIAL_ORDER, ORDER_STEP);
? ? put(ChannelProcessingFilter.class, order.next());
? ? order.next(); // gh-8105
? ? put(WebAsyncManagerIntegrationFilter.class, order.next());
? ? put(SecurityContextPersistenceFilter.class, order.next());
? ? put(HeaderWriterFilter.class, order.next());
? ? put(CorsFilter.class, order.next());
? ? put(CsrfFilter.class, order.next());
? ? put(LogoutFilter.class, order.next());
? ? // ...
}
可以看到,CorsFilter的位置在HeaderWriterFilter之后,在CsrfFilter之前,這個時候還沒到認證過濾器。Spring security根據開發者提供的CorsConfigurationSource對象構建出一個CorsFilter,并將該過濾器置于認證過濾器之前。
Spring Security 提供了多種加密算法的實現,開箱即用,非常方便。這些加密算法實現類的父類是 PasswordEncoder ,如果你想要自己實現一個加密算法的話,也需要繼承 PasswordEncoder。
PasswordEncoder 接口一共也就 3 個必須實現的方法。
public?interface?PasswordEncoder?{
????//?加密也就是對原始密碼進行編碼
????String?encode(CharSequence?var1);
????//?比對原始密碼和數據庫中保存的密碼
????boolean?matches(CharSequence?var1,?String?var2);
????//?判斷加密密碼是否需要再次進行加密,默認返回?false
????default?boolean?upgradeEncoding(String?encodedPassword)?{
????????return?false;
????}
}
官方推薦使用基于 bcrypt 強哈希函數的加密算法實現類。
推薦的做法是通過 DelegatingPasswordEncoder 兼容多種不同的密碼加密方案,以適應不同的業務需求。
DelegatingPasswordEncoder 其實就是一個代理類,并非是一種全新的加密算法,它做的事情就是代理上面提到的加密算法實現類。在 Spring Security 5.0 之后,默認就是基于 DelegatingPasswordEncoder 進行密碼加密的。
● permitAll() :無條件允許任何形式訪問,不管你登錄還是沒有登錄。
● anonymous() :允許匿名訪問,也就是沒有登錄才可以訪問。
● denyAll() :無條件決絕任何形式的訪問。
● authenticated():只允許已認證的用戶訪問。
● fullyAuthenticated() :只允許已經登錄或者通過 remember-me 登錄的用戶訪問。
● hasRole(String) : 只允許指定的角色訪問。
● hasAnyRole(String) : 指定一個或者多個角色,滿足其一的用戶即可訪問。
● hasAuthority(String) :只允許具有指定權限的用戶訪問
● hasAnyAuthority(String) :指定一個或者多個權限,滿足其一的用戶即可訪問。
● hasIpAddress(String) : 只允許指定 ip 的用戶訪問。
單點登錄(Single Sign On),簡稱為 SSO,是目前比較流行的企業業務整合的解決方案 之一。SSO 的定義是在多個應用系統中,用戶只需要登錄一次就可以訪問所有相互信任的應用系統。
如果禁用cookie可以使用url中帶參數,把token傳遞給服務端。當然此方法涉及安全性問題,其實在cookie中保存token同樣存在安全性問題。推薦使用sso框架CAS實現單點登錄。
以登錄天貓為例進行說明:
1)當?戶第?次訪問淘寶的時候,因為還沒有登錄,會被引導到認證中?進?登錄。
2)根據?戶提供的登錄信息,認證系統進?身份驗證,如果通過,則登錄成功,并返回給?戶?個認證的憑據(JWT token)。
3)當?戶訪問天貓時,就會將這個 JWT token 帶上,作為??認證的憑據。
4)應?系統接收到請求后會把 JWT token 送到認證中?進?校驗。
5)如果通過校驗,?戶就可以在不?再次登錄的情況下訪問天貓了。
1)代理登錄(agent):用于無法改造的舊系統;
2)令牌環(token):通過Cookie共享令牌環的方式傳遞當前用戶信息,實現SSO,存在跨域問題;
3)身份票據(ticket):除了增加一臺信任驗證服務器,完全滿足了存儲信任,驗證信任,作用范圍和安全性的問題,也是適用最廣的webSSO實現方式
CAS框架:CAS(Central Authentication Service,即:統一認證服務)是實現SSO單點登錄的框架。CAS分為兩部分,CAS Server和CAS Client。
CAS Server用來負責用戶的認證工作,就像是把第一次登錄用戶的一個標識存在這里,以便此用戶在其他系統登錄時驗證其需不需要再次登錄。
CAS Client就是我們自己開發的應用程序,需要接入CAS Server端。當用戶訪問我們的應用時,首先需要重定向到CAS Server端進行驗證,要是原來登陸過,就免去登錄,重定向到下游系統,否則進行用戶名密碼登陸操作。
Ticket Granting ticket (TGT) :可以認為是CAS Server根據用戶名密碼生成的一張票,存在Server端
Ticket-granting cookie (TGC) :其實就是一個Cookie,存放用戶身份信息,由Server發給Client端
Service ticket (ST) :由TGT生成的一次性票據,用于驗證,只能用一次。相當于Server發給Client一張票,然后Client拿著這個票再來找Server驗證,看看是不是Server簽發的。
1)用戶訪問網站,第一次來,重定向到 CAS Server,發現沒有cookie,所以再重定向到CAS Server端的登錄頁面,并且URL帶有網站地址,便于認證成功后跳轉,形如 http ?/cas-server:8100/login?service=http ?/localhost:8081
注意:service后面這個地址就是登錄成功后要重定向的下游系統URL。
2)在登陸頁面輸入用戶名密碼認證,認證成功后cas-server生成TGT,再用TGT生成一個ST。 然后再第三次重定向并返回ST和cookie(TGC)到瀏覽器
3)瀏覽器帶著ST再訪問想要訪問的地址:
http ?/localhost:8081/?ticket=ST-25939-sqbDVZcuSvrvBC6MQlg5
注意:ticket后面那一串就是ST
4)瀏覽器的服務器收到ST后再去cas-server驗證一下是否為自己簽發的,驗證通過后就會顯示頁面信息,也就是重定向到第1步service后面的那個URL
首次登陸完畢。
5)再登陸另一個接入CAS的網站,重定向到CAS Server,server判斷是第一次來(但是此時有TGC,也就是cookie,所以不用去登陸頁面了),但此時沒有ST,去cas-server申請一個于是重定向到cas-server,形如:http: //cas-server:8100/login?service=http ?/localhost:8082 && TGC(cookie) (傳目標地址和cookie)
6)cas-server生成了ST后重定向給瀏覽器http ?/localhost:8082/?ticket=ST-25939-sqfsafgefesaedswqqw5-xxxx
7)瀏覽器的服務器收到ST后再去cas-server驗證一下是否為自己簽發的,驗證通過后就會顯示頁面信息(同第4步)
Token的意思是“令牌”,是服務端生成的一串字符串,作為客戶端進行請求的一個標識。
當用戶第一次登錄后,服務器生成一個token并將此token返回給客戶端,以后客戶端只需帶上這個token前來請求數據即可,無需再次帶上用戶名和密碼。
簡單Token的組成;uid(用戶唯一的身份標識)、time(當前時間的時間戳)、sign(簽名,token的前幾位以哈希算法壓縮成的一定長度的十六進制字符串。為防止token泄露)。
OAuth 是一個行業的標準授權協議,主要用來授權第三方應用獲取有限的權限。實際上它就是一種授權機制,最終目的是為第三方應用頒發一個有時效性令牌 token,使得第三方應用能夠通過該令牌獲取相關的資源。
OAuth 2.0 比較常用的場景就是第三方登錄,當你的網站接入了第三方登錄時一般就是使用的 OAuth 2.0 協議。
現在OAuth 2.0也常見于支付場景(微信支付、支付寶支付)和開發平臺(微信開放平臺、阿里開放平臺等等)。
Access Token 是在 Oauth2.0 協議中,客戶端訪問資源服務器時需要帶上的令牌(其實就是一段全局唯一的隨機字符串)。擁有這個令牌代表著得到用戶的授權。令牌里面包含哪個用戶 在什么時候 授權給哪個app去做什么事情。當然這些信息是不能直接從Access Token 看出來的,而是存在平臺方的數據庫中,平臺可以用Access Token 作為 key 去查詢出這些信息,然后驗證調用方是否有權限。
Refresh Token是專用于刷新 Access Token 的 token。如果沒有Refresh Token,也可以刷新 Access Token,但每次刷新都要用戶輸入登錄用戶名與密碼。有了 Refresh Token,客戶端直接用Refresh Token 去更新Access Token,無需用戶進行額外的操作。
Json Web Token (JWT)是為了在網絡應用環境間傳遞聲明而執行的一種基于JSON的開放標準。該token被設計為緊湊且安全,特別適用于分布式站點單點登錄場景。
JWT由頭部(header)、載荷(payload)、簽證(signature) 三部分組成。