使用 micronaut 创建受 micronaut-jwt 安全保护的休息端点
@Post
@IRequirement(resourceName = ClaimType.TAG_PRODUCT, permission = {ClaimValue.TAG_OWNER,ClaimValue.TAG_CREATOR,ClaimValue.TAG_MAINTAINER })
Mono<MutableHttpResponse<?>> post(@Body @Valid ProductModel model){
LOG.info(String.format("Creating new product"));
return _iServiceBus.<ProductModel, Mono<ProductModel>>send(model).flatMap(item -> {
if (item != null) {
try {
return Mono.just(HttpResponse.created(item, new URI(String.format("/%s", item.id()))));
} catch (URISyntaxException e) {
return Mono.error(new GlobalException(e));
}
} else
return Mono.just(HttpResponse.serverError());
}
).onErrorMap(throwable -> {
throw new GlobalException(throwable);
});}
安全模型
@Inherited
public @interface IRequirement {
String resourceName();
String[] permission();
}
安全规则
@Singleton
public class AuthorityHandler implements SecurityRule {
@Override
public Publisher<SecurityRuleResult> check(HttpRequest<?> request, @Nullable RouteMatch<?> routeMatch, @Nullable Authentication authentication) {
if (routeMatch instanceof MethodBasedRouteMatch methodBasedRouteMatch) {
if (methodBasedRouteMatch.hasAnnotation(IRequirement.class)) {
AnnotationValue<IRequirement> requiredPermissionAnnotation = methodBasedRouteMatch.getAnnotation(IRequirement.class);
Optional<String> resourceIdName = requiredPermissionAnnotation.stringValue( "resourceName");
String[] permissions = requiredPermissionAnnotation.stringValues("permission");
if (permissions.length > 0 && resourceIdName.isPresent() && authentication != null) {
Map<String, Object> identityClaims = authentication.getAttributes();
if (Arrays.stream(permissions).anyMatch(element -> identityClaims.containsValue(element)))
return Mono.just(SecurityRuleResult.ALLOWED);
else
return Mono.just(SecurityRuleResult.REJECTED);
}
}
}
return Mono.just(SecurityRuleResult.UNKNOWN);
}
}
上述端点受声明保护owner
,creator
并且maintainer
我正在使用身份服务器 4 ( https://github.com/IdentityServer/IdentityServer4 ) 来管理身份,并且当用户登录时,它在访问令牌中包含以下声明
{
"nbf": 1641548846,
"exp": 1641549046,
"iss": "https://localhost:5001",
"client_id": "Falcon_Identity_Server",
"sub": "673533cc-7c0b-40f3-80ac-222696df385d",
"auth_time": 1641548840,
"idp": "local",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "673533cc-7c0b-40f3-80ac-222696df385d",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "admin@local.com",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress": "admin@local.com",
"AspNet.Identity.SecurityStamp": "812aa4cb-b9f1-48ac-9e39-1f0dceb6f1c4",
"identityserver": "owner",
"fb_product": "owner",
"fb_order": "owner",
"fb_payment": "owner",
"jti": "179683F5CCA01EB925B45B1CA6379080",
"sid": "F1D9F917D34178EDAF7A5FD24948F89D",
"iat": 1641548846,
"scope": [
"openid",
"profile",
"email"
],
"amr": [
"pwd"
]
}
上面的终点是寻找那些属性
"identityserver": "owner",
"fb_product": "owner",
"fb_order": "owner",
"fb_payment": "owner",
对于运行真正的应用程序,它运行良好。我正在尝试使用 micronaut httpclient 编写测试用例。但不确定如何使用 micronaut 测试框架进行测试
我试过的东西
@Test
@DisplayName("Should create a JWT token")
void shouldCreateAJwtToken() {
BearerAccessRefreshToken bearerAccessRefreshToken = null;
UsernamePasswordCredentials creds = new UsernamePasswordCredentials("sherlock", "password");
HttpRequest request = HttpRequest.POST("/login", creds);
HttpResponse<BearerAccessRefreshToken> rsp = client.toBlocking().exchange(request, BearerAccessRefreshToken.class);
bearerAccessRefreshToken = rsp.body();
}
它创建一个令牌,但不包含上述访问令牌所需的所有验证。
我们如何在 micronaut 中进行受 JWT 令牌保护的休息端点单元测试
身份验证提供程序
@Singleton
@Requires(env = Environment.TEST)
public record AuthenticationProviderUserPasswordFixture() implements AuthenticationProvider {
@Override
public Publisher<AuthenticationResponse> authenticate(HttpRequest<?> httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
return Flux.create(emitter -> {
if (authenticationRequest.getIdentity().equals("sherlock") && authenticationRequest.getSecret().equals("password")) {
emitter.next(AuthenticationResponse.success((String) authenticationRequest.getIdentity(), List.of("roles1", "roles2")));
emitter.complete();
} else {
emitter.error(AuthenticationResponse.exception());
}
}, FluxSink.OverflowStrategy.ERROR);
}
}