OAuth2保護微服務 案例實戰
文章目錄
前言
不得不吐槽一下,一些經典書籍的案例 真的已經過時了,首先依賴包不適用,案例不正確,代碼欠缺,而且很多關鍵位置的代碼並沒有貼出來,這對於許多不僅僅是看書,也想動手敲一敲案例的學習者來說很不友好!!一度都想跳過這一章,記錄使用使用OAuth2+SpringSecurity+SpringCloud時遇到的一系列坑。
參照《Spring微服務實戰》
1. 依賴包問題
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
注意起步依賴 spring-cloud-starter···,原書中的spring-cloud-···真的讓我吐了,一直懷疑可能是因爲我版本的問題,查了很久之後發現,確實是包的問題。
工程結構如圖:
2.服務引導類Application
@SpringBootApplication
@RestController
@EnableResourceServer
@EnableAuthorizationServer //OAuth2服務
public class OAuth2Application {
@GetMapping(value = "/auth/user",produces = "application/json")
public Map<String,Object> user(OAuth2Authentication user){
Map<String,Object> userInfo=new HashMap<>();
userInfo.put("user",user.getUserAuthentication().getPrincipal());
userInfo.put("authorities", AuthorityUtils.authorityListToSet(user.getUserAuthentication().getAuthorities()));
return userInfo;
}
public static void main(String[] args){
SpringApplication.run(OAuth2Application.class,args);
}
}
OAuth2服務的引導類,除了通過註解聲明該服務是OAuth2服務外,添加了一個 /user 路徑映射,當有客戶端服務訪問由OAuth2保護的服務時會調用這個端點。(說直白點就是,客戶端服務拿着授權令牌Authorization時,對這個Authorization進行解析,回返回對應的user和authorities)。
此類在原書中仍然是個坑,在指向驗證服務時,原書裏的userInfoUri:/auth/user,而原書裏的啓動類卻是RequestMapping("/user"),說是會映射到/auth/user路徑上去,然而我在試驗時,並不是這樣,而是要通過/user纔可以,這不是給自己添麻煩嗎?! (我猜測本書中可能有某個地方設置一個類似路徑prefix這樣的東西,因爲其實所有的請求路徑都是 ip+port/auth/···· 然而 它實際的編碼裏 並沒有添加/auth )
3.配置類OAuth2
@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory() //指定爲內存存儲 jdbc存儲或內存存儲
.withClient("orgFeign") //註冊的應用程序名稱
.secret("thisissecret") //密鑰
.authorizedGrantTypes( //授權類型
"password",
"client_credentials"
).scopes("webclient","mobileclient"); //作用域
//定義驗證服務註冊的客戶端應用程序
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
// .tokenKeyAccess("permitAll()") // auth/oauth/token_key是公開的
// .checkTokenAccess("permitAll()") // auth/oauth/check_token是公開的
// .allowFormAuthenticationForClients() //表單驗證(申請令牌)
.passwordEncoder(NoOpPasswordEncoder.getInstance());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
}
該類定義了哪些應用程序可以訪問由OAuth2保護的服務,重點關注覆寫的兩個configure方法。
第一個configure中,定義了哪些客戶端註冊到OAuth2中,withClient()和secret()是客戶端應用程序獲取OAuth2訪問令牌的名稱和密鑰,authorizedGrantTypes()爲授權類型列表 這裏指定爲密碼類型,scopes爲作用域,爲自定義的內容 可以用來做細粒度的授權規則。
第二個configure中,有三行註釋掉的代碼,其實這是我出現401時參照別人的解決方案,但實際無濟於事,我401的時候加了還是401,我解決正常運行後註釋掉也不影響它照常工作。
4. 配置類Security
與上一個配置類不同,上一個是配置OAuth2相關的,這個配置類是配置SpringSecurity的用戶id 密碼和角色。與單獨使用SpringSecurity進行權限控制資源保護的基本一樣。
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
@Bean
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser("jam").password("123456").roles("USER")
.and()
.withUser("yang").password("123456").roles("USER","ADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/oauth/token").permitAll();
}
}
同樣重點也是兩個configure方法,本文的例子中爲了方便選擇了一個棄用的密碼編碼器NoOpPasswordEncoder,官方已不推薦使用,第一個configure方法採用基於內存的方式註冊了兩個用戶jam和yang;第二個configure方法纔是解決了我之前出現401的關鍵步驟之一!!!原書中並沒有提到需放通 /oauth/token 這一路徑,實屬有點坑人。
以上就可以嘗試起通過OAuth2服務來獲取令牌以及驗證授權。
5. Postman測試
首先 是這裏的路徑問題 原書中是 ip:port/auth/oauth/token 同理,用加上auth之後仍然是不行的,加上前面Security配置類上我們放通的是 /oauth/token(只需要放通,背後映射做的處理由OAtuh2幫我們完成),所以正確的路徑如圖,需要選擇Authorization的Type爲Basic Auth,右邊有兩個參數要填寫,這裏的Username和Password分別對應OAuth2配置類的withClient()和secret(),
除了Authorization之外,還需要寫入對應的參數,這裏需要在body裏選擇表單數據 form-data,然後分別寫grant_type授權類型,scope作用域,username和password(註冊的用戶的用戶名密碼),前兩個參數參見 3.OAtuh2配置類,用戶名密碼是Security配置類上註冊的。這裏有兩個 註釋掉的參數 client_id 和client_secret 這是一些博客上解決訪問/auth/token報401的方案,但是我這裏行不通,所以我把它注掉了。
結果可以看到 access_token和token_type 此爲OAuth2頒發的令牌,而我們可以拿着這個令牌去OAuth2服務器驗證權限,還記得2.的引導類中的那個地址映射嗎?可以在HTTP頭部攜帶這個令牌去驗證。
至此,授權令牌的頒發與驗證就已經完成了。
6. 配置被保護的服務
// 本文以一個 OrgnizationApplication-9001爲例
6.1 yaml與引導類
server:
port: 9001
security:
oauth2:
resource:
userInfoUri: http://localhost:4396/auth/user
@SpringBootApplication
@EnableResourceServer //註解爲受OAuth2保護的資源
@EnableEurekaClient
public class OrganizationApplication9001 {
public static void main(String[] args){
SpringApplication.run(OrganizationApplication9001.class,args);
}
}
@EnableResourceServer 會強制執行一個Filter,會對所有到達改服務的請求執行Filter檢查傳入的HTTP首部是否存在OAuth2訪問令牌,然後通過回調yaml中的uri來查看令牌是否有效。
6.2 測試的Controller
@RestController
public class OrganizationController {
@GetMapping("/organization")
public String getMsg(){
return "This is organization 9001";
}
}
6.2 定義訪問規則
@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET,"/organization")
.hasRole("ADMIN")
.anyRequest()
.authenticated();
}
}
這裏定義了 /organization 資源路徑的規則,需要ADMIN角色,(本文中的兩個用戶jam爲user角色,而yang爲user和admin角色),所以可以通過分別向OAuth獲取這兩個用戶的access_token 然後訪問這個服務 加以驗證。
6.3 訪問被保護的服務
如圖,這個截圖例子測試的是jam用戶也即user角色的結果,會發現403拒絕訪問,達到了預期的效果,yang用戶即admin角色可以自行測試,是可以正常放通進行訪問的。