【问题标题】:Verification of signed JWT验证签名的 JWT
【发布时间】:2019-11-14 01:13:12
【问题描述】:

我正在使用私钥(授权服务器)签署 JWT,并且我正在使用公钥(资源服务器)来“验证”它...... 我如何知道 JWT 是否没有被入侵?或者我该怎么做?

代码来自资源服务器

       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;
    }

【问题讨论】:

  • 使用公钥解密。如果您可以成功解密它是安全的(除非有人拥有您的私钥)。如果解密出错,任何库都会失败。
  • 可以查看Auth0库。它提供了验证令牌所需的工具:auth0.com/docs/api-auth/tutorials/verify-access-token
  • 签署 JWT 不会使您的 JWT 加密。您签署它以确保在将数据传输到另一台服务器时没有人更改数据。当您使用私钥对其进行签名时,您只需使用公钥验证具体私钥对其进行了签名,并且在传输过程中没有人更改数据。
  • @michalk 是的,但是上面的代码可以做到吗?它如何让我知道 JWT 已被入侵?
  • 如果 JWT 已签名,则它不再是 JWT - 它是包含 3 部分的 JWS - 标头、有效负载、签名。您正在使用的库应获取公钥并使用此公钥验证此签名。如果无法验证签名,则应抛出异常或返回一些表明无法正确验证签名的值。

标签: java spring oauth-2.0


【解决方案1】:

Spring Security 将根据授权服务器中的配置对令牌进行验证。

对于独立验证,代码如下:

  RsaVerifier verifier = new RsaVerifier(RSAPublicKey);
  Jwt tokenDecoded = JwtHelper.decodeAndVerify(token, verifier);
  Map<String, Object> claimsMap = (Map<String, Object>) new 
  ObjectMapper().readValue(tokenDecoded.getClaims(), Map.class);
  //Verify the claims then
  // 1 Verify if the token has not already expired
  // 2 Verify the issuance date ( should be before this date )
  // 3 Verify if the issuer of this token is contained in verified authorities.
  // 4 Verify if the token was issued for this client
  // 5 Verify if the token contained any expected claims...

但以上是 Spring Security 实现的 Oauth2 认证过程,客户端应用只需要提供配置即可。

触发器是Spring安全过滤器链中的OAuth2AuthenticationProcessingFilter。当资源受 Oauth2 安全保护时,会添加此过滤器。

在您的应用程序中,授权服务器配置如下所示(以下仅提取相关指示性配置)

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

...
 @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        RSAPemKeyPairLoader keyPairLoader = new RSAPemKeyPairLoader();
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(...);
        converter.setVerifierKey(...);
        return converter;
    }

 @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(...);
        defaultTokenServices.setAccessTokenValiditySeconds(...);
        defaultTokenServices.setRefreshTokenValiditySeconds(...);
        return defaultTokenServices;
    }
}

在您的应用程序中,资源服务器配置如下:

@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
  public void configure(HttpSecurity http) throws Exception {
   ...
  }
}

要在 Spring 实现中跟踪请求的令牌被拦截和验证的位置,请查看 Spring OAUTH2 实现 - 下面的流程详细信息,其中 Authentication 对象,将尝试为成功的请求创建 OAuth2Authentication 的实例。

以下所有代码摘录均来自 spring-security-oauth2-2.0.8.RELEASE 实现。

public class OAuth2AuthenticationManager implements AuthenticationManager {

                ....
                public Authentication authenticate(Authentication authentication) throws AuthenticationException {

                        if (authentication == null) {
                            throw new InvalidTokenException("Invalid token (token not found)");
                        }
                        String token = (String) authentication.getPrincipal();
                        OAuth2Authentication auth = tokenServices.loadAuthentication(token);
                        ...
         }
}

loadAuthentication 基本上是验证访问令牌并尝试将其转换为 OAuth2Authentication

public class DefaultTokenServices implements AuthorizationServerTokenServices ...{

           public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException, InvalidTokenException {

                   OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
                    ...
            }
}

JwtTokenStore 将创建 OAuth2AccessToken 并在此过程中解码和验证字符串令牌。

    public class JwtTokenStore implements TokenStore {

        public OAuth2AccessToken readAccessToken(String tokenValue) {
                OAuth2AccessToken accessToken = convertAccessToken(tokenValue);
                if (jwtTokenEnhancer.isRefreshToken(accessToken)) {
                    throw new InvalidTokenException("Encoded token is a refresh token");
                }
                return accessToken;
            }

        private OAuth2AccessToken convertAccessToken(String tokenValue) {
                return jwtTokenEnhancer.extractAccessToken(tokenValue, jwtTokenEnhancer.decode(tokenValue));
            }

  }

JWTAccessTokenConverter 对令牌声明进行解码和提取。

public class JwtAccessTokenConverter implements AccessTokenConverter {

   protected Map<String, Object> decode(String token) {
                    try {
                        Jwt jwt = JwtHelper.decodeAndVerify(token, verifier);
                        String content = jwt.getClaims();
                        Map<String, Object> map = objectMapper.parseMap(content);
                        if (map.containsKey(EXP) && map.get(EXP) instanceof Integer) {
                            Integer intValue = (Integer) map.get(EXP);
                            map.put(EXP, new Long(intValue));
                        }
                        return map;
                    }
                    catch (Exception e) {
                        throw new InvalidTokenException("Cannot convert access token to JSON", e);
                    }
}

JwtHelper 会进行解码和请求验证。

public static Jwt decodeAndVerify(String token, SignatureVerifier verifier) {
     Jwt jwt = decode(token);
     jwt.verifySignature(verifier);
     return jwt;
}

JwttImpl 调用验证器。

public void verifySignature(SignatureVerifier verifier) {
    verifier.verify(signingInput(), crypto);
}

例如,RSA 签名验证器最终会进行验证:

public class RsaVerifier implements SignatureVerifier {

            public void verify(byte[] content, byte[] sig) {
                    try {
                        Signature signature = Signature.getInstance(algorithm);
                        signature.initVerify(key);
                        signature.update(content);

                        if (!signature.verify(sig)) {
                            throw new InvalidSignatureException("RSA Signature did not match content");
                        }
                    }
                    catch (GeneralSecurityException e) {
                        throw new RuntimeException(e);
                    }
                }
    }

【讨论】:

    猜你喜欢
    • 2016-08-28
    • 2018-09-16
    • 2014-05-29
    • 2017-12-30
    • 2019-11-01
    • 2020-12-15
    • 2016-07-10
    • 2018-05-29
    相关资源
    最近更新 更多