將JWT與Spring Security OAuth結合使用


1.概述

在本教程中,我們將討論如何使用Spring Security OAuth2實現來使用JSON Web令牌

我們還將繼續構建此OAuth系列的上一篇文章

2. Maven配置

首先,我們需要在我們的pom.xml中添加spring-security-jwt依賴項:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
</dependency>

請注意,我們需要將spring-security-jwt依賴項添加到Authorization Server和Resource Server

3.授權服務器

接下來,我們將配置我們的授權服務器以使用JwtTokenStore - 如下所示:

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore())
                 .accessTokenConverter(accessTokenConverter())
                 .authenticationManager(authenticationManager);
    }
 
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }
 
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123");
        return converter;
    }
 
    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }
}

請注意,我們在JwtAccessTokenConverter中使用對稱密鑰來簽署我們的令牌 - 這意味着我們還需要為Resources Server使用相同的密鑰

4.資源服務器

現在,讓我們看看我們的資源服務器配置 - 這與授權服務器的配置非常相似:

@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(ResourceServerSecurityConfigurer config) {
        config.tokenServices(tokenServices());
    }
 
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }
 
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123");
        return converter;
    }
 
    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        return defaultTokenServices;
    }
}

請記住,我們將這兩個服務器定義為完全獨立且可獨立部署。這就是我們需要在新配置中再次聲明一些相同bean的原因。

5.令牌中的自定義Claims

現在讓我們設置一些基礎設施,以便能夠在Access Token中添加一些自定義聲明

框架提供的標准聲明都很好,但大多數時候我們需要在令牌中使用一些額外的信息才能在客戶端使用。框架提供的標准聲明都很好,但大多數時候我們需要在令牌中使用一些額外的信息才能在客戶端使用

我們將定義TokenEnhancer以使用這些附加聲明來自定義我們的訪問令牌

在下面的示例中,我們將使用此CustomTokenEnhancer向我們的訪問令牌添加一個額外的字段“組織”

public class CustomTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(
      OAuth2AccessToken accessToken, 
      OAuth2Authentication authentication) {
        Map<String, Object> additionalInfo = new HashMap<>();
        additionalInfo.put(
          "organization", authentication.getName() + randomAlphabetic(4));
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(
          additionalInfo);
        return accessToken;
    }
}

然后,我們將它連接到我們的授權服務器配置 - 如下所示:

@Override
public void configure(
  AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
    tokenEnhancerChain.setTokenEnhancers(
      Arrays.asList(tokenEnhancer(), accessTokenConverter()));
 
    endpoints.tokenStore(tokenStore())
             .tokenEnhancer(tokenEnhancerChain)
             .authenticationManager(authenticationManager);
}
 
@Bean
public TokenEnhancer tokenEnhancer() {
    return new CustomTokenEnhancer();
}

啟動並運行此新配置 :

{
    "user_name": "john",
    "scope": [
        "foo",
        "read",
        "write"
    ],
    "organization": "johnIiCh",
    "exp": 1458126622,
    "authorities": [
        "ROLE_USER"
    ],
    "jti": "e0ad1ef3-a8a5-4eef-998d-00b26bc2c53f",
    "client_id": "fooClientIdPassword"
}

5.1。使用JS Client中的Access Token

最后,我們希望在AngualrJS客戶端應用程序中使用令牌信息。我們將使用angular-jwt庫。

那么我們要做的就是在index.html中使用“組織”聲明:

<p class="navbar-text navbar-right">{{organization}}</p>
 
<script type="text/javascript"
  src="https://cdn.rawgit.com/auth0/angular-jwt/master/dist/angular-jwt.js">
</script>
 
<script>
var app = 
  angular.module('myApp', ["ngResource","ngRoute", "ngCookies", "angular-jwt"]);
 
app.controller('mainCtrl', function($scope, $cookies, jwtHelper,...) {
    $scope.organiztion = "";
 
    function getOrganization(){
        var token = $cookies.get("access_token");
        var payload = jwtHelper.decodeToken(token);
        $scope.organization = payload.organization;
    }
    ...
});

6.訪問資源服務器上的額外聲明

但是,我們如何在資源服務器端訪問該信息?

我們在這里做的是 - 從訪問令牌中提取額外的聲明

public Map<String, Object> getExtraInfo(OAuth2Authentication auth) {
    OAuth2AuthenticationDetails details
      = (OAuth2AuthenticationDetails) auth.getDetails();
    OAuth2AccessToken accessToken = tokenStore
      .readAccessToken(details.getTokenValue());
    return accessToken.getAdditionalInformation();
}

在下一節中,我們將討論如何使用自定義AccessTokenConverter將這些額外信息添加到我們的身份驗證詳細信息中

6.1。自定義AccessTokenConverter

讓我們創建CustomAccessTokenConverter並使用訪問令牌聲明設置身份驗證詳細信息:

@Component
public class CustomAccessTokenConverter extends DefaultAccessTokenConverter {
 
    @Override
    public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
        OAuth2Authentication authentication
         = super.extractAuthentication(claims);
        authentication.setDetails(claims);
        return authentication;
    }
}

注意:DefaultAccessTokenConverter用於將身份驗證詳細信息設置為Null

6.2。配置JwtTokenStore

接下來,我們將配置我們的JwtTokenStore以使用我們的CustomAccessTokenConverter:

@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfigJwt
 extends ResourceServerConfigurerAdapter {
 
    @Autowired
    private CustomAccessTokenConverter customAccessTokenConverter;
 
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }
 
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setAccessTokenConverter(customAccessTokenConverter);
    }
    // ...
}

6.3。身份驗證對象中提供的額外聲明

既然Authorization Server在令牌中添加了一些額外的聲明,我們現在可以直接在Authentication對象中在Resource Server端訪問

public Map<String, Object> getExtraInfo(Authentication auth) {
    OAuth2AuthenticationDetails oauthDetails
      = (OAuth2AuthenticationDetails) auth.getDetails();
    return (Map<String, Object>) oauthDetails
      .getDecodedDetails();
}

6.4。驗證測試

讓我們確保我們的Authentication對象包含額外信息:

@RunWith(SpringRunner.class)
@SpringBootTest(
  classes = ResourceServerApplication.class, 
  webEnvironment = WebEnvironment.RANDOM_PORT)
public class AuthenticationClaimsIntegrationTest {
 
    @Autowired
    private JwtTokenStore tokenStore;
 
    @Test
    public void whenTokenDoesNotContainIssuer_thenSuccess() {
        String tokenValue = obtainAccessToken("fooClientIdPassword", "john", "123");
        OAuth2Authentication auth = tokenStore.readAuthentication(tokenValue);
        Map<String, Object> details = (Map<String, Object>) auth.getDetails();
  
        assertTrue(details.containsKey("organization"));
    }
 
    private String obtainAccessToken(
      String clientId, String username, String password) {
  
        Map<String, String> params = new HashMap<>();
        params.put("grant_type", "password");
        params.put("client_id", clientId);
        params.put("username", username);
        params.put("password", password);
        Response response = RestAssured.given()
          .auth().preemptive().basic(clientId, "secret")
          .and().with().params(params).when()
          .post("http://localhost:8081/spring-security-oauth-server/oauth/token");
        return response.jsonPath().getString("access_token");
    }
}

注意:我們從授權服務器獲得了具有額外聲明的訪問令牌,然后我們從中讀取了Authentication對象,其中包含詳細信息對象中的額外信息“organization”

7.非對稱KeyPair

在我們之前的配置中,我們使用對稱密鑰來簽署我們的令牌:

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    converter.setSigningKey("123");
    return converter;
}

我們還可以使用非對稱密鑰(公鑰和私鑰)來執行簽名過程

7.1。生成JKS Java KeyStore文件

讓我們首先使用命令行工具keytool生成密鑰 - 更具體地說是.jks文件

keytool -genkeypair -alias mytest 
                    -keyalg RSA 
                    -keypass mypass 
                    -keystore mytest.jks 
                    -storepass mypass

該命令將生成一個名為mytest.jks的文件,其中包含我們的密鑰 - 公鑰和私鑰

還要確保keypass和storepass是相同的。

7.2。導出公鑰

接下來,我們需要從生成的JKS導出我們的公鑰,我們可以使用以下命令來執行此操作:

keytool -list -rfc --keystore mytest.jks | openssl x509 -inform pem -pubkey

示例響應將如下所示:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgIK2Wt4x2EtDl41C7vfp
OsMquZMyOyteO2RsVeMLF/hXIeYvicKr0SQzVkodHEBCMiGXQDz5prijTq3RHPy2
/5WJBCYq7yHgTLvspMy6sivXN7NdYE7I5pXo/KHk4nz+Fa6P3L8+L90E/3qwf6j3
DKWnAgJFRY8AbSYXt1d5ELiIG1/gEqzC0fZmNhhfrBtxwWXrlpUDT0Kfvf0QVmPR
xxCLXT+tEe1seWGEqeOLL5vXRLqmzZcBe1RZ9kQQm43+a9Qn5icSRnDfTAesQ3Cr
lAWJKl2kcWU1HwJqw+dZRSZ1X4kEXNMyzPdPBbGmU6MHdhpywI7SKZT7mX4BDnUK
eQIDAQAB
-----END PUBLIC KEY-----
-----BEGIN CERTIFICATE-----
MIIDCzCCAfOgAwIBAgIEGtZIUzANBgkqhkiG9w0BAQsFADA2MQswCQYDVQQGEwJ1
czELMAkGA1UECBMCY2ExCzAJBgNVBAcTAmxhMQ0wCwYDVQQDEwR0ZXN0MB4XDTE2
MDMxNTA4MTAzMFoXDTE2MDYxMzA4MTAzMFowNjELMAkGA1UEBhMCdXMxCzAJBgNV
BAgTAmNhMQswCQYDVQQHEwJsYTENMAsGA1UEAxMEdGVzdDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAICCtlreMdhLQ5eNQu736TrDKrmTMjsrXjtkbFXj
Cxf4VyHmL4nCq9EkM1ZKHRxAQjIhl0A8+aa4o06t0Rz8tv+ViQQmKu8h4Ey77KTM
urIr1zezXWBOyOaV6Pyh5OJ8/hWuj9y/Pi/dBP96sH+o9wylpwICRUWPAG0mF7dX
eRC4iBtf4BKswtH2ZjYYX6wbccFl65aVA09Cn739EFZj0ccQi10/rRHtbHlhhKnj
iy+b10S6ps2XAXtUWfZEEJuN/mvUJ+YnEkZw30wHrENwq5QFiSpdpHFlNR8CasPn
WUUmdV+JBFzTMsz3TwWxplOjB3YacsCO0imU+5l+AQ51CnkCAwEAAaMhMB8wHQYD
VR0OBBYEFOGefUBGquEX9Ujak34PyRskHk+WMA0GCSqGSIb3DQEBCwUAA4IBAQB3
1eLfNeq45yO1cXNl0C1IQLknP2WXg89AHEbKkUOA1ZKTOizNYJIHW5MYJU/zScu0
yBobhTDe5hDTsATMa9sN5CPOaLJwzpWV/ZC6WyhAWTfljzZC6d2rL3QYrSIRxmsp
/J1Vq9WkesQdShnEGy7GgRgJn4A8CKecHSzqyzXulQ7Zah6GoEUD+vjb+BheP4aN
hiYY1OuXD+HsdKeQqS+7eM5U7WW6dz2Q8mtFJ5qAxjY75T0pPrHwZMlJUhUZ+Q2V
FfweJEaoNB9w9McPe1cAiE+oeejZ0jq0el3/dJsx3rlVqZN+lMhRJJeVHFyeb3XF
lLFCUGhA7hxn2xf3x1JW
-----END CERTIFICATE-----

我們只使用我們的公鑰並將其復制到我們的資源服務器src / main / resources / public.txt:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgIK2Wt4x2EtDl41C7vfp
OsMquZMyOyteO2RsVeMLF/hXIeYvicKr0SQzVkodHEBCMiGXQDz5prijTq3RHPy2
/5WJBCYq7yHgTLvspMy6sivXN7NdYE7I5pXo/KHk4nz+Fa6P3L8+L90E/3qwf6j3
DKWnAgJFRY8AbSYXt1d5ELiIG1/gEqzC0fZmNhhfrBtxwWXrlpUDT0Kfvf0QVmPR
xxCLXT+tEe1seWGEqeOLL5vXRLqmzZcBe1RZ9kQQm43+a9Qn5icSRnDfTAesQ3Cr
lAWJKl2kcWU1HwJqw+dZRSZ1X4kEXNMyzPdPBbGmU6MHdhpywI7SKZT7mX4BDnUK
eQIDAQAB
-----END PUBLIC KEY-----

或者,我們可以通過添加-noout參數來僅導出公鑰:

keytool -list -rfc --keystore mytest.jks | openssl x509 -inform pem -pubkey -noout

7.3。 Maven配置

接下來,我們不希望maven過濾過程接收JKS文件 - 因此我們確保將其排除在pom.xml中:

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
            <excludes>
                <exclude>*.jks</exclude>
            </excludes>
        </resource>
    </resources>
</build>

如果我們使用Spring Boot,我們需要確保通過Spring Boot Maven插件將我們的JKS文件添加到應用程序類路徑中 - addResources

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <addResources>true</addResources>
            </configuration>
        </plugin>
    </plugins>
</build>

7.4。授權服務器

現在,我們將配置JwtAccessTokenConverter以使用mytest.jks中的KeyPair - 如下所示:

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    KeyStoreKeyFactory keyStoreKeyFactory = 
      new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), "mypass".toCharArray());
    converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
    return converter;
}

7.5。資源服務器

最后,我們需要配置我們的資源服務器以使用公鑰 - 如下所示:

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    Resource resource = new ClassPathResource("public.txt");
    String publicKey = null;
    try {
        publicKey = IOUtils.toString(resource.getInputStream());
    } catch (final IOException e) {
        throw new RuntimeException(e);
    }
    converter.setVerifierKey(publicKey);
    return converter;
}

8.結論

在這篇快速文章中,我們專注於使用JSON Web Tokens設置Spring Security OAuth2項目

可以在github項目中找到本教程的完整實現 - 這是一個基於Eclipse的項目,因此它應該很容易導入和運行。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM