Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
天咱們來聊一聊 Spring Security 中的表決機制與投票器。
當用戶想訪問 Spring Security 中一個受保護的資源時,用戶具備一些角色,該資源的訪問也需要一些角色,在比對用戶具備的角色和資源需要的角色時,就會用到投票器和表決機制。
當用戶想要訪問某一個資源時,投票器根據用戶的角色投出贊成或者反對票,表決方式則根據投票器的結果進行表決。
在 Spring Security 中,默認提供了三種表決機制,當然,我們也可以不用系統提供的表決機制和投票器,而是完全自己來定義,這也是可以的。
本文松哥將和大家重點介紹三種表決機制和默認的投票器。
先來看投票器。
在 Spring Security 中,投票器是由 AccessDecisionVoter 接口來規范的,我們來看下 AccessDecisionVoter 接口的實現:
可以看到,投票器的實現有好多種,我們可以選擇其中一種或多種投票器,也可以自定義投票器,默認的投票器是 WebExpressionVoter。
我們來看 AccessDecisionVoter 的定義:
public interface AccessDecisionVoter<S> {
int ACCESS_GRANTED = 1;
int ACCESS_ABSTAIN = 0;
int ACCESS_DENIED = -1;
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
int vote(Authentication authentication, S object,
Collection<ConfigAttribute> attributes);
}
我稍微解釋下:
我們來分別看下幾個投票器的實現。
RoleVoter 主要用來判斷當前請求是否具備該接口所需要的角色,我們來看下其 vote 方法:
public int vote(Authentication authentication, Object object,
Collection<ConfigAttribute> attributes) {
if (authentication == null) {
return ACCESS_DENIED;
}
int result = ACCESS_ABSTAIN;
Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);
for (ConfigAttribute attribute : attributes) {
if (this.supports(attribute)) {
result = ACCESS_DENIED;
for (GrantedAuthority authority : authorities) {
if (attribute.getAttribute().equals(authority.getAuthority())) {
return ACCESS_GRANTED;
}
}
}
}
return result;
}
這個方法的判斷邏輯很簡單,如果當前登錄主體為 null,則直接返回 ACCESS_DENIED 表示拒絕訪問;否則就從當前登錄主體 authentication 中抽取出角色信息,然后和 attributes 進行對比,如果具備 attributes 中所需角色的任意一種,則返回 ACCESS_GRANTED 表示允許訪問。例如 attributes 中的角色為 [a,b,c],當前用戶具備 a,則允許訪問,不需要三種角色同時具備。
另外還有一個需要注意的地方,就是 RoleVoter 的 supports 方法,我們來看下:
public class RoleVoter implements AccessDecisionVoter<Object> {
private String rolePrefix = "ROLE_";
public String getRolePrefix() {
return rolePrefix;
}
public void setRolePrefix(String rolePrefix) {
this.rolePrefix = rolePrefix;
}
public boolean supports(ConfigAttribute attribute) {
if ((attribute.getAttribute() != null)
&& attribute.getAttribute().startsWith(getRolePrefix())) {
return true;
}
else {
return false;
}
}
public boolean supports(Class<?> clazz) {
return true;
}
}
可以看到,這里涉及到了一個 rolePrefix 前綴,這個前綴是 ROLE_,在 supports 方法中,只有主體角色前綴是 ROLE_,這個 supoorts 方法才會返回 true,這個投票器才會生效。
RoleHierarchyVoter 是 RoleVoter 的一個子類,在 RoleVoter 角色判斷的基礎上,引入了角色分層管理,也就是角色繼承,關于角色繼承,小伙伴們可以參考松哥之前的文章(Spring Security 中如何讓上級擁有下級的所有權限?)。
RoleHierarchyVoter 類的 vote 方法和 RoleVoter 一致,唯一的區別在于 RoleHierarchyVoter 類重寫了 extractAuthorities 方法。
@Override
Collection<? extends GrantedAuthority> extractAuthorities(
Authentication authentication) {
return roleHierarchy.getReachableGrantedAuthorities(authentication
.getAuthorities());
}
角色分層之后,需要通過 getReachableGrantedAuthorities 方法獲取實際具備的角色,具體請參考:Spring Security 中如何讓上級擁有下級的所有權限? 一文。
這是一個基于表達式權限控制的投票器,松哥后面專門花點時間和小伙伴們聊一聊基于表達式的權限控制,這里我們先不做過多展開,簡單看下它的 vote 方法:
public int vote(Authentication authentication, FilterInvocation fi,
Collection<ConfigAttribute> attributes) {
assert authentication != null;
assert fi != null;
assert attributes != null;
WebExpressionConfigAttribute weca = findConfigAttribute(attributes);
if (weca == null) {
return ACCESS_ABSTAIN;
}
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
fi);
ctx = weca.postProcess(ctx, fi);
return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
: ACCESS_DENIED;
}
如果你熟練使用 SpEL 的話,這段代碼應該說還是很好理解的,不過根據我的經驗,實際工作中用到 SpEL 場景雖然有,但是不多,所以可能有很多小伙伴并不了解 SpEL 的用法,這個需要小伙伴們自行復習下,我也給大家推薦一篇還不錯的文章:https://www.cnblogs.com/larryzeal/p/5964621.html。
這里代碼實際上就是根據傳入的 attributes 屬性構建 weca 對象,然后根據傳入的 authentication 參數構建 ctx 對象,最后調用 evaluateAsBoolean 方法去判斷權限是否匹配。
上面介紹這三個投票器是我們在實際開發中使用較多的三個。
另外還有幾個比較冷門的投票器,松哥也稍微說下,小伙伴們了解下。
Jsr250Voter
處理 Jsr-250 權限注解的投票器,如 @PermitAll,@DenyAll 等。
AuthenticatedVoter
AuthenticatedVoter 用于判斷 ConfigAttribute 上是否擁有 IS_AUTHENTICATED_FULLY、IS_AUTHENTICATED_REMEMBERED、IS_AUTHENTICATED_ANONYMOUSLY 三種角色。
IS_AUTHENTICATED_FULLY 表示當前認證用戶必須是通過用戶名/密碼的方式認證的,通過 RememberMe 的方式認證無效。
IS_AUTHENTICATED_REMEMBERED 表示當前登錄用戶必須是通過 RememberMe 的方式完成認證的。
IS_AUTHENTICATED_ANONYMOUSLY 表示當前登錄用戶必須是匿名用戶。
當項目引入 RememberMe 并且想區分不同的認證方式時,可以考慮這個投票器。
AbstractAclVoter
提供編寫域對象 ACL 選項的幫助方法,沒有綁定到任何特定的 ACL 系統。
PreInvocationAuthorizationAdviceVoter
使用 @PreFilter 和 @PreAuthorize 注解處理的權限,通過 PreInvocationAuthorizationAdvice 來授權。
當然,如果這些投票器不能滿足需求,也可以自定義。
一個請求不一定只有一個投票器,也可能有多個投票器,所以在投票器的基礎上我們還需要表決機制。
表決相關的類主要是三個:
他們的繼承關系如上圖。
三個決策器都會把項目中的所有投票器調用一遍,默認使用的決策器是 AffirmativeBased。
三個決策器的區別如下:
這里的具體判斷邏輯比較簡單,松哥就不貼源碼了,感興趣的小伙伴可以自己看看。
當我們使用基于表達式的權限控制時,像下面這樣:
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/user/**").hasRole("user")
.anyRequest().fullyAuthenticated()
那么默認的投票器和決策器是在 AbstractInterceptUrlConfigurer#createDefaultAccessDecisionManager 方法中配置的:
private AccessDecisionManager createDefaultAccessDecisionManager(H http) {
AffirmativeBased result = new AffirmativeBased(getDecisionVoters(http));
return postProcess(result);
}
List<AccessDecisionVoter<?>> getDecisionVoters(H http) {
List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
WebExpressionVoter expressionVoter = new WebExpressionVoter();
expressionVoter.setExpressionHandler(getExpressionHandler(http));
decisionVoters.add(expressionVoter);
return decisionVoters;
}
這里就可以看到默認的決策器和投票器,并且決策器 AffirmativeBased 對象創建好之后,還調用 postProcess 方法注冊到 Spring 容器中去了,結合松哥本系列前面的文章,大家知道,如果我們想要修改該對象就非常容易了:
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/user/**").hasRole("user")
.anyRequest().fullyAuthenticated()
.withObjectPostProcessor(new ObjectPostProcessor<AffirmativeBased>() {
@Override
public <O extends AffirmativeBased> O postProcess(O object) {
List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
decisionVoters.add(new RoleHierarchyVoter(roleHierarchy()));
AffirmativeBased affirmativeBased = new AffirmativeBased(decisionVoters);
return (O) affirmativeBased;
}
})
.and()
.csrf()
.disable();
這里只是給大家一個演示,正常來說我們是不需要這樣修改的。當我們使用不同的權限配置方式時,會有自動配置對應的投票器和決策器。或者我們手動配置投票器和決策器,如果是系統配置好的,大部分情況下并不需要我們修改。
本文主要和小伙伴們簡單分享一下 Spring Security 中的投票器和決策器,關于授權的更多知識,松哥下篇文章繼續和小伙伴們細聊。
天是被《即刻電音》相關熱搜洗版的一天。
上到節目主理人,下到主理人粉絲,甚至連主理人的經紀人都沒落下。
可能是節目開播以來喜提熱搜最多的一次。
到底怎么回事呢?
原來是《即刻電音》的錄制現場發生了極大的騷亂。
來看一個張藝興粉絲發來的repo▽
簡單總結一下:
1.大張偉粉絲覺得節目投票有問題,一直在喊“黑幕”
2.大張偉為保隊員又一次打起了感情牌
3.張藝興想說話卻發現話筒沒聲,憤怒地喊“開麥”和“宇宙(張藝興隊名)就是diao”
4.張藝興經紀人上場搶走張藝興話筒,并說大張偉粉絲輸不起就別玩
插播一條,給沒看過《即刻電音》的朋友們補補課。
張藝興粉絲控訴的打感情牌其實是指大張偉在節目中哭訴的事。
當時因為有選手對淘汰的規則表示不滿,大張偉搬出自己的例子來進行說服。
過程中他一度哽咽地說:“我的心告訴我應該退出這個行業,但我一直都在這個行業的原因就是因為我愛音樂。我有很多問題,但是也有太多人仗著我喜歡音樂欺負我。”
用笑遮掩自己的情緒失控就還挺令人動容的▽
OK,補完課再回到兩家粉絲撕X的事。
咱們也不能只偏聽一方觀點,再來看一下大張偉粉絲方面關于錄制現場的repo▽
過程上的描述和張藝興粉絲那邊的說法基本相同。
但細節上進行了一些反駁,比如:
1.大張偉粉絲質疑節目組黑幕是因為從第二輪開始就發現投票器有問題
2.大張偉情緒激動是因為他的團隊面臨團滅的風險,同時質問導演組:“錢我可以不要了,但你們要有底線。”
3.大張偉粉絲罵節目組的話被路過的張藝興聽到了,并回頭問大張偉粉絲“誰罵我”
4.張藝興確實大喊開麥被楊天真攔下了,但當時本來就要退場了,所有人的麥都會被收
也是個公說公有理,婆說婆有理的“羅生門”。
不過,讓張藝興粉絲生氣的其中一個原因還在于大張偉粉絲在節目現場上升愛豆。
據張藝興粉絲描述,當時看張藝興情緒激動,大張偉粉絲不僅沒有收斂,還冷嘲熱諷罵張藝興犯賤。
說:“見過撿錢的,見過撿東西的,沒見過撿罵的。”
但大張偉粉絲則說,他們壓根沒罵張藝興,罵的是這個XX的世界,結果恰好被路過的張藝興聽到。
由此還衍生出了一個關于張藝興從大張偉粉絲全世界路過的段子...
更抓馬的部分在于,兩方粉絲都說是第一次見自家愛豆這么生氣。
粉絲的賣慘邏輯就真的都很相似。
大張偉粉絲還痛心疾首稱:“我第一次見大張偉當眾給自己一個耳光,這要怎么才能下得去手?”
本來張藝興粉絲就一直在斥責大張偉賣慘了,這么一說不是在給對家送錘送人頭嗎...
于是,其他粉絲只好一而再再而三地進行澄清。
替他們感到心累。
然而,就在雙方爭得不可開交之時,張藝興粉絲卻開始一條接一條地甩出錄制現場的各種音頻。
要知道,節目錄制是不能帶這些電子設備進去的。
真不愧是經驗老道的流量粉。
M@https://v.youku.com/v_show/id_XMzk4OTUxNTQzNg==.html@M
根據音頻來看,大張偉確實哽咽了▽
大張偉發言完畢后,他的粉絲用很大的聲音喊“黑幕”▽
這時,輪到張藝興隊的隊員tsunano進行發言。
他說想給大家講一個小故事。
但臺下卻一直情緒激動地在喊“不聽”▽
tsunano試圖將故事繼續講下去,背景音卻全是讓他閉嘴的聲音▽
這些“不聽”、“閉嘴”的抗議聲甚至大到大張偉不得不出面調節的程度▽
而在結果公布后,節目組應該是有讓主理人進到候場區之類的地方進行休息或是調試。
只聽到錄制重新開始,導演讓主理人回到各自的位置。
就在這時,張藝興突然大喊了幾聲開麥。
開麥后,就有了他感慨“wow”。
緊接著就是repo種所說的張藝興歇斯底里的那句“宇宙隊就是最diao的”。
之前也有人說是大張偉煽動了現場的粉絲情緒。
在被曝光的另一段錄音當中,有粉絲錄到休息期間,大張偉的確帶著情緒對張藝興粉絲 說了“你們牛逼”。
但橘子君聽完音頻覺得倒也不像一些repo當中說的那樣是朝張藝興粉絲喊話。
當時大張偉并沒有帶麥,算是正常講話的聲音大小,應該只有附近一部分粉絲能聽見。
萬萬沒想到,錄個節目竟然能錄到兩位在娛樂圈摸爬滾打多年的藝人全都情緒失控...
只能說,大家就還挺真情實感的。
聽一聽音頻就知道了,現場氛圍真的就跟打群架似的,來一把火隨時能著...
面對如此窘境,主持人只好先cue掌握著最主要投票權的專業音樂評審回應黑幕問題▽
看觀眾情緒依然未得到任和平復,主持人又出面了。
他說:“這個節目沒有黑幕,也沒必要有黑幕。你覺得你投票器壞了那一票就能夠讓整個節目的結果有所改變嗎?你覺得所有你們投票器壞了就針對你們了嗎?”
嗯嗯嗯???
主持人不應該是起到安撫作用嗎,怎么所說的話還這么具有煽動性呢???
很多比賽就是會因為一票之差決定命運啊,難道不是每一票都很重要嗎???
不過,從專業評審和主持人的兩段話來看,《即刻電音》這個節目似乎還真的存在一些問題。
投票器壞掉也就算了,專業評審竟然說自己沒有完整看到選手的演出???
到底咋回事啊...
不是說好的公平公正嗎[捂臉]...
所以,也怪不得兩邊粉絲會這樣爆發。
連節目中的另一位主理人尚雯婕都在節目錄完的第一時間無語地在微博發了個“呵呵”。
再來說說《即刻電音》這個節目。
別看它電音的題材小眾,但每期的話題還真不比爆款節目來得少。
第一期就因為選手diss大張偉而引發討論。
當時,選手Anti-General說自己一聽大張偉要來參加節目,他的第一反應就是退賽。
節目一播出,Anti-General就因為這番言論被罵很慘。
隨后,他又在微博發了長文進行解釋。
為什么會在節目里這樣說?
一是因為抄襲事件,二是因為大張偉在節目中所表演的曲目并不能讓他認同。
雖然大張偉在節目中的一些專業表現讓他的印象有所改觀,但他還是認為大張偉在用自己做不到的事要求別人。
他理解大張偉所承受的精神壓力,但又認為這些壓力都是因為大張偉還沒能做出服眾的作品,那大家就只能把以前的事翻出來講。
最后,Anti-General還很嗆地放話:至于大老師你這次能不能洗白,我們拭目以待。
當時大張偉也有回應,他說:
“一個常年在做音樂的人,即便不善言表,答起這個‘自己’的問題也會滔滔不絕。”
“我音樂中的自己就是DM48的游樂園,就是陽光彩虹小白馬,就是必須熱血有趣又可愛。”
算是解答了Anti-General說他“用自己做不到的事要求別人”的這部分▽
但選手所提出的洗白質疑依然依然是目前《即刻電音》為人詬病的問題之一。
大概是因為常常被說抄襲,所以大張偉在《即刻電音》中一直都沖在抓抄襲的第一線。
選手表演完,他立刻就說跟某樂隊的音樂有些相似▽
選手承認有借鑒,但并不承認抄襲▽
特邀主理人也認同大張偉說的觀點▽
大張偉則表示,自己之所以會說這段話,是因為有時候大家所認為的瑕疵,“天都會原諒你,但是網友不會”。
再加上開頭我們放過的那段大張偉哭訴截圖,理所當然有不少人會質疑這節目是在給大張偉洗白。
而針對洗白質疑,大張偉自己則說:“我根本沒必要洗白,因為我早就是彩色的了。”
除此之外,圍繞張藝興的主要爭議則是上次讓馮提莫晉級。
馮提莫大家應該都并不陌生,印象中她跟電音沒扯上過啥關系。
但她卻來參加了這檔節目...
張藝興看完表演后很為難,他先是做了這樣一個雙手抱頭的動作,說了一句“嘶...喔...這個......這個怎么說呢”▽
然后給出了這樣的評價:
“你們選的是未來感,沒有讓我覺得很未來啊。”
這時候和馮提莫一起合作的KK張站出來幫馮提莫講話,說“想要幫她制作和改編歌曲,所以在編曲上用了大量未來的聲音設計......”
藝興終于聽不下去了,舉手打斷:
“等一下,對不起打斷你一下,就是我覺得就很普通,一般的電子舞曲的歌曲就是這樣的,我沒有覺得有什么很未來的東西。”
“你是一個很好的歌手,你也是一個好的制作人,就是呈現出來的東西有點差強人意。”
到現在為止,張藝興的態度已經很明顯了吧,他并不滿意這個組合的表演。
按照這個走向,他接下來會很自然地“淘汰”他們才對。
然而最終的結果是,他給了“推薦”(滿臉寫著開心)▽
粉絲們也看不過去了,在評論里放出了錄制當天的“實情”。
說是在導演勸說下張藝興才會讓馮提莫晉級。
但張藝興自己則說“是我做的決定,導演組沒有逼我”。
但也并沒有多少人相信就是了。
熱評都是“被綁架了你就眨眨眼”...
-------------------------
到目前為止《即刻電音》一共才播了5期,爭議話題就已經有前面說的這么多了。
爭議程度怕是跟當年的《花少2》都有得一拼了吧?
還不知道最新錄制的這一期已經亂到這種程度后期要怎么剪。
吃瓜...
最后一句
能做的不多,給后期買點防脫洗發水吧。
Spring Security 是一種基于 Spring AOP 和 Servlet 過濾器的安全框架。原名Acegi Security,最早于2003年起源于Spring社區。 2007年末正式歸為Spring Framework的正式子項目,并改名為Spring Security 。此后, Spring Security有了長遠發展,現已成為一款基于Spring Framework的廣泛應用的安全框架,主要為應用服務提供用戶認證(Authentication)和用戶授權(Authorization)功能。詳情可參考Spring Security 官方文檔。
Spring Security 針對安全方面的兩大難題, 鑒權(Authentication)和授權(Authorization)提供了靈活強大的解決方案。
Spring Security優勢:
如果項目需要安全控制功能,不用自己去實現一套, 集成Spring Security專業安全框架是首選, 適用后臺管理、接口資源管理、微服務統一鑒權等場景。
處理流程圖:
類名 | 描述 |
AffirmativeBased | 只要有一個投票器允許訪問, 請求立刻允許放行, 不管之前存在拒絕的決定 |
ConsensusBased | 多數票機制(允許或拒絕),多數的一方決定了AccessDecisionManager的結果。平局的投票和空票(全部棄權)的結果是可配置的 |
UnanimousBased | 所有的投票器必須全部是允許的, 否則訪問將被拒絕 |
Spring Boot 與Spring Security 集成, 包含一般集成用法, 還包括自定義用戶登陸頁面使用, 自定義內存模式驗證, 以及自定義登陸成功與失敗邏輯處理。
1、創建工程spring-boot-security-integrate
啟動類:
com.mirson.spring.boot.security.integrate.startup.SecurityIntegrateApplication
@SpringBootApplication
@ComponentScan(basePackages = {"com.mirson"})
public class SecurityIntegrateApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityIntegrateApplication.class, args);
}
}
2、MAVEN依賴
<dependencies>
<!-- Spring Boot Security 安全依賴組件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Boot Thymeleaf 模板依賴組件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Spring Boot Web 依賴組件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Spring boot freemarker 自動化配置組件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
</dependencies>
3、定義一個外部訪問接口
1)創建實體
定義一個用戶實體
com.mirson.spring.boot.security.integrate.po.User
@Data
public class User {
/**
* ID
*/
private Integer id;
/**
* 用戶名稱
*/
private String name;
/**
* 年齡
*/
private String age;
/**
* 省份
*/
private String province;
/**
* 創建時間
*/
private Date createDate;
}
2)提供Web訪問接口
提供一個獲取用戶信息接口
com.mirson.spring.boot.security.integrate.controller.UserController
@RestController
@RequestMapping("/user")
@Log4j2
public class UserController {
@GetMapping("/getUserInfo")
@ResponseBody
public User getUserInfo() {
User user = new User();
user.setId(0);
user.setAge("21");
user.setName("user1");
user.setCreateDate(new Date());
return user;
}
}
4、工程配置
server:
port: 22618
spring:
application:
name: security-integrate
# security 安全配置
security:
user:
name: "admin"
password: "admin"
設置默認的用戶名與密碼為admin。
5、功能驗證
1) 請求獲取用戶信息接口
訪問接口: http://127.0.0.1:22618/getUserInfo
沒有鑒權的情況下, 會出現登陸界面。
2)輸入用戶信息admin/admin, 再次請求獲取用戶信息接口
正確輸入用戶信息后, 可以正常訪問用戶信息接口。
Spring Security 內置會有一套登陸頁面, 也可以自定修改, 這里通過freemark模板來實現自定登陸頁面渲染。
application.yml增加配置:
# freemarker 模板配置
freemarker:
allow-request-override: false
allow-session-override: false
cache: true
charset: UTF-8
check-template-location: true
content-type: text/html
enabled: true
expose-request-attributes: false
expose-session-attributes: false
expose-spring-macro-helpers: true
prefer-file-system-access: true
suffix: .ftl
template-loader-path: classpath:/templates/
增加freemark模板文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>自定義系統登陸</title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/signin.css" rel="stylesheet">
</head>
<body>
<div class="container form-margin-top">
<form class="form-signin" action="/user/doUserLogin" method="post">
<h2 class="form-signin-heading" align="center">自定義系統登陸</h2>
<input type="text" name="username" class="form-control form-margin-top" placeholder="賬號" required autofocus>
<input type="password" name="password" class="form-control" placeholder="密碼" required>
<button class="btn btn-lg btn-primary btn-block" type="submit">sign in</button>
</form>
</div>
<footer>
<p>support by: mirson</p>
</footer>
</body>
</html>
添加CSS靜態資源文件
添加JAVA CONFIG配置:
com.mirson.spring.boot.security.integrate.config.SpringSecurityConfiguration
@Configuration
@EnableWebSecurity
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/user/userLoginForm",
"/css/**").permitAll()
.antMatchers("/user/getUserInfo").authenticated()
.and()
.formLogin()
.loginPage("/user/userLoginForm") //自定義登錄頁面
.permitAll() //允許所有人訪問該路由
.and()
.csrf()
.disable() //暫時禁用csrc否則無法提交
.httpBasic();
}
}
重啟, 訪問接口: http://127.0.0.1:22618/user/getUserInfo
會自動跳轉到自定義的登陸頁面:
修改com.mirson.spring.boot.security.integrate.config.SpringSecurityConfiguration配置:
@Configuration
@EnableWebSecurity
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/user/doUserLogin", "/user/userLoginForm",
"/css/**").permitAll()
.antMatchers("/user/getUserInfo").authenticated() //hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/user/userLoginForm") //自定義登錄頁面
.loginProcessingUrl("/user/doUserLogin") // 自定義登陸處理地址
.permitAll() //允許所有人訪問該路由
.and()
.csrf()
.disable() //暫時禁用csrc否則無法提交
.httpBasic();
}
}
通過Spring Security 可以控制, 哪些資源需要受權限保護, 哪些可以開放訪問。
/user/userLoginForm 用戶登陸頁面
/user/doUserLogin 登陸處理地址
/css/** 靜態資源文件
/user/getUserInfo 獲取用戶信息接口
可以指定Role角色權限, 不指定, 只要登陸即擁有訪問權限。
loginPage指定/user/userLoginForm為自定義登陸頁面;
loginProcessingUrl為登陸請求處理接口地址,可以不用對其做具體實現,Spring Security 會做默認處理。
1、創建鑒權用戶對象:
com.mirson.spring.boot.security.integrate.po.OAuthUser
@Data
public class OAuthUser extends User {
public OAuthUser(String account, String password){
super(account, password, true, true, true, true, Collections.EMPTY_SET);
}
}
繼承的是Spring Security 的User對象, 將賬號和密碼信息, 傳遞至父類構造方法。
2、修改SpringSecurityConfiguration配置
增加:
@Configuration
@EnableWebSecurity
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
/**
* 用戶認證服務
* */
@Bean
@Override
protected UserDetailsService userDetailsService() {
//創建基于內存用戶管理對象
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//自定義權限
Collection<GrantedAuthority> adminAuth = new ArrayList<>();
adminAuth.add(new SimpleGrantedAuthority("ADMIN"));
//自定義用戶
OAuthUser oAuthUser = new OAuthUser("admin", "admin123");
manager.createUser(oAuthUser);
return manager;
}
/**
* 配置密碼編碼器, 不需加密
* @return
*/
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
...
}
采用內存模式管理用戶對象InMemoryUserDetailsManager, 自定義認證用戶名和密碼, 分別為admin,admin123。 需要配置密碼編碼器, 可以支持自定義密碼加密方式, 這里不需加密, 配置NoOpPasswordEncoder。
Spring Security 提供了接口, 登陸成功, 可以通過處理器實現自定義邏輯。 新建com.mirson.spring.boot.security.integrate.handler.SecuritySuccessHandler
@Component
@Log4j2
public class SecuritySuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
@Autowired
private ObjectMapper objectMapper;
/**
* 認證成功處理
* @param request
* @param response
* @param authentication
* @throws IOException
* @throws ServletException
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("Process in SecuritySuccessHandler ==> login success.");
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}
這里通過自定義登陸成功處理器, 將登陸成功的信息返回客戶端。
將處理器加入到自定義配置SpringSecurityConfiguration中:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/user/doUserLogin", "/user/userLoginForm",
"/css/**").permitAll()
.antMatchers("/user/getUserInfo").authenticated() //hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/user/userLoginForm") //自定義登錄頁面
.loginProcessingUrl("/user/doUserLogin") // 自定義登陸處理地址
.successHandler(securitySuccessHandler) // 自定義登陸成功處理器
.permitAll() //允許所有人訪問該路由
.and()
.csrf()
.disable() //暫時禁用csrc否則無法提交
.httpBasic();
}
如果登陸失敗, 也可以通過處理器實現自定義邏輯。 新建com.mirson.spring.boot.security.integrate.handler.SecurityFailureHandler
@Component
@Log4j2
public class SecurityFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
log.info("Process in SecurityFailureHandler ==> login failure.");
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(exception.getMessage()));
}
}
將登陸失敗的錯誤信息, 返回給客戶端。
修改自定義配置SpringSecurityConfiguration:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/user/doUserLogin", "/user/userLoginForm",
"/css/**").permitAll()
.antMatchers("/user/getUserInfo").authenticated() //hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/user/userLoginForm") //自定義登錄頁面
.loginProcessingUrl("/user/doUserLogin") // 自定義登陸處理地址
.successHandler(securitySuccessHandler) // 自定義登陸成功處理器
.failureHandler(securityFailureHandler) // 自定義登陸失敗處理器
.permitAll() //允許所有人訪問該路由
.and()
.csrf()
.disable() //暫時禁用csrc否則無法提交
.httpBasic();
}
1、驗證內存模式鑒權
內存模式我們設置的用戶名和密碼為admin/admin123
默認配置文件是admin/admin
輸入admin/admin123, 成功登陸, 內存模式已生效。
2、登陸成功處理器驗證
重啟服務, 訪問獲取用戶信息接口, http://127.0.0.1:22618/user/getUserInfo
默認是會跳轉到登陸頁面, 如果沒配置登陸成功處理器, 登陸成功后, 會進入上一次訪問頁面
可以看到, 登陸成功后, 并沒有跳轉到上一次訪問的用戶信息接口, 而是返回了登陸成功處理器的結果。
3、登陸失敗處理器驗證
同樣, 問獲取用戶信息接口, http://127.0.0.1:22618/user/getUserInfo, 會自動跳轉到登陸頁面。
采用錯誤的用戶密碼, 返回了登陸失敗處理器的結果。
Spring Security 提供了鑒權與授權的功能支持, 這里做了詳細講解, 如何使用與配置, 并講解了自定義鑒權處理功能, 實際業務當中,并非一層不變, 會做不同配置修改,比如自定義資源訪問配置, 不同項目有不同的要求, 掌握這些自定義配置, 基本可以覆蓋主要的業務場景, 針對更復雜的鑒權, 可以采用oauth2做鑒權處理, 在后續教程中會做講解。
教程源碼下載地址: https://download.csdn.net/download/hxx688/86400104
*請認真填寫需求信息,我們會在24小時內與您取得聯系。