一、JWT:JSON Web Token ( JWT)是一種開放的標准( RFC 7519 ), JWT 定義了一種緊湊且自包含的標准,該標准旨在將各個主體的信息包裝為 JSON 對象。主體信息是通過數字簽名進行和驗證的。常使用 HMAC算法或 RSA (公鑰/私鑰 非對稱性 密〉算法對 JWT 進行簽名,安全性很高。
1)特點:
(1)緊湊型( compact):由於是加密后的字符串,JWT數據體積非常小,可通過POST請求參數或 HTTP 請求頭發送。另外,數據體積小意味着傳輸速度很快。
(2)自包含(self-contained):JWT 包含了主體的所有信息,所以避免了每個請求都需要Uaa 服務驗證身份,降低了服務器的負載。
2)結構:包含3個部分,通過".(點)"分割。
(1)Header (頭)。
(2)Payload (有效載荷〉。
(3)Signature (簽名)。
大概樣子如下:
解析后的樣子:
二、JWT的應用場景。
1)認證:這是使用 JWT 最常見的場景。一旦用戶登錄成功獲取 JWT 后,后續的每個請求將攜帶該 JWT 。該 JWT 包含了用戶信息、權限點等信息,根據 JWT 含的信息,資源服務可以控制該 JWT 可以訪問的資源范圍。因為 JWT 開銷很小,並且能夠在不同的域中使用,單點登錄是一個廣泛使用 JWT 的場景。
2)信息交換:JWT 是在各方之間安全傳輸信息的一種方式,使用簽名加密,安全性很高。另外,當使用 Header Payload算簽名時,還可以驗證內容是否被篡改。
三、JWT如何使用。
客戶端通過提供用戶名、密碼向服務器請求獲取JWT ,服務器判斷用戶名和密碼正確無誤之后,將用戶信息和權限點經過加密以JWT形式返回給客戶端。在以后的每次請求中獲取到該 JWT 客戶端都需要攜帶該JWT這樣做的好處就是以后的請求都不需要通過 認證服務來判斷該請求的用戶以及該用戶的權限。在微服務系統中,可以利用 JWT 實現單點登錄。
四、具體的實現過程。(基本和上一章(Spring-Cloud之OAuth2開放授權-11)的代碼一樣,我這里只說核心修改的部分)
1、認證服務器修改部分
package com.cetc.config; import com.zaxxer.hikari.HikariDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory; @Configuration @EnableAuthorizationServer public class AuthServerConfiguration extends AuthorizationServerConfigurerAdapter{ @Autowired private AuthDetailsService authDetailsService; @Autowired private AuthenticationManager authenticationManager; @Autowired private ClientDetailsService clientDetailsService; @Bean public ClientDetailsService clientDetailsService(HikariDataSource dataSource) { //使用數據庫的配置方式 return new JdbcClientDetailsService(dataSource); } @Bean public TokenStore tokenStore() { //token也使用數據的方式,后面會將JWT的使用方式 return new JwtTokenStore(jwtAccessTokenConverter()); } @Bean protected JwtAccessTokenConverter jwtAccessTokenConverter() { ClassPathResource resource = new ClassPathResource("jwt/jwt.jks"); KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resource, "auth_jwt".toCharArray()); JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); jwtAccessTokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair("oauth2-jwt")); return jwtAccessTokenConverter; } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security //token獲取方式 .tokenKeyAccess("permitAll()") //檢測加入權限 .checkTokenAccess("isAuthenticated()") //允許表單認證 .allowFormAuthenticationForClients(); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //這里就是具體的授權管理過程了 clients.withClientDetails(clientDetailsService); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints //這里使用的認證方式為security配置方式 .authenticationManager(authenticationManager) //提供get和post的認證方式 .allowedTokenEndpointRequestMethods(HttpMethod.POST, HttpMethod.GET) //這里一定要配置userDetailsService,不然刷新token會出錯,refresh_token .userDetailsService(authDetailsService) .tokenStore(tokenStore()).tokenEnhancer(jwtAccessTokenConverter()) //自定義認證頁面 .pathMapping("/oauth/confirm_access", "/oauth/confirm_access"); } }
2、資源服務器
package com.cetc.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import org.springframework.util.FileCopyUtils; import java.io.IOException; import java.util.Arrays; @Configuration @EnableResourceServer public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter{ @Override public void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .anyRequest().authenticated(); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() throws IOException { JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); ClassPathResource resource = new ClassPathResource("jwt/jwt.cert"); jwtAccessTokenConverter.setVerifierKey(new String(FileCopyUtils.copyToByteArray(resource.getInputStream()))); return jwtAccessTokenConverter; } @Bean public TokenStore tokenStore() throws IOException { return new JwtTokenStore(jwtAccessTokenConverter()); } @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.tokenStore(tokenStore()); } }
3、第三方或者SSO客戶端,不做修改。因為,JWT令牌是認證服務器給出的。解析JWT令牌的為資源服務器,所以SSO客戶端只需要按原來的方式進行調用就可以了。
五、jks文件生成,上面使用jks文件為文件token密鑰,所以我們在使用的時候需要自己加入。
1)生成jks文件
keytool -genkeypair -alias oauth2-jwt -keyalg RSA -keypass auth_jwt -storepass auth_jwt -keystore jwt.jks
2)獲取公鑰,這里最好在linux服務器上面進行,本地安裝OpenSSL過於麻煩
keytool -list -rfc --keystore jwt.jks | openssl x509 -inform pem -pubkey
賦值公鑰到jwt.cert文件中
3)按照配置的指定路徑放入jwt.jks和jwt.cert
六、測試資源服務器訪問,啟動Eureka-Server、Eureka-Client、Auth-Server-Jwt、Auth-Resource-Jwt端口為8670、8673、8697、8698.
1)添加客戶端到數據庫
2)獲取令牌:
(1)獲取授權碼
oauth/authorize?response_type=code&client_id=&redirect_uri=
(2)獲取令牌
oauth/token?client_id=&client_secret=&grant_type=authorization_code&redirect_uri=&code=
3)攜帶令牌訪問資源服務器
七、JWT的基本使用就差不多這個樣子了,JWT的目的是在較少認證服務器的訪問。那么這個也存在問題就是如果用戶權限修改或者其他部分修改,那么在令牌的使用就不是最新的,這里就會導致權限錯誤問題。當然這個問題可以通過配置網關,在網關處緩存,如果存在修改這清楚緩存,要求重新登錄。
八、本編源碼:https://github.com/lilin409546297/spring-cloud/tree/master/oauth2-jwt