Spring Cloud Security OAuth2 配置jwt令牌以及完善配置

一、jwt令牌

通過之前的項目我們發現了一個問題:通過上邊的測試我們發現,當資源服務和授權服務不在一起時資源服務使用RemoteTokenServices 遠程請求授權服務驗證token,如果訪問量較大將會影響系統的性能 。

解決:令牌採用JWT格式即可解決上邊的問題,用戶認證通過會得到一個JWT令牌,JWT令牌中已經包括了用戶相關的信息,客戶端只需要攜帶JWT訪問資源服務,資源服務根據事先約定的算法自行完成令牌校驗,無需每次都請求認證服務完成授權

1、什麼是jwt?

JSON Web Token(JWT)是一個開放的行業標準(RFC 7519),它定義了一種簡介的、自包含的協議格式,用於 在通信雙方傳遞json對象,傳遞的信息經過數字簽名可以被驗證和信任。JWT可以使用HMAC算法或使用RSA的公 鑰/私鑰對來簽名,防止被篡改。

JWT令牌的優點: 1)jwt基於json,非常方便解析。 2)可以在令牌中自定義豐富的內容,易擴展。 3)通過非對稱加密算法及數字簽名技術,JWT防止篡改,安全性高。 4)資源服務使用JWT可不依賴認證服務即可完成授權。1)JWT令牌較長,佔存儲空間比較大。

2、jwt令牌結構

通過學習JWT令牌結構爲自定義jwt令牌打好基礎。 JWT令牌由三部分組成,每部分中間使用點(.)分隔,比如xxxxx.yyyyy.zzzzz

(1)Header

頭部包括令牌的類型(即JWT)及使用的哈希算法(如HMAC SHA256RSA) ,一個例子如下:header內容。
 
{ "alg": "HS256", "typ": "JWT" }

將上邊的內容使用Base64Url編碼,得到一個字符串就是JWT令牌的第一部分。

(2)Payload

第二部分是負載,內容也是一個json對象,它是存放有效信息的地方,它可以存放jwt提供的現成字段,比如:iss(簽發者),exp(過期時間戳), sub(面向的用戶)等,也可自定義字段。

此部分不建議存放敏感信息,因爲此部分可以解碼還原原始內容。

最後將第二部分負載使用Base64Url編碼,得到一個字符串就是JWT令牌的第二部分。

{ "sub": "1234567890", "name": "456", "admin": true }

(3)Signature

第三部分是簽名,此部分用於防止jwt內容被篡改。

這個部分使用base64url將前兩部分進行編碼,編碼後使用點(.)連接組成字符串,最後使用header中聲明 簽名算法進行簽名。 一個例子:

HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
base64UrlEncode(header)jwt令牌的第一部分。
base64UrlEncode(payload)jwt令牌的第二部分。
secret:簽名所使用的密鑰
 

二、配置jwt

1、修改TokenConfig

package com.oauth.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
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;

/**
 * @ClassName TokenConfig
 * @Description
 * @Author
 * @Date 2020/5/9 22:13
 * @Version 1.0
 **/
@Configuration
public class TokenConfig {

    private static String KEY = "uaa123";

    /**
     * (2)
     * InMemoryTokenStore、JdbcTokenStore、JwtTokenStore
     */
    @Bean
    public TokenStore tokenStore() {
        //使用內存存儲令牌
//        return new InMemoryTokenStore();
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(KEY); //對稱祕鑰,資源服務器使用該祕鑰來驗證
        return converter;
    }
}

2、修改AuthorizationServer

添加註入:

 @Autowired
    private JwtAccessTokenConverter accessTokenConverter;

增強令牌

@Bean
    public AuthorizationServerTokenServices tokenService() {
        DefaultTokenServices service = new DefaultTokenServices();
        //配置客戶端詳情服務
        service.setClientDetailsService(clientDetailsService);
        //支持刷新令牌
        service.setSupportRefreshToken(true);
        //令牌存儲策略
        service.setTokenStore(tokenStore);

        //增強令牌
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
        service.setTokenEnhancer(tokenEnhancerChain);

        // 令牌默認有效期2小時
        service.setAccessTokenValiditySeconds(7200);
        // 刷新令牌默認有效期3天 return service;
        service.setRefreshTokenValiditySeconds(259200);
        return service;
    }

測試一下,再生成令牌(可以看到,很長的一個令牌)

看一下權限:訪問check_token接口

將tokenConifg,拷貝到order中

修改ResouceServerConfig

 @Autowired
    private TokenStore tokenStore;
 @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(RESOURCE_ID)//資源的id
                .tokenStore(tokenStore)
//                .tokenServices(tokenService())
                .stateless(true);//驗證tokenService
    }

可以訪問資源證明我們成功了

3、完善配置

將tokenconfig copy

作出修改(不再使用tokenservice):

package com.oauth.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.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
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.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;

import javax.interceptor.AroundInvoke;

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResouceServerConfig extends ResourceServerConfigurerAdapter {
    public static final String RESOURCE_ID = "res1";

    @Autowired
    private TokenStore tokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(RESOURCE_ID)//資源的id
                .tokenStore(tokenStore)
                .stateless(true);//驗證tokenService
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/**").access("#oauth2.hasScope('all')")
                .and().csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

}

我們再來測試:

依然是可以訪問的。證明我們配置成功。

實際上,到現在我們的oauth2.0就基本完事了,但是現在仍然存在很多問題,比如好多數據在內存中,這是不理想的。我們現在來完善配置。

2、完善配置

創建兩張表:

CREATE TABLE `oauth_client_details` (
  `client_id` varchar(255) NOT NULL COMMENT '客戶端標 識',
  `resource_ids` varchar(255) DEFAULT NULL COMMENT '接入資源列表',
  `client_secret` varchar(255) DEFAULT NULL COMMENT '客戶端祕鑰',
  `scope` varchar(255) DEFAULT NULL,
  `authorized_grant_types` varchar(255) DEFAULT NULL,
  `web_server_redirect_uri` varchar(255) DEFAULT NULL,
  `authorities` varchar(255) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additional_information` longtext,
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `archived` tinyint(4) DEFAULT NULL,
  `trusted` tinyint(4) DEFAULT NULL,
  `autoapprove` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='接入客戶端信息';
CREATE TABLE `oauth_code` (
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `code` varchar(255) DEFAULT NULL,
  `authentication` blob,
  KEY `code_index` (`code`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

這裏的表結構跟oauth2.0是對應的。不需要添加javabean之類的。

在AuthorizationServer類中添加

@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
 /**
     * 客戶端信息
     */
 @Bean
public ClientDetailsService clientDetailsService(DataSource dataSource) {
    ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        ((JdbcClientDetailsService)clientDetailsService).setPasswordEncoder(passwordEncoder);
   return clientDetailsService;
}

修改:

 /**
     * (1)配置客戶端詳情,進行clent_id和client_secret等驗證
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService);
    }

 /**
     * 設置授權碼模式的授權碼如何 存取,暫時採用數據庫存儲方式
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
        return new JdbcAuthorizationCodeServices(dataSource);
    }

然後啓動服務類,測試:

完美申請到令牌了。

之後我們再測試一下授權碼模式,依然是可用的!!!

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