简体   繁体   English

如何确保 Spring 安全保护资源服务器仅接受来自自己应用程序的 JWT 令牌

[英]How to ensure only JWT tokens from own application are accepted in Spring Security secured resource server

I am using Spring Security with Spring Boot 2.2.0, trying to get Azure AD B2C working, using spring-security-oauth2-resource-server:5.2.0 and spring-security-oauth2-jose:5.2.0 . I am using Spring Security with Spring Boot 2.2.0, trying to get Azure AD B2C working, using spring-security-oauth2-resource-server:5.2.0 and spring-security-oauth2-jose:5.2.0 .

Using this config:使用此配置:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/api/**")
            .authenticated()
            .and()
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
    }
}

with spring.security.oauth2.resourceserver.jwt.jwk-set-uri set in my application.properties .在我的application.properties中设置了spring.security.oauth2.resourceserver.jwt.jwk-set-uri

I can get a token from Azure AD B2C and access my own API endpoint using that token.我可以从 Azure AD B2C 获取令牌,并使用该令牌访问我自己的 API 端点。 However, if I use a token from another directory , the endpoint can also be accessed.但是,如果我使用另一个目录中的令牌,也可以访问端点。

I do see in the claims of the principal that this comes from another azure directory.我确实在委托人的声明中看到这来自另一个 azure 目录。 Is this something I need to manually add in my application (testing if the application id matches in the claims)?这是我需要在我的应用程序中手动添加的东西吗(测试应用程序 ID 是否与声明中的匹配)? Or should I add some other configuration that I have not done yet?或者我应该添加一些我还没有完成的其他配置?

I also tried adding my own JwtDecoder bean like this using JwtDecoders.fromOidcIssuerLocation("https://mycompb2ctestorg.b2clogin.com/mycompb2ctestorg.onmicrosoft.com/v2.0/?p=B2C_1_ropc_flow");我还尝试使用JwtDecoders.fromOidcIssuerLocation("https://mycompb2ctestorg.b2clogin.com/mycompb2ctestorg.onmicrosoft.com/v2.0/?p=B2C_1_ropc_flow");添加我自己的 JwtDecoder bean , but that gives: ,但这给出了:

java.lang.IllegalStateException: The Issuer "https://mycompb2ctestorg.b2clogin.com/60780907-bc3a-469a-82d1-b89ffed655af/v2.0/" 
provided in the configuration did not match the requested issuer 
"https://mycompb2ctestorg.b2clogin.com/mycompb2ctestorg.onmicrosoft.com/v2.0/?p=B2C_1_ropc_flow"

Also, using:此外,使用:

spring.security.oauth2.resourceserver.jwt.issuer-uri=https://mycompb2ctestorg.b2clogin.com/mycompb2ctestorg.onmicrosoft.com/v2.0/?p=B2C_1_ropc_flow

gives the same exception as trying to declare my own JwtDecoder bean.给出与尝试声明我自己的JwtDecoder bean 相同的异常。

After reading about custom token validators in the Spring Security docs, I have added a custom validator that checks the audience claim to ensure the token was issued for my own application.在阅读了 Spring 安全文档中的自定义令牌验证器之后,我添加了一个自定义验证器,用于检查受众声明以确保为我自己的应用程序颁发了令牌。 To do this, create this validator class:为此,请创建此验证器 class:

private static class AudienceValidator implements OAuth2TokenValidator<Jwt> {

    @Override
    public OAuth2TokenValidatorResult validate(Jwt token) {
        if (token.getAudience().contains("my-application-id-here")) {
            return OAuth2TokenValidatorResult.success();
        } else {
            return OAuth2TokenValidatorResult.failure(
                    new OAuth2Error("invalid_token", "The audience is not as expected, got " + token.getAudience(),
                                    null));
        }
    }
}

And use it by declaring your own JwtDecoder bean in your WebSecurityConfigurerAdapter configuration class:并通过在WebSecurityConfigurerAdapter配置 class 中声明自己的JwtDecoder bean 来使用它:

@Bean
public JwtDecoder jwtDecoder() {
    NimbusJwtDecoder result = NimbusJwtDecoder.withJwkSetUri(properties.getJwt().getJwkSetUri()).build();
    result.setJwtValidator(
            new DelegatingOAuth2TokenValidator<Jwt>(
                   JwtValidators.createDefault(), 
                   new AudienceValidator()) 
            );
    return result;
}

The default validator will check things like the timestamp.默认验证器将检查时间戳等内容。 If that is ok, the AudienceValidator will check the audience claim.如果没问题,AudienceValidator 将检查观众声明。

NOTE: The order that you pass in the validators in the DelegatingOAuth2TokenValidator defines the order of how the JWT token is checked.注意:您在DelegatingOAuth2TokenValidator中传入验证器的顺序定义了检查 JWT 令牌的顺序。 In the example here, the timestamp is checked before the audience.在此处的示例中,在观众之前检查时间戳。 If you want the audience check first, you need put it first in the constructor of DelegatingOAuth2TokenValidator如果你想让观众先检查,你需要把它放在DelegatingOAuth2TokenValidator的构造函数中

I know I'm late to the party but there is some information missing in the current answers.我知道我迟到了,但当前答案中缺少一些信息。

The IllegalStateException:非法状态异常:

provided in the configuration did not match the requested issuer 

is very likely caused as @Dragana Le Mitova already mentioned by the slash at the end of the issue-uri property.很可能是因为@Dragana Le Mitova 在 issue-uri 属性末尾的斜线已经提到过。 This needs to be set to:这需要设置为:

spring.security.oauth2.resourceserver.jwt.issuer-uri=https://mycompb2ctestorg.b2clogin.com/mycompb2ctestorg.onmicrosoft.com/v2.0

Spring will then pull the configuration for the OAuth Resource Server from https://mycompb2ctestorg.b2clogin.com/mycompb2ctestorg.onmicrosoft.com/v2.0/.well-known/openid-configuration automatically. Spring will then pull the configuration for the OAuth Resource Server from https://mycompb2ctestorg.b2clogin.com/mycompb2ctestorg.onmicrosoft.com/v2.0/.well-known/openid-configuration automatically.


To ensure that we only accept JWTs from our Azure application we need to check the audience (aud) attribute of the JWT.为确保我们只接受来自 Azure 应用程序的 JWT,我们需要检查 JWT 的受众 (aud) 属性。 For Azure Applications this is usually the same as the client/app id.对于 Azure 应用程序,这通常与客户端/应用程序 ID 相同。 As @Wim Deblauwe has already answered, this is done with custom validators for the JwtDecoder .正如@Wim Deblauwe 已经回答的那样,这是通过JwtDecoder的自定义验证器完成的。 Spring Security even gives us an example in their documentation for a custom JWT validator in which they implement the audience claim check. Spring Security 甚至在他们的文档中为我们提供了一个自定义 JWT 验证器的示例,他们在其中实施了受众声明检查。 This is done by providing our own validator in the JwtDecoder bean.这是通过在JwtDecoder bean 中提供我们自己的验证器来完成的。

Audience (aud) claim validator:受众 (aud) 声明验证器:

public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
    OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is missing", null);

    public OAuth2TokenValidatorResult validate(Jwt jwt) {
        if (jwt.getAudience().contains("messaging")) {
            return OAuth2TokenValidatorResult.success();
        } else {
            return OAuth2TokenValidatorResult.failure(error);
        }
    }
}

JwtDecoder: Jwt解码器:

@Bean
JwtDecoder jwtDecoder() {
    NimbusJwtDecoderJwkSupport jwtDecoder = (NimbusJwtDecoderJwkSupport)
        JwtDecoders.withOidcIssuerLocation(issuerUri);

    OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator();
    OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
    OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);

    jwtDecoder.setJwtValidator(withAudience);

    return jwtDecoder;
}

This is slightly different than the answer provided by @Wim Deblauwe in the way that we do create the validators using JwtValidators.createDefaultWithIssuer(issuerUri) instead of JwtValidators.createDefault() .这与@Wim Deblauwe 提供的答案略有不同,我们使用JwtValidators.createDefaultWithIssuer(issuerUri)而不是JwtValidators.createDefault()创建验证器。 This is important because we essentially want to check 3 JWT attributes:这很重要,因为我们本质上要检查 3 个 JWT 属性:

  • Expiration到期
  • Issuer发行人
  • Audience观众

With JwtValidators.createDefault() we only create a validator for the expiration attribute.使用JwtValidators.createDefault()我们只为过期属性创建一个验证器。 With JwtValidators.createDefaultWithIssuer(issuerUri) we create validators for the expiration and issuer attribute.使用JwtValidators.createDefaultWithIssuer(issuerUri)我们为过期和颁发者属性创建验证器。 JwtValidators.createDefaultWithIssuer(issuerUri) is also the default behavior of Spring Security when setting the issue-uri property. JwtValidators.createDefaultWithIssuer(issuerUri)也是 Spring Security 在设置 issue-uri 属性时的默认行为。

There is an ongoing discussion if the way we currently have to add custom validators is optimal if you want to go a bit more in-depth.如果您想更深入地了解 go,我们目前必须添加自定义验证器的方式是否最佳,目前 正在进行讨论

I suspect that the exception you are getting when trying to declare your own JwtDecoder beam, comes from the slash that is missing.我怀疑您在尝试声明自己的JwtDecoder束时遇到的异常来自缺少的斜线。

Note that the " requested issuer " always lacks the trailing slash, even if it was explicitly specified with trailing slash in the configuration.请注意,“请求的颁发者”总是缺少尾部斜杠,即使在配置中明确指定了尾部斜杠。

Spring Security removes any trailing slash from the issuer URI before appending, as mandated by the spec. Spring 按照规范的要求,安全性会在附加之前从颁发者 URI 中删除任何尾部斜杠。 However, after having fetched the configuration, the returned configuration's issuer URI matched against the "cleaned up" version of the issue URI, rather than the the originally provided one.但是,在获取配置后,返回的配置的颁发者 URI 与问题 URI 的“清理”版本匹配,而不是最初提供的版本。

Because fromOidcIssuerLocation() doesn't know about the originally provided issuer URL, it matches against the cleanedIssuer, which causes the described problem.因为fromOidcIssuerLocation()不知道最初提供的颁发者 URL,所以它与 cleanIssuer 匹配,这会导致所描述的问题。 The easiest way to solve that is to do the cleanup within fromOidcIssuerLocation() , which then still has the original version available for matching.解决这个问题的最简单方法是在fromOidcIssuerLocation()中进行清理,然后仍然可以匹配原始版本。

Another note related to the first question you asked.另一个与您提出的第一个问题有关的注释。

If I know your API's identifier + your tenant id, I can acquire an access token for your API using client credentials, The token will not contain scopes or roles.如果我知道您的 API 标识符 + 您的租户 ID,我可以使用客户端凭据为您的 API 获取访问令牌,该令牌将不包含范围或角色。 it cannot.这不可以。 So it is critical that you check for the presence of valid delegated permissions (aka scopes) or valid app permissions (in roles claim).因此,检查是否存在有效的委派权限(也称为范围)或有效的应用程序权限(在角色声明中)至关重要。

Look at this particular code.看看这个特定的代码。

 JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();

        TokenValidationParameters validationParameters = new TokenValidationParameters
        {
            // We accept both the App Id URI and the AppId of this service application
            ValidAudiences = new[] { audience, clientId },

            // Supports both the Azure AD V1 and V2 endpoint
            ValidIssuers = new[] { issuer, $"{issuer}/v2.0" },
            IssuerSigningKeys = signingKeys
        };

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM