網(wǎng)站頁面由什么構成百度seo關鍵詞優(yōu)化公司
【SpringSecurity】springboot整合SpringSecurity實現(xiàn)登錄校驗與權限認證
- 【一】SpringSecurity框架簡介
- 【二】SpringSecurity與shiro
- 【1】SpringSecurity特點
- 【2】shiro特點
- 【3】SpringSecurity和shiro總結
- 【三】SpringSecurity過濾器
- 【1】SpringSecurity中常見的過濾器
- 【2】15種過濾器
- (1)WebAsyncManagerIntegrationFilter
- (2)SecurityContextPersistenceFilter
- (3)HeaderWriterFilter
- (4)CsrfFilter
- (5)LogoutFilter
- (6)UsernamePasswordAuthenticationFilter
- (7)DefaultLoginPageGeneratingFilter
- (8)DefaultLogoutPageGeneratingFilter
- (9)BasicAuthenticationFilter
- (10)RequestCacheAwareFilter
- (11)SecurityContextHolderAwareRequestFilter
- (12)AnonymousAuthenticationFilter
- (13)SessionManagementFilter
- (14)ExceptionTranslationFilter
- (15)FilterSecurityInterceptor
- 【3】SpringSecurity基本流程
- (1)整體流程概述
- (2)詳細流程步驟
- 【四】PasswordEncoder接口
- 【五】整合SpringSecurity實現(xiàn)登錄校驗與權限認證
- 【1】身份認證
- (1)controller測試
- (2)登錄認證流程
- (3)創(chuàng)建一個用戶表
- (4)創(chuàng)建一個MyUserDetailsService類
- (5)通過配置類對AuthenticationManager與自定義的UserDetails和PasswordEncoder進行關聯(lián)
- (6)在登錄方法所在的類中注入AuthenticationManager
- (7)介紹UsernamePasswordAuthenticationToken
- (8)測試登錄
- (9)SecurityFilterChain 過濾器
- (10)測試登錄
- (11)測試退出登錄
- 【2】權限校驗
- (1)角色與權限
- (2)角色表與權限表
- (3)權限認證流程
- (4)在MyUserDetailsService中實現(xiàn)用戶權限的賦值
- (5)MySysUserDetails中完成角色和權限的賦值
- (6)基于請求
- (7)基于方法
- 【3】總結
- 【五】Springboot整合SpringSecurity入門
- 【1】pom.xml
- 【2】application.properties
- 【3】SecurityConfig
- 【4】啟動類
- 【5】User實體類
- 【6】UserService服務層
- 【7】UserMapper
- 【8】UserController控制層
- 【六】微服務認證與授權實現(xiàn)思路
- 【七】微服務代碼實例
- 【1】父工程pom.xml
- 【2】common模塊
- 【3】common模塊->SpringSecurity子模塊
- 【4】common模塊->service_base
- 【5】gateway模塊
- 【八】ruoyi平臺整合SpringSecurity案例【待完成】
【一】SpringSecurity框架簡介
關于安全方面的兩個主要區(qū)域是“認證”和“授權”(或者說是訪問控制),一般來說,Web應用的安全性包括用戶認證(Authentication)和用戶授權(Authorization)兩個部分,這兩點也是SpringSecurity重要核心功能。
(1)用戶認證指的是:驗證某個用戶是否為系統(tǒng)中的合法主體,也就是說用戶能否訪問該系統(tǒng)。用戶認證一般要求用戶提供用戶名和密碼。系統(tǒng)通過校驗用戶名和密碼來完成認證過程。通俗點說就是系統(tǒng)認為用戶是否能登錄。
(2)用戶授權指的是驗證某個用戶是否有權限執(zhí)行某個操作。在一個系統(tǒng)中,不同用戶所具有的的權限是不同的。比如對一個文件來說,有的用戶只能進行讀取,而有的用戶可以進行修改。一般來說,系統(tǒng)會為不同的用戶分配不同的角色,而每個角色則對應一系列的權限。通俗點講就是系統(tǒng)判斷用戶是否有權限去做某些事情。
【二】SpringSecurity與shiro
【1】SpringSecurity特點
(1)與Spring框架無縫整合
(2)全面的權限控制
(3)專門為Web開發(fā)而設計
舊版本不能脫離Web環(huán)境使用
新版本對整個框架進行了分層抽取,分成了核心模塊和Web模塊,單獨引入了核心模塊就可以脫離Web環(huán)境
重量級
【2】shiro特點
Apache旗下的輕量級權限控制框架
(1)輕量級
shiro主張的理念是把復雜的事情變簡單,針對性能更高要求的互聯(lián)網(wǎng)應用有更好的變現(xiàn)
(2)通用性
好處:不局限于Web環(huán)境,可以脫離Web環(huán)境使用
缺陷:在Web環(huán)境下一些特定的需求需要手動編寫代碼定制
【3】SpringSecurity和shiro總結
相對于shiro,在SSM中整合SpringSecurity都是比較麻煩的操作,所以,SpringSecurity雖然功能比shiro強大,但是使用反而沒有shiro多,(shiro雖然功能沒有SpringSecurity多,但是對于大部分項目而言,shiro也夠用了)。自從有了Springboot之后,Springboot對于SpringSecurity提供了自動化配置方案,可以使用更少的配置來使用SpringSecurity。因此,一般來說,常見的安全管理技術棧的組合是這樣的:
(1)SSM+shiro
(2)Springboot/SpringCloud+SpringSecurity
以上只是一個推薦的組合而已,如果單從技術上來說,無論怎么組合,都是可以運行的。
【三】SpringSecurity過濾器
【1】SpringSecurity中常見的過濾器
【2】15種過濾器
SpringSecurity采用的是責任鏈的設計模式,它有一條很長的過濾器鏈。這些過濾器按特定順序執(zhí)行,每個過濾器負責不同的安全任務,如身份驗證、授權、會話管理等。下面介紹一些重要的過濾器及其功能:
(1)WebAsyncManagerIntegrationFilter
功能:將 Spring Security 上下文與 Spring 的 WebAsyncManager 集成,確保異步請求也能正確處理安全上下文。
位置:通常位于過濾器鏈的最前端。
(2)SecurityContextPersistenceFilter
功能:在每個請求開始時,從 HttpSession 中獲取安全上下文(SecurityContext),并將其設置到當前線程中;在請求結束時,將安全上下文保存回 HttpSession 中。
位置:在請求處理的早期階段執(zhí)行。
(3)HeaderWriterFilter
功能:用于向響應頭中添加安全相關的頭部信息,如 X-Frame-Options、X-XSS-Protection 等,增強應用的安全性。
位置:在安全上下文設置之后執(zhí)行。
(4)CsrfFilter
功能:防止跨站請求偽造(CSRF)攻擊,驗證請求中的 CSRF 令牌是否有效。
位置:在處理表單提交等敏感請求之前執(zhí)行。
(5)LogoutFilter
功能:處理用戶的注銷請求,清除安全上下文、銷毀會話等。
位置:在處理注銷相關的 URL 請求時執(zhí)行。
(6)UsernamePasswordAuthenticationFilter
功能:處理基于表單的用戶名和密碼認證,從請求中提取用戶名和密碼,嘗試進行身份驗證。
位置:通常在處理登錄表單提交的 URL 時執(zhí)行。
(7)DefaultLoginPageGeneratingFilter
功能:如果沒有自定義登錄頁面,該過濾器會生成一個默認的登錄頁面。
位置:在處理登錄相關請求時,若沒有自定義登錄頁面則會起作用。
(8)DefaultLogoutPageGeneratingFilter
功能:如果沒有自定義注銷頁面,該過濾器會生成一個默認的注銷頁面。
位置:在處理注銷相關請求時,若沒有自定義注銷頁面則會起作用。
(9)BasicAuthenticationFilter
功能:處理基于 HTTP Basic 認證的請求,從請求頭中提取基本認證信息進行身份驗證。
位置:在處理需要 Basic 認證的請求時執(zhí)行。
(10)RequestCacheAwareFilter
功能:處理請求緩存,當用戶在未認證的情況下訪問受保護資源時,會緩存該請求,認證成功后重定向到原請求的資源。
位置:在認證前后處理請求緩存相關操作。
(11)SecurityContextHolderAwareRequestFilter
功能:將 HttpServletRequest 包裝成 SecurityContextHolderAwareRequestWrapper,提供額外的安全相關方法。
位置:在請求處理過程中,對請求進行包裝。
(12)AnonymousAuthenticationFilter
功能:如果請求在經(jīng)過前面的過濾器后仍未認證,該過濾器會為請求設置一個匿名身份,避免后續(xù)處理因缺少身份信息而出錯。
位置:在前面的認證過濾器之后執(zhí)行。
(13)SessionManagementFilter
功能:管理用戶會話,處理會話超時、并發(fā)會話控制等問題。
位置:在會話相關操作的處理階段執(zhí)行。
(14)ExceptionTranslationFilter
功能:捕獲認證和授權過程中拋出的異常,并將其轉換為合適的 HTTP 響應,如重定向到登錄頁面或返回 403 狀態(tài)碼。
位置:在認證和授權過濾器之后,處理異常情況。
(15)FilterSecurityInterceptor
功能:進行最終的授權檢查,根據(jù)配置的訪問規(guī)則判斷用戶是否有權限訪問請求的資源。
位置:位于過濾器鏈的末尾,在所有其他過濾器執(zhí)行完畢后進行最終的授權決策。
【3】SpringSecurity基本流程
(1)整體流程概述
當一個客戶端發(fā)起請求時,請求會進入 Spring Security 的過濾器鏈。過濾器鏈中的各個過濾器會依次對請求進行處理,其中涉及認證的過濾器會嘗試對用戶進行身份驗證。如果認證成功,用戶的身份信息會被存儲在安全上下文中;如果認證失敗,則會根據(jù)配置進行相應的錯誤處理。
(2)詳細流程步驟
(1)請求進入過濾器鏈
客戶端發(fā)起請求后,請求首先會到達 Spring Security 的過濾器鏈。Spring Security 默認有多個過濾器,這些過濾器按特定順序排列,每個過濾器負責不同的安全任務。例如,SecurityContextPersistenceFilter 是過濾器鏈中的第一個過濾器,它會在請求開始時從 HttpSession 中獲取安全上下文(SecurityContext),并將其設置到當前線程中;在請求結束時,將安全上下文保存回 HttpSession 中。
(2)認證過濾器處理
不同類型的認證方式由不同的認證過濾器處理,以下是幾種常見的認證方式及其對應的過濾器:
1-表單登錄認證(UsernamePasswordAuthenticationFilter)
請求匹配:當請求的 URL 匹配到配置的登錄 URL(默認為 /login),且請求方法為 POST 時,UsernamePasswordAuthenticationFilter 會開始工作。
提取認證信息:該過濾器會從請求中提取用戶名和密碼,通常是從表單的 username 和 password 字段中獲取。
創(chuàng)建認證令牌:使用提取的用戶名和密碼創(chuàng)建一個 UsernamePasswordAuthenticationToken 對象,該對象實現(xiàn)了 Authentication 接口,用于封裝用戶的認證信息。
調用認證管理器:將創(chuàng)建的 UsernamePasswordAuthenticationToken 對象傳遞給 AuthenticationManager 進行認證。
2-HTTP Basic 認證(BasicAuthenticationFilter)
請求匹配:當請求頭中包含 Authorization 字段,且值以 Basic 開頭時,BasicAuthenticationFilter 會對請求進行處理。
提取認證信息:從 Authorization 字段中提取基本認證信息(通常是經(jīng)過 Base64 編碼的用戶名和密碼),并進行解碼。
創(chuàng)建認證令牌:使用解碼后的用戶名和密碼創(chuàng)建 UsernamePasswordAuthenticationToken 對象。
調用認證管理器:將認證令牌傳遞給 AuthenticationManager 進行認證。
(3)認證管理器(AuthenticationManager)處理
AuthenticationManager 是一個接口,它的主要職責是對傳入的 Authentication 對象進行認證。默認實現(xiàn)是 ProviderManager,它內部維護了一個 AuthenticationProvider 列表。
1-遍歷認證提供者:ProviderManager 會遍歷 AuthenticationProvider 列表,依次調用每個 AuthenticationProvider 的 authenticate 方法,直到找到能夠處理該 Authentication 對象的 AuthenticationProvider。
2-認證處理:AuthenticationProvider 會根據(jù)具體的認證邏輯對 Authentication 對象進行驗證,例如查詢數(shù)據(jù)庫驗證用戶名和密碼是否匹配。如果驗證成功,會返回一個已認證的 Authentication 對象;如果驗證失敗,會拋出相應的異常。
(4)用戶詳情服務(UserDetailsService)
在認證過程中,AuthenticationProvider 通常會調用 UserDetailsService 來獲取用戶的詳細信息。UserDetailsService 是一個接口,其主要方法是 loadUserByUsername,該方法根據(jù)用戶名從數(shù)據(jù)源(如數(shù)據(jù)庫、LDAP 等)中加載用戶的詳細信息,返回一個 UserDetails 對象。UserDetails 接口封裝了用戶的核心信息,如用戶名、密碼、權限等。
(5)認證結果處理
認證成功:如果 AuthenticationProvider 認證成功,會返回一個已認證的 Authentication 對象,其中包含用戶的詳細信息和權限。UsernamePasswordAuthenticationFilter 或 BasicAuthenticationFilter 會將該對象設置到安全上下文中(SecurityContextHolder),表示用戶已成功認證。
認證失敗:如果認證失敗,AuthenticationProvider 會拋出相應的異常,如 BadCredentialsException(用戶名或密碼錯誤)。ExceptionTranslationFilter 會捕獲這些異常,并根據(jù)配置進行相應的處理,例如重定向到登錄頁面或返回 401 未授權狀態(tài)碼。
(6)后續(xù)請求處理
認證成功后,用戶的身份信息會存儲在安全上下文中。后續(xù)的請求會通過 SecurityContextPersistenceFilter 從 HttpSession 中恢復安全上下文,確保用戶在整個會話期間保持認證狀態(tài)。同時,FilterSecurityInterceptor 會根據(jù)配置的訪問規(guī)則對請求進行授權檢查,判斷用戶是否有權限訪問請求的資源。
【四】PasswordEncoder接口
【五】整合SpringSecurity實現(xiàn)登錄校驗與權限認證
創(chuàng)建一個spring boot項目,并導入一些初始依賴,不贅述
【1】身份認證
(1)controller測試
由于我們加入了 spring-boot-starter-security 的依賴,所以security就會自動生效了。這時直接編寫一個controller控制器,并編寫一個接口進行測試:
可以看到我們在訪問這個接口時出現(xiàn)了攔截,必須要我們進行登錄之后才能訪問;
(2)登錄認證流程
Spring Security 6.x 的認證實現(xiàn)流程如下:
(1)用戶提交登錄請求
(2)Spring Security 將請求交給 UsernamePasswordAuthenticationFilter 過濾器處理。
(3)UsernamePasswordAuthenticationFilter 獲取請求中的用戶名和密碼,并生成一個 AuthenticationToken 對象,將其交給 AuthenticationManager 進行認證。
(4)AuthenticationManager 通過 UserDetailsService 獲取用戶信息,然后使用 PasswordEncoder 對用戶密碼進行校驗。
(5)如果密碼正確,AuthenticationManager 會生成一個認證通過的 Authentication 對象,并返回給 UsernamePasswordAuthenticationFilter 過濾器。如果密碼不正確,則 AuthenticationManager 拋出一個 AuthenticationException 異常。
(6)UsernamePasswordAuthenticationFilter 將 Authentication 對象交給 SecurityContextHolder 進行管理,并調用 AuthenticationSuccessHandler 處理認證成功的情況。
(7)如果認證失敗,UsernamePasswordAuthenticationFilter 會調用 AuthenticationFailureHandler 處理認證失敗的情況。
看起來有點復雜,其實寫起來很簡單的。spring security的底層就是一堆的過濾器來是實現(xiàn)的,而我們只需要編寫一些重要的過濾器即可,其他的就用spring security默認的實現(xiàn),只要不影響我們正常的登錄功能即可。
(3)創(chuàng)建一個用戶表
創(chuàng)建一個用戶表用來進行登錄實現(xiàn),注意這個表中的用戶名不能重復,我們將用戶名作為每一個用戶的唯一憑證,就如同人的手機號或者身份證號一樣。
實體類、mapper、service、controller等基本配置不贅述
(4)創(chuàng)建一個MyUserDetailsService類
創(chuàng)建一個MyUserDetailsService類來實現(xiàn)SpringSecurity的UserDetailsService接口(這里進行用戶登錄和授權的邏輯處理)
UserDetailsService:此接口中定義了登錄服務方法,用來實現(xiàn)登錄邏輯。方法的返回值是UserDetails,也是spring security框架定義中的一個接口,用來存儲用戶信息,我們可以自定義一個類用來實現(xiàn)這個接口,將來返回的時候就返回我們自定義的用戶實體類。
(1)實現(xiàn)UserDetailsService接口
@Component
public class MyUserDetailsService implements UserDetailsService {/** UserDetailsService:提供查詢用戶功能,如根據(jù)用戶名查詢用戶,并返回UserDetails*UserDetails,SpringSecurity定義的類, 記錄用戶信息,如用戶名、密碼、權限等* */@Autowiredprivate SysUserMapper sysUserMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//根據(jù)用戶名從數(shù)據(jù)庫中查詢用戶SysUser sysUser = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(username != null, SysUser::getUsername, username));if (sysUser==null){throw new UsernameNotFoundException("用戶不存在");}// 封裝查詢到的用戶信息MySysUserDetails mySysUserDetails=new MySysUserDetails(sysUser);return mySysUserDetails;}
}
(2)實現(xiàn)UserDetails接口
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MySysUserDetails implements UserDetails {private Integer id;private String username;private String password;// 用戶擁有的權限集合,我這里先設置為null,將來會再更改的@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}public MySysUserDetails(SysUser sysUser) {this.id = sysUser.getId();this.username = sysUser.getUsername();this.password = sysUser.getPassword();}// 后面四個方法都是用戶是否可用、是否過期之類的。我都設置為true@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
(5)通過配置類對AuthenticationManager與自定義的UserDetails和PasswordEncoder進行關聯(lián)
Spring Security是通過AuthenticationManager實現(xiàn)的認證,會借此來判斷用戶名和密碼的正確性
密碼解析器spring security框架定義的接口:PasswordEncoder
spring security框架強制要求,必須在spring容器中存在PasswordEncoder類型對象,且對象唯一
@Configuration
@EnableWebSecurity //開啟webSecurity服務
public class SecurityConfig {@Autowiredprivate MyUserDetailsService myUserDetailsService;@Beanpublic AuthenticationManager authenticationManager(PasswordEncoder passwordEncoder){DaoAuthenticationProvider provider=new DaoAuthenticationProvider();//將編寫的UserDetailsService注入進來provider.setUserDetailsService(myUserDetailsService);//將使用的密碼編譯器加入進來provider.setPasswordEncoder(passwordEncoder);//將provider放置到AuthenticationManager 中ProviderManager providerManager=new ProviderManager(provider);return providerManager;}/** 在security安全框架中,提供了若干密碼解析器實現(xiàn)類型。* 其中BCryptPasswordEncoder 叫強散列加密??梢员WC相同的明文,多次加密后,* 密碼有相同的散列數(shù)據(jù),而不是相同的結果。* 匹配時,是基于相同的散列數(shù)據(jù)做的匹配。* Spring Security 推薦使用 BCryptPasswordEncoder 作為密碼加密和解析器。* */
@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
}
(6)在登錄方法所在的類中注入AuthenticationManager
調用authenticate實現(xiàn)認證邏輯,并且在認證之后返回認證過的用戶信息:
(1)controller層
// 用戶登錄
@PostMapping("/login")
public String login(@RequestBody LoginDto loginDto){String token= sysUserService.login(loginDto);return token;
}
(2)對應的service層的方法
編寫具體的登錄方法,創(chuàng)建一個UsernamePasswordAuthenticationToken對象,并傳入相應的用戶名和密碼;注入一個AuthenticationManager的bean,這個bean是spring security封裝的用來進行認證的類,調用這個類的authenticate方法并傳入UsernamePasswordAuthenticationToken對象;
@Autowiredprivate AuthenticationManager authenticationManager;// 登錄接口的具體實現(xiàn)@Overridepublic String login(LoginDto loginDto) {// 傳入用戶名和密碼UsernamePasswordAuthenticationToken usernamePassword =new UsernamePasswordAuthenticationToken(loginDto.getUsername(),loginDto.getPassword());//是實現(xiàn)登錄邏輯,此時就會去調用LoadUserByUsername方法Authentication authenticate = authenticationManager.authenticate(usernamePassword);//獲取返回的用戶信息Object principal = authenticate.getPrincipal();//強轉為MySysUserDetails類型MySysUserDetails mySysUserDetails = (MySysUserDetails) principal;//輸出用戶信息System.err.println(mySysUserDetails);//返回tokenString token= UUID.randomUUID().toString();return token;}
(7)介紹UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken是Spring Security中用于表示基于用戶名和密碼的身份驗證令牌的類。它主要有以下兩個構造方法:
(1)UsernamePasswordAuthenticationToken(Object principal, Object credentials)
1-principal參數(shù)表示認證主體,通常是用戶名或用戶對象。在身份驗證過程中,這通常是用來標識用戶的信息,可以是用戶名、郵箱等。
2-credentials參數(shù)表示憑據(jù),通常是用戶的密碼或其他憑證信息。在身份驗證過程中,這用于驗證用戶的身份。
(2)UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities)
1-除了上述兩個參數(shù)外,這個構造方法還接受一個授權權限集合(authorities參數(shù))。這個集合表示用戶所擁有的權限,通常是一個包含用戶權限信息的集合。
2-GrantedAuthority接口代表了用戶的權限信息,可以通過該接口的實現(xiàn)類來表示用戶具體的權限。
這兩個構造方法的作用是創(chuàng)建一個包含用戶身份信息、憑據(jù)信息和權限信息的身份驗證令牌,以便在Spring Security中進行身份驗證和授權操作。通過這些構造方法,可以將用戶的相關信息封裝成一個完整的身份驗證對象,方便在安全框架中進行處理和驗證。
總之,UsernamePasswordAuthenticationToken是在Spring Security中用于表示用戶名密碼身份驗證信息的重要類,通過不同的構造方法可以滿足不同場景下的需求。
(8)測試登錄
造一些用戶數(shù)據(jù),并進行測試
訪問:訪問http://localhost:8080/test,自動跳轉到了Spring Security提供的默認的登錄頁面;這是因為Spring Security默認所有的請求都要先登錄才行,我們在這里登錄之后就可以繼續(xù)訪問test頁面了;
這里的用戶名和密碼就是我們在數(shù)據(jù)庫中存儲的用戶名和密碼
既然這個test請求要先進行攔截認證才能訪問,那么,我們剛才編寫的登錄接口sys-user/login豈不是也要先進行攔截認證才能訪問,這就與我們編寫登錄接口的初衷違背了,我們這個接口就是用來登陸的,現(xiàn)在還要先登錄認證,之后再訪問這個登錄接口。那么有沒有一種方法,不使用SpringSecurity默認的登錄頁面呢,使我們編寫的登錄接口所有人都可以直接訪問呢?
(9)SecurityFilterChain 過濾器
配置用戶登錄的接口可以暴露出來,被所有人都正常的訪問,不會被攔截轉跳到默認登錄頁面,而是跳到自定義的登錄頁面。
在第二步設置的SecurityConfig類中設置過濾器:
(1)在spring security6.x版本之后,原先經(jīng)常用的and()方法被廢除了,現(xiàn)在spring官方推薦使用Lambda表達式的寫法。
(2)因為我們接下來要進行測試,所以禁用CSRF保護
/** 配置權限相關的配置* 安全框架本質上是一堆的過濾器,稱之為過濾器鏈,每一個過濾器鏈的功能都不同* 設置一些鏈接不要攔截* */@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {//關閉csrfhttpSecurity.csrf(it->it.disable());
httpSecurity.authorizeHttpRequests(it->it.requestMatchers("/login","/sys-user/login").permitAll() //設置登錄路徑所有人都可以訪問.anyRequest().authenticated() //其他路徑都要進行攔截);//表單httpSecurity.formLogin(from->from.loginPage("/login") //跳轉到自定義的登錄頁面.loginProcessingUrl("/sys-user/login") //處理前端的請求,與from表單的action一致即可.defaultSuccessUrl("/index") //默認的請求成功之后的跳轉頁面,直接訪問登錄頁面);return httpSecurity.build();}
配置對應的controller
@Controller
public class Login {@GetMapping("/login")public String login(){System.out.println("用戶進入登錄頁面");return "login"; //沒使用json返回,直接映射到自定義登錄的頁面}@GetMapping("/index")@ResponseBodypublic String index(){return "用戶登錄成功";}
}
(10)測試登錄
(1)訪問test請求:遇到攔截,說明我們的配置生效了
(2)訪問login請求,并用賬號密碼登陸成功
登錄之后,會跳轉到/test請求地址
現(xiàn)在我們直接訪問/login登錄頁面:可以看到返回了/index頁面的內容(這個是我們設置的默認登錄成功之后返回的頁面)
(11)測試退出登錄
需要注意的是在Spring Security中,沒有專門用于處理退出失敗的接口。退出(注銷)操作通常是由瀏覽器發(fā)起的,Spring Security會攔截注銷請求并執(zhí)行相應的注銷邏輯。
退出操作通常是通過調用SecurityContextLogoutHandler來完成的,它會清除用戶的安全上下文,包括認證信息和會話信息。
在security框架中,默認提供了退出登陸的功能。請求地址是 /lohout 此為默認值,可以通過配置進行修該。直接請求 /logout ,會實現(xiàn)自動退出登錄邏輯(默認的/logout接收get、和post請求)
退出登陸時,會清楚內存中的登錄用戶主體信息,銷毀會話對象等等。
自定義退出接口:
httpSecurity.logout(logout->{logout.logoutUrl("/user/login") //自定義退出接口.logoutSuccessHandler(logoutSuccess); //退出成功之后的邏輯});
編寫退出成功之后的邏輯,我們可以在這里刪除掉redis中的數(shù)據(jù),清除登錄的上下文,設置返回的信息等等…(如果是前后端分離狀態(tài)下的spring security,這些工作都可以在自定義的退出接口中進行實現(xiàn)。如果是前后端不分離的表單式登錄,還是使用傳統(tǒng)的Cookie和Session來進行用戶信息的保存,我們自需要調用ogout.logoutUrl(“/user/login”) 方法來指定退出路徑即可,退出的邏輯不需要我們來實現(xiàn)。)
@Component
public class LogoutSuccess implements LogoutSuccessHandler {@Resourceprivate RedisTemplate<String,String> redisTemplate;/*
* 登錄成功之后的邏輯
* */@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {String token = request.getHeader("token");// 刪除redis中的數(shù)據(jù)redisTemplate.delete(token);Map<String,Object> map=new HashMap<>();map.put("msg","退出成功");map.put("code",200);response.getWriter().write(JSON.toJSONString(map));response.setContentType("application/json;charset=utf-8");}}
【2】權限校驗
(1)角色與權限
角色與權限在SpringSecurity中的作用:
(1)角色(Role)
角色是一組權限的集合,通常代表著用戶的身份或職責。在Spring Security中,可以通過配置將角色分配給用戶或者用戶組,以此來控制用戶對系統(tǒng)資源的訪問。例如,管理員擁有添加、刪除和修改用戶的權限,而普通用戶只能查看自己的信息。
(2)權限(Permission)
權限是指對某一特定資源的訪問控制,例如讀寫文件、訪問數(shù)據(jù)庫等。在Spring Security中,通常使用“資源-操作”命名方式來定義權限,例如“/admin/* - GET”表示允許訪問以/admin/開頭的所有URL的GET請求??梢詫嘞薹峙浣o角色,也可以將其分配給單獨的用戶。
(2)角色表與權限表
角色與權限之間的關系是多對多的,建立兩張簡單的表;一張用來存放角色、一張用來存放權限
(1)角色表
(2)權限表
其他代碼自動生成,不贅述
(3)權限認證流程
SpringSecurity要求將身份認證信息存到GrantedAuthority對象列表中。代表了當前用戶的權限。 GrantedAuthority對象由AuthenticationManager插入到Authentication對象中,然后在做出授權決策 時由AccessDecisionManager實例讀取。 GrantedAuthority 接口只有一個方法
String getAuthority();
AuthorizationManager實例通過該方法來獲得GrantedAuthority。通過字符串的形式表示, GrantedAuthority可以很容易地被大多數(shù)AuthorizationManager實現(xiàn)讀取。如果GrantedAuthority不 能精確地表示為String,則GrantedAuthorization被認為是復雜的,getAuthority()必須返回null
直接在登錄時查詢用戶的權限,并放在我們自定義的實現(xiàn)了UserDetail的接口類中,用來表示登錄用戶的全部信息;
(4)在MyUserDetailsService中實現(xiàn)用戶權限的賦值
在MySysUserDetails類中加入兩個屬性,記錄從數(shù)據(jù)庫中查處的角色和權限信息
這里就簡單一點,不在做多表關聯(lián)查詢了。直接把zhangsan用戶設置為超級管理員,擁有所有權限;lisi用戶設置為普通管理員,擁有基本權限。
在MyUserDetailsService中實現(xiàn)用戶權限的賦值:
@Component
public class MyUserDetailsService implements UserDetailsService {/** UserDetailsService:提供查詢用戶功能,如根據(jù)用戶名查詢用戶,并返回UserDetails*UserDetails,SpringSecurity定義的類, 記錄用戶信息,如用戶名、密碼、權限等* */@Autowiredprivate SysUserMapper sysUserMapper;@Autowiredprivate SysRoleMapper sysRoleMapper;@Autowiredprivate SysPermissionsMapper sysPermissionsMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//根據(jù)用戶名從數(shù)據(jù)庫中查詢用戶SysUser sysUser = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(username != null, SysUser::getUsername, username));if (sysUser==null){throw new UsernameNotFoundException("用戶不存在");}MySysUserDetails mySysUserDetails=new MySysUserDetails(sysUser);if ("zhangsan".equals(username)){//zhangsan用戶是超級管理員,擁有一切權限SysRole sysRole = sysRoleMapper.selectOne(new LambdaQueryWrapper<SysRole>().eq(SysRole::getRoleName, "超級管理員"));Set<SysRole> roles=new HashSet<>();roles.add(sysRole);mySysUserDetails.setRoles(roles);SysPermissions sysPermissions = sysPermissionsMapper.selectById(1);Set<String> permissions=new HashSet<>();permissions.add(sysPermissions.getPermissionsName());mySysUserDetails.setPermissions(permissions);}if ("lisi".equals(username)){
//lisi用戶是普通管理員,擁有基本權限SysRole sysRole = sysRoleMapper.selectOne(new LambdaQueryWrapper<SysRole>().eq(SysRole::getRoleName, "普通管理員"));Set<SysRole> roles=new HashSet<>();roles.add(sysRole);mySysUserDetails.setRoles(roles);SysPermissions sysPermissions = sysPermissionsMapper.selectById(2);Set<String> permissions=new HashSet<>();permissions.add(sysPermissions.getPermissionsName());mySysUserDetails.setPermissions(permissions);}return mySysUserDetails;}
}
(5)MySysUserDetails中完成角色和權限的賦值
private Set<SysRole> roles;// 權限信息private Set<String> permissions;// 用戶擁有的權限集合,我這里先設置為null,將來會再更改的@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {System.err.println("進入權限的獲取方法");List<GrantedAuthority> authorities = new ArrayList<>(); // 授權信息列表// 將角色名稱添加到授權信息列表中roles.forEach(role->authorities.add(new SimpleGrantedAuthority(role.getRoleName())));// 將權限名稱添加到授權信息列表中permissions.forEach(permission->authorities.add(new SimpleGrantedAuthority(permission)));return authorities; // 返回授權信息列表}
用戶認證之后,會去存儲用戶對應的權限,并且給資源設置對應的權限,SpringSecurity支持兩種粒度 的權限:
1、基于請求的:在配置文件中配置路徑,可以使用**的通配符
2、基于方法的:在方法上使用注解實現(xiàn)
角色配置:在UserDetails接口中存在相關的權限和角色管理,只不過我們在實現(xiàn)這個接口的時候,將這些都設置為了null?,F(xiàn)在我們只需要將這些信息實現(xiàn)即可
(6)基于請求
還是在SecurityFilter過濾器中實現(xiàn)請求地址的權限校驗
httpSecurity.authorizeHttpRequests(it->
//hello地址只有超級管理員角色才能訪問
it.requestMatchers("/hello").hasRole("超級管理員")
//hello2地址只有"擁有所有權限"的權限才能訪問
.requestMatchers("hello2").hasAuthority("擁有所有權限").requestMatchers("/login","sys-user/login").permitAll() //設置登錄路徑所有人都可以訪問.anyRequest().authenticated() //其他路徑都要進行攔截);
使用sili進行登錄時,訪問hello2接口顯示權限不夠:
使用zhangsan進行登錄時,訪問hello2接口可以訪問到:
(7)基于方法
基于方法的權限認證要在SecurityConfig類上加上@EnableMethodSecurity注解,表示開啟了方法權限的使用;
常用的有四個注解:
@PreAuthorize
@PostAuthorize
@PreFilter
@PostFilter
/*測試@PreAuthorize注解
* 作用:使用在類或方法上,擁有指定的權限才能訪問(在方法運行前進行校驗)
* String類型的參數(shù):語法是Spring的EL表達式
* 有權限:test3權限
* hasRole:會去匹配authorities,但是會在hasRole的參數(shù)前加上一個ROLE_前綴,
* 所以在定義權限的時候需要加上ROLE_前綴
* role和authorities的關系是:role是一種復雜的寫法,有ROLE_前綴,authorities是role的簡化寫法
* 如果使用
* hasAnyRole:則匹配的權限是在authorities加上前綴ROLE_
* 推薦使用
* hasAnyAuthority:匹配authorities,但是不用在authorities的參數(shù)前加上ROLE_前綴
* */
@PreAuthorize("hasAnyAuthority('擁有所有權限')")
@ResponseBody
@GetMapping("/test3")
public String test3(){System.out.println("一個請求");return "一個test3請求";
}/*@PostAuthorize:在方法返回時進行校驗??梢赃€是校驗權限、或者校驗一些其他的東西(接下來我們校驗返回值的長度)
*返回結果的長度大于3、則認為是合法的
returnObject:固定寫法,代指返回對象
* */
@ResponseBody
@PostAuthorize("returnObject.length()>4")
@GetMapping("/test4")
public String test4(){System.out.println("一個test4請求");return "小張自傲張最終";
}/*
* @PreFilter:過濾符合條件的數(shù)據(jù)進入到接口
* */@PostFilter("filterObject.length()>3")@ResponseBody@GetMapping("/test5")public String test5(){System.out.println("一個test4請求");List<String> list = new ArrayList<>();list.add("張三");list.add("王麻子");list.add("狗叫什么");return "一個test5請求";}/*
* @PreFilter:過濾符合條件的數(shù)據(jù)返回,數(shù)據(jù)必須是Collection、map、Array【數(shù)組】
* */
@PreFilter("filterObject.length()>5")
@ResponseBody
@PostMapping("/test6")
public List<String> test6(@RequestBody List<String> list){return list;
}
需要注意的是這些方法不僅僅局限在權限的校驗,還能對返回的結果做一定的操作;
最需要注意的就是@PreFilter注解,它要求前端傳遞的參數(shù)一定是數(shù)組或集合;
基于方法鑒權 在SpringSecurity6版本中@EnableGlobalMethodSecurity被棄用,取而代之的是 @EnableMethodSecurity。默認情況下,會激活pre-post注解,并在內部使用 AuthorizationManager。
新老API區(qū)別 此@EnableMethodSecurity替代了@EnableGlobalMethodSecurity。提供了以下改進:
- 使用簡化的AuthorizationManager。
- 支持直接基于bean的配置,而不需要擴展GlobalMethodSecurityConfiguration
- 使用Spring AOP構建,刪除抽象并允許您使用Spring AOP構建塊進行自定義
- 檢查是否存在沖突的注釋,以確保明確的安全配置
- 符合JSR-250
- 默認情況下啟用@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter
【3】總結
(1)登錄校驗(Authentication):
1-用戶提交用戶名和密碼進行登錄。
2-Spring Security會攔截登錄請求,并將用戶名和密碼與存儲在系統(tǒng)中的憑據(jù)(如數(shù)據(jù)庫或LDAP)進行比對。
3-如果用戶名和密碼匹配,則認為用戶通過了身份驗證,可以繼續(xù)訪問受限資源。
4-認證成功后,Spring Security會創(chuàng)建一個包含用戶信息和權限的安全上下文(Security Context)。
(2)權限認證(Authorization):
1-一旦用戶通過了身份驗證,Spring Security就會開始進行權限認證。
2-針對每個受限資源或操作,可以配置相應的權限要求,例如需要哪些角色或權限才能訪問。
3-Spring Security會根據(jù)配置的權限要求,檢查當前用戶所擁有的角色和權限,判斷是否滿足訪問條件。
4-如果用戶擁有足夠的角色或權限,就被允許訪問資源;否則將被拒絕訪問,并可能重定向到登錄頁面或返回相應的錯誤信息。
Spring Security通過身份驗證(Authentication)來確認用戶的身份,并通過授權(Authorization)來控制用戶對受保護資源的訪問。這種分離的設計使得安全配置更加靈活,并且可以輕松地對不同的用戶和角色進行管理和控制。
【五】Springboot整合SpringSecurity入門
【1】pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.guor</groupId><artifactId>securityProject</artifactId><version>0.0.1-SNAPSHOT</version><name>securityProject</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.0.5</version></dependency><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--lombok用來簡化實體類--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
【2】application.properties
server.port=8111
#spring.security.user.name=root
#spring.security.user.password=root#mysql數(shù)據(jù)庫連接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/security?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
【3】SecurityConfig
SecurityConfig
package com.guor.security.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();String password = passwordEncoder.encode("123");auth.inMemoryAuthentication().withUser("zs").password(password).roles("admin");}@BeanPasswordEncoder password() {return new BCryptPasswordEncoder();}
}
UserSecurityConfig
package com.guor.security.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;import javax.sql.DataSource;@Configuration
public class UserSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserService userService;//注入數(shù)據(jù)源@Autowiredprivate DataSource dataSource;//配置對象@Beanpublic PersistentTokenRepository persistentTokenRepository() {JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();jdbcTokenRepository.setDataSource(dataSource);//jdbcTokenRepository.setCreateTableOnStartup(true);return jdbcTokenRepository;}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userService(userService).passwordEncoder(password());}@BeanPasswordEncoder password() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {//退出http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();//配置沒有權限訪問跳轉自定義頁面http.exceptionHandling().accessDeniedPage("/unauth.html");http.formLogin() //自定義自己編寫的登錄頁面.loginPage("/on.html") //登錄頁面設置.loginProcessingUrl("/user/login") //登錄訪問路徑.defaultSuccessUrl("/success.html").permitAll() //登錄成功之后,跳轉路徑.failureUrl("/unauth.html").and().authorizeRequests().antMatchers("/","/test/hello","/user/login").permitAll() //設置哪些路徑可以直接訪問,不需要認證//當前登錄用戶,只有具有admins權限才可以訪問這個路徑//1 hasAuthority方法// .antMatchers("/test/index").hasAuthority("admins")//2 hasAnyAuthority方法// .antMatchers("/test/index").hasAnyAuthority("admins,manager")//3 hasRole方法 ROLE_sale.antMatchers("/test/index").hasRole("sale").anyRequest().authenticated().and().rememberMe().tokenRepository(persistentTokenRepository()).tokenValiditySeconds(60)//設置有效時長,單位秒.userDetailsService(userService);// .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());// .and().csrf().disable(); //關閉csrf防護}
}
【4】啟動類
【5】User實體類
package com.guor.security.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
public class User {private Integer id;private String username;private String password;
}
【6】UserService服務層
package com.guor.security.service;import com.guor.security.entity.User;
import com.guor.security.mapper.UsersMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//調用usersMapper方法,根據(jù)用戶名查詢數(shù)據(jù)庫QueryWrapper<Users> wrapper = new QueryWrapper();// where username=?wrapper.eq("username",username);User user = userMapper.selectOne(wrapper);//判斷if(user == null) {//數(shù)據(jù)庫沒有用戶名,認證失敗throw new UsernameNotFoundException("用戶名不存在!");}List<GrantedAuthority> auths =AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");//從查詢數(shù)據(jù)庫返回users對象,得到用戶名和密碼,返回return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),auths);}
}
【7】UserMapper
package com.guor.security.mapper;import com.guor.security.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;@Repository
public interface UserMapper extends BaseMapper<User> {
}
【8】UserController控制層
package com.guor.security.controller;import com.guor.security.entity.User;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;
import java.util.List;@RestController
@RequestMapping("/test")
public class UserController {@GetMapping("hello")public String hello() {return "hello security";}@GetMapping("index")public String index() {return "hello index";}@GetMapping("update")//@Secured({"ROLE_sale","ROLE_manager"})//@PreAuthorize("hasAnyAuthority('admins')")@PostAuthorize("hasAnyAuthority('admins')")public String update() {System.out.println("update......");return "hello update";}@GetMapping("getAll")@PostAuthorize("hasAnyAuthority('admins')")@PostFilter("filterObject.username == 'admin1'")public List<Users> getAllUser(){ArrayList<Users> list = new ArrayList<>();list.add(new Users(11,"admin1","6666"));list.add(new Users(21,"admin2","888"));System.out.println(list);return list;}
}
【六】微服務認證與授權實現(xiàn)思路
(1)如果是基于Session,那么SpringSecurity會對cookie里的SessionID進行解析,找到服務器存儲的Session信息,然后判斷當前用戶是否復合請求的要求
(2)如果是token,則是解析出token,然后將當前請求加入到SpringSecurity管理的權限信息中去
如果系統(tǒng)的模塊眾多,每個模塊都需要進行授權與認證,所以我們選擇基于token的形式進行授權與認證,用戶根據(jù)用戶名密碼認證成功,然后獲取當前用戶角色的一系列權限值,并以用戶名為key,權限列表為value的形式存入redis緩存中,根據(jù)用戶名相關信息生成token返回,瀏覽器將token記錄到cookie中,每次調用api接口都默認將token攜帶到header請求頭中,SpringSecurity解析header頭獲取token信息,解析token獲取當前用戶名,根據(jù)用戶名就可以從redis中獲取權限列表,這樣SpringSecurity就能夠判斷當前請求是否有權限訪問。
【七】微服務代碼實例
【1】父工程pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><modules><module>common</module><module>infrastructure</module><module>service</module></modules><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.atguigu</groupId><artifactId>acl_parent</artifactId><packaging>pom</packaging><version>0.0.1-SNAPSHOT</version><name>acl_parent</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version><mybatis-plus.version>3.0.5</mybatis-plus.version><velocity.version>2.0</velocity.version><swagger.version>2.7.0</swagger.version><jwt.version>0.7.0</jwt.version><fastjson.version>1.2.28</fastjson.version><gson.version>2.8.2</gson.version><json.version>20170516</json.version><cloud-alibaba.version>0.2.2.RELEASE</cloud-alibaba.version></properties><dependencyManagement><dependencies><!--Spring Cloud--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Hoxton.RELEASE</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${cloud-alibaba.version}</version><type>pom</type><scope>import</scope></dependency><!--mybatis-plus 持久層--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus.version}</version></dependency><!-- velocity 模板引擎, Mybatis Plus 代碼生成器需要 --><dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>${velocity.version}</version></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>${gson.version}</version></dependency><!--swagger--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>${swagger.version}</version></dependency><!--swagger ui--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>${swagger.version}</version></dependency><!-- JWT --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>${jwt.version}</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>${fastjson.version}</version></dependency><dependency><groupId>org.json</groupId><artifactId>json</artifactId><version>${json.version}</version></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
【2】common模塊
common模塊pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>acl_parent</artifactId><groupId>com.atguigu</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>common</artifactId><packaging>pom</packaging><modules><module>service_base</module><module>spring_security</module></modules><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><scope>provided </scope></dependency><!--mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><scope>provided </scope></dependency><!--lombok用來簡化實體類:需要安裝lombok插件--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided </scope></dependency><!--swagger--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><scope>provided </scope></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><scope>provided </scope></dependency><!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- spring2.X集成redis所需common-pool2--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.6.0</version></dependency></dependencies>
</project>
【3】common模塊->SpringSecurity子模塊
(1)核心配置類
SpringSecurity的核心配置就是繼承WebSecurityConfigurerAdapter并注解@EnableWebSecurity的配置。這個配置指明了用戶名密碼的處理方式、請求路徑、登錄登出控制等和安全相關的配置。
package com.atguigu.security.config;import com.atguigu.security.filter.TokenAuthFilter;
import com.atguigu.security.filter.TokenLoginFilter;
import com.atguigu.security.security.DefaultPasswordEncoder;
import com.atguigu.security.security.TokenLogoutHandler;
import com.atguigu.security.security.TokenManager;
import com.atguigu.security.security.UnauthEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {private TokenManager tokenManager;private RedisTemplate redisTemplate;private DefaultPasswordEncoder defaultPasswordEncoder;private UserDetailsService userDetailsService;@Autowiredpublic TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,TokenManager tokenManager, RedisTemplate redisTemplate) {this.userDetailsService = userDetailsService;this.defaultPasswordEncoder = defaultPasswordEncoder;this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}/*** 配置設置* @param http* @throws Exception*///設置退出的地址和token,redis操作地址@Overrideprotected void configure(HttpSecurity http) throws Exception {http.exceptionHandling().authenticationEntryPoint(new UnauthEntryPoint())//沒有權限訪問.and().csrf().disable().authorizeRequests().anyRequest().authenticated().and().logout().logoutUrl("/admin/acl/index/logout")//退出路徑.addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and().addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate)).addFilter(new TokenAuthFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();}//調用userDetailsService和密碼處理@Overridepublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);}//不進行認證的路徑,可以直接訪問@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/api/**");}
}
(2)實體類
package com.atguigu.security.entity;import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;import java.util.ArrayList;
import java.util.Collection;
import java.util.List;@Data
public class SecurityUser implements UserDetails {//當前登錄用戶private transient User currentUserInfo;//當前權限private List<String> permissionValueList;public SecurityUser() {}public SecurityUser(User user) {if (user != null) {this.currentUserInfo = user;}}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {Collection<GrantedAuthority> authorities = new ArrayList<>();for(String permissionValue : permissionValueList) {if(StringUtils.isEmpty(permissionValue)) continue;SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);authorities.add(authority);}return authorities;}
}
package com.atguigu.security.entity;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;import java.io.Serializable;@Data
@ApiModel(description = "用戶實體類")
public class User implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "微信openid")private String username;@ApiModelProperty(value = "密碼")private String password;@ApiModelProperty(value = "昵稱")private String nickName;@ApiModelProperty(value = "用戶頭像")private String salt;@ApiModelProperty(value = "用戶簽名")private String token;}
(3)過濾器
package com.atguigu.security.filter;import com.atguigu.security.security.TokenManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;public class TokenAuthFilter extends BasicAuthenticationFilter {private TokenManager tokenManager;private RedisTemplate redisTemplate;public TokenAuthFilter(AuthenticationManager authenticationManager,TokenManager tokenManager,RedisTemplate redisTemplate) {super(authenticationManager);this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {//獲取當前認證成功用戶權限信息UsernamePasswordAuthenticationToken authRequest = getAuthentication(request);//判斷如果有權限信息,放到權限上下文中if(authRequest != null) {SecurityContextHolder.getContext().setAuthentication(authRequest);}chain.doFilter(request,response);}private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {//從header獲取tokenString token = request.getHeader("token");if(token != null) {//從token獲取用戶名String username = tokenManager.getUserInfoFromToken(token);//從redis獲取對應權限列表List<String> permissionValueList = (List<String>)redisTemplate.opsForValue().get(username);Collection<GrantedAuthority> authority = new ArrayList<>();for(String permissionValue : permissionValueList) {SimpleGrantedAuthority auth = new SimpleGrantedAuthority(permissionValue);authority.add(auth);}return new UsernamePasswordAuthenticationToken(username,token,authority);}return null;}}
package com.atguigu.security.filter;import com.atguigu.security.entity.SecurityUser;
import com.atguigu.security.entity.User;
import com.atguigu.security.security.TokenManager;
import com.atguigu.utils.utils.R;
import com.atguigu.utils.utils.ResponseUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {private TokenManager tokenManager;private RedisTemplate redisTemplate;private AuthenticationManager authenticationManager;public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {this.authenticationManager = authenticationManager;this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;this.setPostOnly(false);this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));}//1 獲取表單提交用戶名和密碼@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException {//獲取表單提交數(shù)據(jù)try {User user = new ObjectMapper().readValue(request.getInputStream(), User.class);return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword(),new ArrayList<>()));} catch (IOException e) {e.printStackTrace();throw new RuntimeException();}}//2 認證成功調用的方法@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult)throws IOException, ServletException {//認證成功,得到認證成功之后用戶信息SecurityUser user = (SecurityUser)authResult.getPrincipal();//根據(jù)用戶名生成tokenString token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());//把用戶名稱和用戶權限列表放到redisredisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(),user.getPermissionValueList());//返回tokenResponseUtil.out(response, R.ok().data("token",token));}//3 認證失敗調用的方法protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)throws IOException, ServletException {ResponseUtil.out(response, R.error());}
}
(4)security
package com.atguigu.security.security;import com.atguigu.utils.utils.MD5;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;@Component
public class DefaultPasswordEncoder implements PasswordEncoder {public DefaultPasswordEncoder() {this(-1);}public DefaultPasswordEncoder(int strength) {}//進行MD5加密@Overridepublic String encode(CharSequence charSequence) {return MD5.encrypt(charSequence.toString());}//進行密碼比對@Overridepublic boolean matches(CharSequence charSequence, String encodedPassword) {return encodedPassword.equals(MD5.encrypt(charSequence.toString()));}
}
package com.atguigu.security.security;import com.atguigu.utils.utils.R;
import com.atguigu.utils.utils.ResponseUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//退出處理器
public class TokenLogoutHandler implements LogoutHandler {private TokenManager tokenManager;private RedisTemplate redisTemplate;public TokenLogoutHandler(TokenManager tokenManager,RedisTemplate redisTemplate) {this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}@Overridepublic void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {//1 從header里面獲取token//2 token不為空,移除token,從redis刪除tokenString token = request.getHeader("token");if(token != null) {//移除tokenManager.removeToken(token);//從token獲取用戶名String username = tokenManager.getUserInfoFromToken(token);redisTemplate.delete(username);}ResponseUtil.out(response, R.ok());}
}
package com.atguigu.security.security;import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;import java.util.Date;@Component
public class TokenManager {//token有效時長private long tokenEcpiration = 24*60*60*1000;//編碼秘鑰private String tokenSignKey = "123456";//1 使用jwt根據(jù)用戶名生成tokenpublic String createToken(String username) {String token = Jwts.builder().setSubject(username).setExpiration(new Date(System.currentTimeMillis()+tokenEcpiration)).signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();return token;}//2 根據(jù)token字符串得到用戶信息public String getUserInfoFromToken(String token) {String userinfo = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();return userinfo;}//3 刪除tokenpublic void removeToken(String token) { }
}
package com.atguigu.security.security;import com.atguigu.utils.utils.R;
import com.atguigu.utils.utils.ResponseUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class UnauthEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {ResponseUtil.out(httpServletResponse, R.error());}
}
【4】common模塊->service_base
(1)RedisConfig
package com.atguigu.utils;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;@EnableCaching //開啟緩存
@Configuration //配置類
public class RedisConfig extends CachingConfigurerSupport {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();RedisSerializer<String> redisSerializer = new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);template.setConnectionFactory(factory);//key序列化方式template.setKeySerializer(redisSerializer);//value序列化template.setValueSerializer(jackson2JsonRedisSerializer);//value hashmap序列化template.setHashValueSerializer(jackson2JsonRedisSerializer);return template;}@Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {RedisSerializer<String> redisSerializer = new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);//解決查詢緩存轉換異常的問題ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);// 配置序列化(解決亂碼的問題),過期時間600秒RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).disableCachingNullValues();RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();return cacheManager;}
}
(2)SwaggerConfig
package com.atguigu.utils;import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;@Configuration//配置類
@EnableSwagger2 //swagger注解
public class SwaggerConfig {@Beanpublic Docket webApiConfig(){return new Docket(DocumentationType.SWAGGER_2).groupName("webApi").apiInfo(webApiInfo()).select()//.paths(Predicates.not(PathSelectors.regex("/admin/.*"))).paths(Predicates.not(PathSelectors.regex("/error.*"))).build();}private ApiInfo webApiInfo(){return new ApiInfoBuilder().title("網(wǎng)站-課程中心API文檔").description("本文檔描述了課程中心微服務接口定義").version("1.0").contact(new Contact("java", "http://atguigu.com", "1123@qq.com")).build();}
}
(3)工具類
package com.atguigu.utils.utils;import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;public final class MD5 {public static String encrypt(String strSrc) {try {char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8','9', 'a', 'b', 'c', 'd', 'e', 'f' };byte[] bytes = strSrc.getBytes();MessageDigest md = MessageDigest.getInstance("MD5");md.update(bytes);bytes = md.digest();int j = bytes.length;char[] chars = new char[j * 2];int k = 0;for (int i = 0; i < bytes.length; i++) {byte b = bytes[i];chars[k++] = hexChars[b >>> 4 & 0xf];chars[k++] = hexChars[b & 0xf];}return new String(chars);} catch (NoSuchAlgorithmException e) {e.printStackTrace();throw new RuntimeException("MD5加密出錯!!+" + e);}}public static void main(String[] args) {System.out.println(MD5.encrypt("111111"));}
}
package com.atguigu.utils.utils;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class ResponseUtil {public static void out(HttpServletResponse response, R r) {ObjectMapper mapper = new ObjectMapper();response.setStatus(HttpStatus.OK.value());response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);try {mapper.writeValue(response.getWriter(), r);} catch (IOException e) {e.printStackTrace();}}
}
package com.atguigu.utils.utils;import lombok.Data;
import java.util.HashMap;
import java.util.Map;//統(tǒng)一返回結果的類
@Data
public class R {private Boolean success;private Integer code;private String message;private Map<String, Object> data = new HashMap<String, Object>();//把構造方法私有private R() {}//成功靜態(tài)方法public static R ok() {R r = new R();r.setSuccess(true);r.setCode(20000);r.setMessage("成功");return r;}//失敗靜態(tài)方法public static R error() {R r = new R();r.setSuccess(false);r.setCode(20001);r.setMessage("失敗");return r;}public R success(Boolean success){this.setSuccess(success);return this;}public R message(String message){this.setMessage(message);return this;}public R code(Integer code){this.setCode(code);return this;}public R data(String key, Object value){this.data.put(key, value);return this;}public R data(Map<String, Object> map){this.setData(map);return this;}
}
【5】gateway模塊
(1)pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>infrastructure</artifactId><groupId>com.atguigu</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>api_gateway</artifactId><dependencies><dependency><groupId>com.atguigu</groupId><artifactId>service_base</artifactId><version>0.0.1-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!--gson--><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId></dependency><!--服務調用--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency></dependencies>
</project>
(2)application.properties
# 端口號
server.port=8222
# 服務名
spring.application.name=service-gateway
# nacos服務地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# 使用服務發(fā)現(xiàn)路由
spring.cloud.gateway.discovery.locator.enabled=true# 配置路由規(guī)則
spring.cloud.gateway.routes[0].id=service-acl
# 設置路由uri lb://注冊服務名稱
spring.cloud.gateway.routes[0].uri=lb://service-acl
# 具體路徑規(guī)則
spring.cloud.gateway.routes[0].predicates= Path=/*/acl/**
(3)解決跨域
package com.atguigu.gateway.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;@Configuration
public class CorsConfig {//解決跨域@Beanpublic CorsWebFilter corsWebFilter() {CorsConfiguration config = new CorsConfiguration();config.addAllowedMethod("*");config.addAllowedOrigin("*");config.addAllowedHeader("*");UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());source.registerCorsConfiguration("/**",config);return new CorsWebFilter(source);}
}