Spring Boot+Shiro實(shí)現(xiàn)一個(gè)Http請(qǐng)求的Basic認(rèn)證
前言
今天跟小伙伴們分享一個(gè)實(shí)戰(zhàn)內(nèi)容,使用Spring Boot+Shiro實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Http認(rèn)證。
場(chǎng)景是這樣的,我們平時(shí)的工作中可能會(huì)對(duì)外提供一些接口,如果這些接口不做一些安全認(rèn)證,什么人都可以訪問(wèn),安全性就太低了,所以我們的目的就是增加一個(gè)接口的認(rèn)證機(jī)制,防止別人通過(guò)接口攻擊服務(wù)器。
至于Shiro是什么,Http的Basic認(rèn)證是什么,王子就簡(jiǎn)單介紹一下,詳細(xì)內(nèi)容請(qǐng)自行了解。
Shiro是一個(gè)Java的安全框架,可以簡(jiǎn)單實(shí)現(xiàn)登錄、鑒權(quán)等等的功能。
Basic認(rèn)證是一種較為簡(jiǎn)單的HTTP認(rèn)證方式,客戶(hù)端通過(guò)明文(Base64編碼格式)傳輸用戶(hù)名和密碼到服務(wù)端進(jìn)行認(rèn)證,通常需要配合HTTPS來(lái)保證信息傳輸?shù)陌踩?/p>
實(shí)踐部分
首先說(shuō)明一下測(cè)試環(huán)境。
王子已經(jīng)有了一套集成好Shiro的Spring Boot框架,這套框架詳細(xì)代碼就不做展示了,我們只來(lái)看一下測(cè)試用例。
要測(cè)試的接口代碼如下:
/** * @author liumeng */ @RestController @RequestMapping("/test") @CrossOrigin public class TestAppController extends BaseController { /** * 數(shù)據(jù)匯總 */ @GetMapping("/list") public AjaxResult test() { return success("測(cè)試接口!"); } }
使用Shiro,一定會(huì)有Shiro的攔截器配置,這部分代碼如下:
/** * Shiro過(guò)濾器配置 */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // Shiro的核心安全接口,這個(gè)屬性是必須的 shiroFilterFactoryBean.setSecurityManager(securityManager); // 身份認(rèn)證失敗,則跳轉(zhuǎn)到登錄頁(yè)面的配置 shiroFilterFactoryBean.setLoginUrl(loginUrl); // 權(quán)限認(rèn)證失敗,則跳轉(zhuǎn)到指定頁(yè)面 shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl); // Shiro連接約束配置,即過(guò)濾鏈的定義 LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // 對(duì)靜態(tài)資源設(shè)置匿名訪問(wèn) filterChainDefinitionMap.put("/favicon.ico**", "anon"); filterChainDefinitionMap.put("/lr.png**", "anon"); filterChainDefinitionMap.put("/css/**", "anon"); filterChainDefinitionMap.put("/docs/**", "anon"); filterChainDefinitionMap.put("/fonts/**", "anon"); filterChainDefinitionMap.put("/img/**", "anon"); filterChainDefinitionMap.put("/ajax/**", "anon"); filterChainDefinitionMap.put("/js/**", "anon"); filterChainDefinitionMap.put("/lr/**", "anon"); filterChainDefinitionMap.put("/captcha/captchaImage**", "anon"); // 退出 logout地址,shiro去清除session filterChainDefinitionMap.put("/logout", "logout"); // 不需要攔截的訪問(wèn) filterChainDefinitionMap.put("/login", "anon,captchaValidate"); filterChainDefinitionMap.put("/ssoLogin", "anon"); // 開(kāi)啟Http的Basic認(rèn)證 filterChainDefinitionMap.put("/test/**", "authcBasic"); // 注冊(cè)相關(guān) filterChainDefinitionMap.put("/register", "anon,captchaValidate"); Map<String, Filter> filters = new LinkedHashMap<String, Filter>(); filters.put("onlineSession", onlineSessionFilter()); filters.put("syncOnlineSession", syncOnlineSessionFilter()); filters.put("captchaValidate", captchaValidateFilter()); filters.put("kickout", kickoutSessionFilter()); // 注銷(xiāo)成功,則跳轉(zhuǎn)到指定頁(yè)面 filters.put("logout", logoutFilter()); shiroFilterFactoryBean.setFilters(filters); // 所有請(qǐng)求需要認(rèn)證authcBasic filterChainDefinitionMap.put("/**", "user,kickout,onlineSession,syncOnlineSession"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; }
這里我們要關(guān)注的是代碼中的
filterChainDefinitionMap.put("/test/**", "authcBasic");
這部分代碼,它指定了我們的測(cè)試接口啟動(dòng)了Http的Basic認(rèn)證,這就是我們的第一步。
做到這里我們可以嘗試的去用瀏覽器訪問(wèn)一下接口,會(huì)發(fā)現(xiàn)如下情況:
這就代表Basic認(rèn)證已經(jīng)成功開(kāi)啟了,這個(gè)時(shí)候我們輸入系統(tǒng)的用戶(hù)名和密碼,你以為它就能成功訪問(wèn)了嗎?
答案是否定的,我們只是開(kāi)啟了認(rèn)證,但并沒(méi)有實(shí)現(xiàn)認(rèn)證的邏輯。
王子通過(guò)閱讀部分Shiro源碼,發(fā)現(xiàn)每次發(fā)送請(qǐng)求后,都會(huì)調(diào)用ModularRealmAuthenticator這個(gè)類(lèi)的doAuthenticate方法,源碼如下:
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { assertRealmsConfigured(); Collection<Realm> realms = getRealms(); if (realms.size() == 1) { return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken); } else { return doMultiRealmAuthentication(realms, authenticationToken); } }
可以看出,這個(gè)方法主要就是對(duì)Realm進(jìn)行了管理,因?yàn)槲覀兊南到y(tǒng)本身已經(jīng)有兩個(gè)Ream了,針對(duì)的是不同情況的權(quán)限驗(yàn)證,所以為了使用起來(lái)不沖突,我們可以繼承這個(gè)類(lèi)來(lái)實(shí)現(xiàn)我們自己的邏輯,在配置類(lèi)中增加如下內(nèi)容即可:
@Bean public ModularRealmAuthenticator modularRealmAuthenticator(){ //用自己重新的覆蓋 UserModularRealmAuthericator modularRealmAuthericator = new UserModularRealmAuthericator(); modularRealmAuthericator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy()); return modularRealmAuthericator; }
然后在我們自己的UserModularRealmAuthericator類(lèi)中重寫(xiě)doAuthenticate方法就可以了,這里面的具體實(shí)現(xiàn)邏輯就要看你們自己的使用場(chǎng)景了。
我們可以自己新創(chuàng)建一個(gè)Realm來(lái)單獨(dú)校驗(yàn)Basic認(rèn)證的情況,或者共用之前的Realm,這部分就自由發(fā)揮了。
大概內(nèi)容如下:
public class UserModularRealmAuthericator extends ModularRealmAuthenticator { private static final Logger logger = LoggerFactory.getLogger(UserModularRealmAuthericator.class); @Override protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { assertRealmsConfigured(); //強(qiáng)制轉(zhuǎn)換返回的token UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;//所有Realm Collection<Realm> realms = getRealms(); //最終選擇的Realm Collection<Realm> typeRealms = new ArrayList<>(); for(Realm realm:realms){ if(...){ //這部分是自己的邏輯判斷,過(guò)濾出想要使用的Realm typeRealms.add(realm); } } //判斷是單Realm 還是多Realm if(typeRealms.size()==1){ return doSingleRealmAuthentication(typeRealms.iterator().next(),usernamePasswordToken); }else{ return doMultiRealmAuthentication(typeRealms,usernamePasswordToken); } } }
Realm的具體實(shí)現(xiàn)代碼這里就不做演示了,無(wú)非就是判斷用戶(hù)名密碼是否能通過(guò)校驗(yàn)的邏輯。如果不清楚,可以自行了解Realm的實(shí)現(xiàn)方式。
Realm校驗(yàn)實(shí)現(xiàn)后,Basic認(rèn)證就已經(jīng)實(shí)現(xiàn)了。
測(cè)試部分
接下來(lái)我們?cè)俅问褂脼g覽器對(duì)接口進(jìn)行測(cè)試,輸入用戶(hù)名和密碼,就會(huì)發(fā)現(xiàn)接口成功響應(yīng)了。
我們來(lái)抓取一下請(qǐng)求情況
可以發(fā)現(xiàn),Request Header中有了Basic認(rèn)證的信息Authorization: Basic dGVzdDoxMjM0NTY=
這部分內(nèi)容是這樣的,Basic為一個(gè)固定的寫(xiě)法,dGVzdDoxMjM0NTY=這部分內(nèi)容是userName:Password組合后的Base64編碼,所以我們只要給第三方提供這個(gè)編碼,他們就可以通過(guò)編碼訪問(wèn)我們的接口了。
使用PostMan測(cè)試一下
可以發(fā)現(xiàn)接口是可以成功訪問(wèn)的。
總結(jié)
到這里本篇文章就結(jié)束了,王子向大家仔細(xì)的介紹了如何使用Shiro實(shí)現(xiàn)一個(gè)Http請(qǐng)求的Basic認(rèn)證,是不是很簡(jiǎn)單呢。
以上就是Spring Boot+Shiro實(shí)現(xiàn)一個(gè)Http請(qǐng)求的Basic認(rèn)證的詳細(xì)內(nèi)容,更多關(guān)于Spring Boot+Shiro Http請(qǐng)求的Basic認(rèn)證的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java中的Set接口實(shí)現(xiàn)類(lèi)HashSet和LinkedHashSet詳解
這篇文章主要介紹了Java中的Set接口實(shí)現(xiàn)類(lèi)HashSet和LinkedHashSet詳解,Set接口和java.util.List接口一樣,同樣繼承自Collection接口,它與Collection接口中的方法基本一致,并沒(méi)有對(duì)Collection接口進(jìn)行功能上的擴(kuò)充,只是比Collection接口更加嚴(yán)格了,需要的朋友可以參考下2024-01-01Mybatis有查詢(xún)結(jié)果但存不進(jìn)實(shí)體類(lèi)的解決方案
這篇文章主要介紹了Mybatis有查詢(xún)結(jié)果但存不進(jìn)實(shí)體類(lèi)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11java堆棧類(lèi)使用實(shí)例(java中stack的使用方法)
java中stack的使用方法,堆棧是一種"后進(jìn)先出"(LIFO) 的數(shù)據(jù)結(jié)構(gòu), 只能在一端進(jìn)行插入(稱(chēng)為"壓棧") 或刪除 (稱(chēng)為"出棧")數(shù)據(jù)的操作,下面看示例吧2013-12-12如何解決Spring in action @valid驗(yàn)證不生效的問(wèn)題
這篇文章主要介紹了如何解決Spring in action @valid驗(yàn)證不生效的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06Java創(chuàng)建與結(jié)束線(xiàn)程代碼示例
這篇文章主要介紹了Java創(chuàng)建與結(jié)束線(xiàn)程代碼示例,小編覺(jué)得挺不錯(cuò)的,這里分享給大家,供需要的朋友參考。2017-10-10