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