上一節在認證服務器里,將token 由uuid改造成了JWT,之前在網關上拿到令牌access_token后,需要去認證服務器校驗令牌,將令牌信息轉換為用戶信息。
現在有了jwt后,由於jwt是自包含的,已經包含了用戶的身份信息,所以在網關上不需要去認證服務器驗令牌了。
之前在網關上所做的這些去認證服務器驗令牌信息,轉換為用戶信息,去認證服務器做權限的判斷,這些其實SpringSecurity-OAuth都已經實現好了的。之前之所以手寫是為了理解SpringSecurity-OAuth內部的實現。
1,刪掉網關上filter包里的過濾器
2,在網關項目里加上依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
3,配置獲取jwt驗簽的key的uri
認證服務器生成Jwt的時候,是進行了簽名的,有一個簽名的key;解析Jwt,需要驗簽,也需要這個key值,所以需要告訴網關,去哪里獲取這個簽名的key。
在網關的配置文件里配置: (這個uri具體在org.springframework.security.oauth2.provider.endpoint.TokenKeyEndpoint里處理)
資源服務器啟動的時候,就會去認證服務器拿這個key,所以啟動網關前必須要保證認證服務器是啟動的。
security:
oauth2:
resource:
jwt:
key-uri: http://auth.nb.com:9090/oauth/token_key #獲取解析jwt,驗簽名key的路徑
client:
client-id: gateway #獲取驗簽key需要身份認證,這里是網關的clientId
client-secret: 123456 #獲取驗簽key需要身份認證,這里是網關的secret
4,網關作為一個資源服務器,配置其安全配置
/** * 作為一個資源服務器存在 */ @Configuration @EnableResourceServer public class GatewaySecurityConfig extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/token/**").permitAll() //放過/token開頭的請求,是在申請令牌 .anyRequest().authenticated(); } }
5,各個微服務的改造
之前各個微服務需要用到用戶信息的時候,在網關上,網關從認證服務器解析令牌,獲取到用戶信息后,是將用戶名以明文的方式放在了請求頭里,各個微服務從請求頭里獲取到明文的username參數,這樣是有安全問題的。
此時,各個微服務也需要解析jwt,獲取用戶信息。所以各個微服務就需要跟網關一樣,解析jwt,所以也需要從認證服務器獲取驗簽的key,故需要做和網關一樣的配置。
在訂單微服務,引入 SpringSecurity-OAuth依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

訂單微服務也作為資源服務器存在,所以需要給訂單微服務打上資源服務器的標記 @EnableResourceServer
在訂單微服務,獲取用戶信息 也要用注解 @AuthenticationPrincipal String username 。
實驗:
1,先啟動 認證服務器,因為各個微服務啟動的時候就會去認證服務器拿jwt驗簽的key。
2.啟動網關。
發現報如下異常 Caused by: org.springframework.web.client.HttpClientErrorException$NotFound: 404 null

這個異常的意思是,在拿jwt驗簽key的時候,找不到該服務,為什么呢?在認證服務器上,配置jwt tokenStore的時候,需要做一下特殊處理:
需要將 JwtAccessTokenConverter 暴露為Spring的Bean,而且必須為public的。
再次重啟認證服務器,重啟網關,正常啟動。
postman調用網關,獲取令牌 http://localhost:9070/token/oauth/token
客戶端信息表
通過網關,拿access_token去訂單服務創建訂單 http://localhost:9070/order/orders
處理辦法:
1,在oauth_client_details表里的orderApp應用的resource_ids 字段里,配置上網關的resourceId,前提是網關代碼里也配置了resourceId。這樣安全性更高,只是需要維護這些資源服務器id。
調用成功
2,如果對安全性要求不高,可以把oauth_client_details表里 orderApp的resource_ids 字段設置為空,這樣給orderApp客戶端發出的jwt就可以訪問任何的微服務了。
在微服務之間傳遞jwt令牌信息
比如訂單微服務調用了加個微服務,那么訂單微服務怎么把用戶信息傳遞給價格微服務呢?之前的做法是,將用戶信息放在請求頭里,進行傳遞。
其實SpringSecurity-OAuth已經替你把這些事情做好了。可以在微服務之間傳遞jwt令牌,傳過去的jwt會被解析為用戶信息。
下面改造訂單微服務和價格微服務,在他們之間傳遞用戶信息。
價格微服務也作為資源服務器存在
@SpringBootApplication @EnableResourceServer//作為資源服務器 public class NbPriceApiApplication { public static void main(String[] args) { SpringApplication.run(NbPriceApiApplication.class, args); System.err.println("============= Price Api 啟動完成 ============"); } }
價格微服務也加入SpringCloud和SpringSecurity-OAuth的依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency>
dependencyManagement里:
<!--spring cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
價格微服務獲取用戶信息 : @AuthenticationPrincipal String username
能在微服務之間自動傳遞jwt令牌信息, 主角就是:OAuth2RestTemplate,它能從請求上下文中拿到jwt 令牌,然后將其放入請求頭,在其他微服務里,就可以通過@AuthenticationPrincipal 注解來獲得用戶信息了。
在訂單微服務里配置OAuth2RestTemplate
@Configuration @SpringBootApplication @EnableResourceServer//作為資源服務器存在 public class NbOrderApiApplication { //聲名OAuth2RestTemplate //會從請求的上下文里拿到令牌,放到請求頭里,發出去。需要兩個參數,springboot會自動出入進來 @Bean public OAuth2RestTemplate oAuth2RestTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context){ return new OAuth2RestTemplate(resource,context); } public static void main(String[] args) { SpringApplication.run(NbOrderApiApplication.class, args); System.err.println("============= Order Api 啟動完成 ============"); } }
隨便用某個客戶端生成一個jwt令牌
然后通過網關調用創建訂單服務器
查看訂單服務和價格服務里,是否打印了用戶名
訂單服務日志,網關已經把jwt傳遞給了訂單服務,而且訂單服務把jwt解析成了用戶信息
查看價格微服務,看訂單服務是否把jwt傳給了價格服務
至此,在網關上已經實現了由SpringSecurity-OAuth替我們實現各種認證啊、授權的過濾器。前面系列文章說的都是自己實現這些過濾器,自己實現是為了了解其中的原理。
目前的架構,前面文章說的兩個問題:
1,在網關上再去認證服務器驗令牌,認證服務器壓力變大
解決:token 信息是 jwt,已經自包含身份信息。不用再去認證服務器驗令牌。減少了一次請求,網關、認證服務器的壓力減小了。
2,明文在微服務之間的請求頭里傳遞用戶信息
解決:JWT是自包含身份信息的,用OAuth2RestTemplate發請求,SpringSecurity-OAuth會自動從請求上下文拿到jwt信息,放進請求頭,下游微服務拿到后會解析jwt。
到目前來說,各個微服務(包括網關),都是資源服務器,需要在它們配置類上打上 @EnableResourceServer 注解,使其成為資源服務器。而且各個資源服務器需要引入SpringCloud的依賴以及 spring-cloud-starter-oauth2 依賴。
目前的架構圖是這樣的
代碼:https://github.com/lhy1234/springcloud-security/tree/chapt-6-2-jwt02
歡迎關注個人公眾號一起交流學習: