«1. Обзор

В этом кратком руководстве мы будем работать с реализацией Spring Security OAuth2 и узнаем, как проверять утверждения JWT с помощью нового JwtClaimsSetVerifier, представленного в Spring Security OAuth 2.2.0.RELEASE.

2. Конфигурация Maven

Во-первых, нам нужно добавить последнюю версию spring-security-oauth2 в наш pom.xml:

<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>

3. Конфигурация хранилища токенов

Далее, давайте настроим наш TokenStore на сервере ресурсов:

@Bean
public TokenStore tokenStore() {
    return new JwtTokenStore(accessTokenConverter());
}

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

Обратите внимание, как мы добавляем новый верификатор в наш JwtAccessTokenConverter.

Дополнительные сведения о настройке JwtTokenStore см. в статье об использовании JWT с Spring Security OAuth.

Теперь, в следующих разделах, мы обсудим различные типы верификаторов утверждений и то, как заставить их работать вместе.

4. IssuerClaimVerifier

Мы начнем с простого — с проверки утверждения Issuer «iss» с помощью IssuerClaimVerifier — следующим образом:

@Bean
public JwtClaimsSetVerifier issuerClaimVerifier() {
    try {
        return new IssuerClaimVerifier(new URL("http://localhost:8081"));
    } catch (MalformedURLException e) {
        throw new RuntimeException(e);
    }
}

В этом примере мы добавили простой IssuerClaimVerifier к проверить нашего эмитента. Если токен JWT содержит другое значение для утверждения эмитента «iss», будет выдано простое исключение InvalidTokenException.

Естественно, если токен содержит утверждение эмитента «iss», исключение не будет выдано, и токен будет считаться действительным.

5. Пользовательский верификатор утверждений

Но здесь интересно то, что мы также можем создать собственный верификатор утверждений:

@Bean
public JwtClaimsSetVerifier customJwtClaimVerifier() {
    return new CustomClaimVerifier();
}

Вот простая реализация того, как это может выглядеть – чтобы проверить, Утверждение user_name существует в нашем токене JWT:

public class CustomClaimVerifier implements JwtClaimsSetVerifier {
    @Override
    public void verify(Map<String, Object> claims) throws InvalidTokenException {
        String username = (String) claims.get("user_name");
        if ((username == null) || (username.length() == 0)) {
            throw new InvalidTokenException("user_name claim is empty");
        }
    }
}

Обратите внимание, что здесь мы просто реализуем интерфейс JwtClaimsSetVerifier, а затем предоставляем полностью настраиваемую реализацию метода проверки, что дает нам полную гибкость для любого вида проверки. нам нужно.

6. Объединение нескольких верификаторов утверждений

Наконец, давайте посмотрим, как объединить несколько верификаторов утверждений с помощью DelegatingJwtClaimsSetVerifier – следующим образом: эти проверяющие.

@Bean
public JwtClaimsSetVerifier jwtClaimsSetVerifier() {
    return new DelegatingJwtClaimsSetVerifier(Arrays.asList(
      issuerClaimVerifier(), customJwtClaimVerifier()));
}

7. Простой интеграционный тест

Теперь, когда мы закончили с реализацией, давайте проверим наши верификаторы утверждений с помощью простого интеграционного теста:

Мы начнем с токена, который не содержит эмитент (но содержит user_name) — который должен быть действительным:

@RunWith(SpringRunner.class)
@SpringBootTest(
  classes = ResourceServerApplication.class, 
  webEnvironment = WebEnvironment.RANDOM_PORT)
public class JwtClaimsVerifierIntegrationTest {

    @Autowired
    private JwtTokenStore tokenStore;

    ...
}

Причина, по которой это допустимо, проста — первый верификатор активен только в том случае, если в токене существует утверждение эмитента. Если этого утверждения не существует — верификатор не сработает.

@Test
public void whenTokenDontContainIssuer_thenSuccess() {
    String tokenValue = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....";
    OAuth2Authentication auth = tokenStore.readAuthentication(tokenValue);
    
    assertTrue(auth.isAuthenticated());
}

Далее давайте посмотрим на токен, который содержит действительный эмитент (http://localhost:8081) и user_name. Это также должно быть действительным:

Если токен содержит недопустимый эмитент (http://localhost:8082) — тогда он будет проверен и определен как недействительный:

@Test
public void whenTokenContainValidIssuer_thenSuccess() {
    String tokenValue = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9....";
    OAuth2Authentication auth = tokenStore.readAuthentication(tokenValue);
    
    assertTrue(auth.isAuthenticated());
}

Далее , когда токен не содержит утверждения user_name, он будет недействительным:

@Test(expected = InvalidTokenException.class)
public void whenTokenContainInvalidIssuer_thenException() {
    String tokenValue = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9....";
    OAuth2Authentication auth = tokenStore.readAuthentication(tokenValue);
    
    assertTrue(auth.isAuthenticated());
}

И, наконец, когда токен содержит пустое утверждение user_name, он также недействителен:

@Test(expected = InvalidTokenException.class)
public void whenTokenDontContainUsername_thenException() {
    String tokenValue = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9....";
    OAuth2Authentication auth = tokenStore.readAuthentication(tokenValue);
    
    assertTrue(auth.isAuthenticated());
}

8. Заключение

@Test(expected = InvalidTokenException.class)
public void whenTokenContainEmptyUsername_thenException() {
    String tokenValue = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9....";
    OAuth2Authentication auth = tokenStore.readAuthentication(tokenValue);
    
    assertTrue(auth.isAuthenticated());
}

В этой быстрой статье мы рассмотрели новую функциональность верификатора в Spring Security OAuth.

Как всегда, полный исходный код доступен на GitHub.

«