-
-
Notifications
You must be signed in to change notification settings - Fork 91
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deprecated WithMockJwtAuth Annotation Concern #166
Comments
You can set only very partial JSON as input to The reason why Using this annotations quite a lot on "real" projects (most with some UX approach at some point), it turned out that having a few personas and specifying access controls rules with them gave a much better grasp of what was happening to business people (when talking about fine grained authorities for each endpoint, they were frequently lost). So having a JSON file for each persona and decorating the test with its name was actually helping a lot in reviews to validate that test context is actually what the business expects. |
Thanks for the explanation. I understand the concern about the possible impedance mismatch between test security claims and real-world production claims. I'm thinking right now how I would go about crafting a test case that shows this in a concrete form. This would help me think through the pros and cons of this issue some more. Unfortunately, due to work time constraints, I can't actually spend much time testing this out right now. I might be able to in a week or two. Luckily (!) for me, I don't have management that care about personas or anyone else really that I need to have these types of discussions with about security related code. Mostly, their eyes glaze over if I start talking about token claims. For better or worse. If this annotation isn't going to be removed, is the deprecation just there so that people will look at the note? If that is the case, could you put more comments on the deprecation indicating that it won't be going away and why? Maybe if I get around to making the demo test cases, we could write up some better documentation explaining the case a little bit better to people. |
@danielshiplett I investigated a bit around It wasn't complicated to search for the authentication converter in the application context and to inject it in the authentication factory behind each annotation. This improves the situation a bit: the authentication type is the right one when such a converter is provided and returns something else than the default As consequence, I'll keep it deprecated for now. Maybe, if I find a way to have it well integrated with |
@ch4mpy Need your help with the latest version of the library. I could not get our tests working after upgrading to 7.3.0. Current working tests -> https://github.com/eclipse-xpanse/xpanse/blob/main/runtime/src/test/java/org/eclipse/xpanse/runtime/AdminServicesApiWithZitadelAndMysqlTest.java#L86 i tried to set the authorities at different places. But does not help. {
"preferred_username": "xpanse-user",
"email": "[email protected]",
"aud": "https://localhost:7082",
"scope": "openid user isv admin",
"roles": ["user", "isv", "admin"],
"urn:zitadel:iam:org:project:roles": {
"user": "user",
"isv": "isv"
},
"sub": "1234566"
} Could you please let me understand what am I missing? Regards, |
Is your authentication converter exposed as a Can you please add it here? The authentication converter is a |
An option to have your test pass is using @swaroopar from what I can see in your repo, you configure no Regarding security in your pom, all I could find is: <dependency>
<groupId>org.eclipse.xpanse.modules</groupId>
<artifactId>security</artifactId>
<version>${project.version}</version>
</dependency> Unfortunately, I could not find any useful documentation about what the Spring Boot starter behind it configures and I don't really feel like digging into their source code. What I can see in your The annotation to use in your test depending on the actual type of An option to consider would be using |
@ch4mpy thank you for taking time for my question. we have a filter bean here -> https://github.com/eclipse-xpanse/xpanse/blob/main/modules/security/src/main/java/org/eclipse/xpanse/modules/security/zitadel/config/ZitadelWebSecurityConfig.java#L96 could you please see if this information helps? Would still first like to see if I can get the tests working with P.S - We will also check and consider switching to spring-addons-starter-oidc in the coming days. |
@swaroopar if you want Your conf is unusual: switch between access token introspection and JWT decoding depending on an application property. Switching from one to another has major impact at runtime: the default
Also note that you should probably be sharing the authorities extraction logic between the introspection and JWT decoding (see in the example conf below how). The test annotation to use is not the same in the different cases:
What you could do in your case is a unified @Data
@RequiredArgsConstructor
public static class SimplePrincipal implements Principal {
private final String name;
}
public static class XpanseAuthentication extends AbstractAuthenticationToken {
private static final long serialVersionUID = -2797266050799781972L;
private final Map<String, Object> claims;
private final Principal principal;
private final String token;
public XpanseAuthentication(String username, Collection<? extends GrantedAuthority> authorities, Map<String, Object> claims, String token) {
super(authorities);
this.claims = Collections.unmodifiableMap(claims);
this.principal = new SimplePrincipal(username);
this.token = token;
super.setAuthenticated(true);
}
@Override
public Map<String, Object> getCredentials() {
return claims;
}
@Override
public Principal getPrincipal() {
return principal;
}
public String getTokenString() {
return token;
}
}
@Slf4j
@Component
static class GrantedAuthoritiesExtractor implements Converter<Map<String, Object>, Collection<GrantedAuthority>> {
@Override
public Collection<GrantedAuthority> convert(Map<String, Object> claims) {
Set<GrantedAuthority> roles;
String userId = claims.get(USERID_KEY);
Map<String, Object> rolesClaim = claims.get(GRANTED_ROLES_KEY);
if (Objects.isNull(rolesClaim) || rolesClaim.isEmpty()) {
roles = Set.of(new SimpleGrantedAuthority(DEFAULT_ROLE));
log.info("Get user [id:{}] granted authorities is empty," + " set default authority user", userId);
} else {
roles = rolesClaim.keySet().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet());
log.info("Get user [id:{}] granted authorities:{}.", userId, roles);
}
return roles;
}
}
static interface XpanseAuthenticationConverter extends Converter<Jwt, XpanseAuthentication> {
}
@Bean
// This Converter<Jwt, AbstractAuthenticationToken> must be exposed as a bean to be picked by @WithJwt
XpanseAuthenticationConverter jwtAuthenticationConverter(Converter<Map<String, Object>, Collection<GrantedAuthority>> authoritiesConverter) {
return jwt -> {
final var username = (String) jwt.getClaims().get(USERID_KEY);
final var authorities = authoritiesConverter.convert(jwt.getClaims());
return new XpanseAuthentication(username, authorities, jwt.getClaims(), jwt.getTokenValue());
};
}
@Bean
// This OpaqueTokenAuthenticationConverter must be exposed as a bean to be picked by @WithOpaqueToken
OpaqueTokenAuthenticationConverter opaqueTokenAuthenticationConverter(Converter<Map<String, Object>, Collection<GrantedAuthority>> authoritiesConverter) {
return (String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) -> {
final var username = (String) authenticatedPrincipal.getAttributes().get(USERID_KEY);
final var authorities = authoritiesConverter.convert(authenticatedPrincipal.getAttributes());
return new XpanseAuthentication(username, authorities, authenticatedPrincipal.getAttributes(), introspectedToken);
};
}
@Bean
SecurityFilterChain apiFilterChain(
HttpSecurity http,
HandlerMappingIntrospector introspector,
Converter<Jwt, AbstractAuthenticationToken> jwtAuthenticationConverter,
OpaqueTokenAuthenticationConverter opaqueTokenAuthenticationConverter) {
...
if (StringUtils.equalsIgnoreCase(AUTH_TYPE_TOKEN, authTokenType)) {
// Config custom OpaqueTokenIntrospector
http
.oauth2ResourceServer(
oauth2 -> oauth2
.opaqueToken(
opaque -> opaque
.introspector(new ZitadelOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret))
.authenticationConverter(opaqueTokenAuthenticationConverter)));
}
if (StringUtils.equalsIgnoreCase(AUTH_TYPE_JWT, authTokenType)) {
// Config custom JwtAuthenticationConverter
http.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter)));
}
return http.build();
} This will bring some homogeneity at runtime, but you'll still have to test separately your app for introspection and JWT decoding (with dedicated annotation for each). Spring-addons contains such an Again, for your current tests, |
@ch4mpy thank you again for the detailed answer. Your feedback makes complete sense to me. For now, I have first got the tests working with P.S - regarding an option to switch between opaqueToken and JWT - we did this because we earlier had only opaqueToken and then found out that the OpaqueToken process increases the load on the Oauth system. So we introduced JWT and then just to keep it backward compatible, we also allowed OpaqueToken to be used by consumers if needed. |
@swaroopar I'm waiting for your questions about spring-addons and how it could match your needs. By the way, regarding the way you switch between JWT decoding and introspection, you are not quite following the Spring Boot way of doing things. What I do for that in spring-addons is exposing |
@ch4mpy thank you for asking. It is on my TODO list and hopefully will find sometime this week to refactor it. Regarding the switch between JWT and introspection, we are currently using the property directly to configure it. Will definitely consider your suggestion during refactoring. I will get back to you when it is done. |
@ch4mpy I refactored our security related code as per suggestion and now the test works with |
This is more a question than a feature. But certainly not a bug.
I noticed on a recent update of spring-addons-oauth2-test to 7.2.0 that the WithMockJwtAuth annotation is deprecated?
Can you help me understand why? I see that
@WithJwt
is recommended. However, it is missing the ability to set claims and granted authorities directly. Instead, I must craft JSON or have a file with a full JWT available (I think). I much prefer to have concise code at the point of test, rather than having to go dig into another file to understand what my test is doing.Is there an alternative? I need to have an annotation that creates the
org.springframework.security.oauth2.jwt.Jwt
principal, as that is what is received for an OAuth2 resource server.The text was updated successfully, but these errors were encountered: