Spring-Cloud之OAuth2的JWT保護-12

  一、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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章