简体   繁体   English

Wildfly 中仅承载身份验证,不使用 Keycloak

[英]Bearer only authentication in Wildfly without using Keycloak

I want to do my own implementation of Bearer-only authentication in Wildfly.我想在 Wildfly 中自己实现 Bearer-only 身份验证。 In essence, I will do the following steps:本质上,我将执行以下步骤:

  1. When I receive a request, I will check if it has an Authorization header.当我收到一个请求时,我会检查它是否有一个 Authorization 标头。

  2. I obtain the token and check against a database (in this case I will be using Redis) for the validity of it.我获取令牌并检查数据库(在这种情况下,我将使用 Redis)检查它的有效性。

  3. I obtain the role for that user from the database.我从数据库中获取该用户的角色。

  4. I want to be able to use the @RolesAllowed annotation on my rest services.我希望能够在我的休息服务上使用@RolesAllowed注释。

How do I go about it?我该怎么做? How do I need to modify the Wildfly configuration files?如何修改 Wildfly 配置文件? What interfaces do I need to implement?我需要实现哪些接口? How can I pass the role of the user to the security context so that Wildfly does the @RolesAllowed check for me?如何将用户的角色传递给安全上下文,以便@RolesAllowed为我检查 @RolesAllowed?

If answering, consider that I am an experienced Java Programmer, but new to Wildfly, so you can skip details on programming logic but not on Wildfly configuration.如果回答,请考虑我是一位经验丰富的 Java 程序员,但对 Wildfly 不熟悉,因此您可以跳过有关编程逻辑的详细信息,但不能跳过有关 Wildfly 配置的详细信息。 Also in your answer don't worry on how the token got to Redis in the first place, or how the client obtained it.同样在您的回答中,不要担心令牌首先是如何到达 Redis 的,或者客户端是如何获得它的。

EDIT编辑

This is what I have done, but with no luck yet.这就是我所做的,但还没有运气。 I have implemented an AuthenticationFilter that implements ContainerRequestFilter .我已经实现了一个实现ContainerRequestFilterAuthenticationFilter (I am including below only the main filter function that I have implemented. Note that there are some helper functions that get the roles from the database that are not included). (我在下面只包括我已经实现的主要过滤器功能。请注意,有一些从数据库中获取角色的辅助功能不包括在内)。 Even when, at the end of the function I set the security context of the request context with the user profile (which contains the role), I cannot get to work the @RolesAllowed annotations on my JAX-RS rest services.即使在函数结束时,我使用用户配置文件(包含角色)设置请求上下文的安全上下文,我也无法在我的 JAX-RS 休息服务上使用@RolesAllowed注释。 Any pointers on what should I do?关于我应该做什么的任何指示?

Note: I have not modified any Wildfly configuration files or the web.xml file.注意:我没有修改任何 Wildfly 配置文件或 web.xml 文件。 I know that the filter is being called for every request because I am able to LOG messages from it on every request.我知道每个请求都会调用过滤器,因为我能够在每个请求上记录来自它的消息。

/** 
 * (non-Javadoc)
 * @see javax.ws.rs.container.ContainerRequestFilter#filter(javax.ws.rs.container.ContainerRequestContext)
 */
@Override
public void filter(ContainerRequestContext requestContext) {

    //1. Read the JSON web token from the header
    String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
    if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
        requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
        return;
    }

    String token = authorizationHeader.substring("Bearer".length()).trim();

    try{
        //Note that if the token is not in the database,
        //an exception will be thrown and we abort.

        UserProfile userProfile = this.getUserProfile(token);

        if (null == userProfile){
            userProfile = this.decodeToken(token);
        }


        if (null == userProfile){
            throw new Exception();
        }


        String role = userProfile.getUserRole();
        if (null == role){
            role = this.getRoleFromMod(userProfile);
            if (null == role){
                role = RoleType.READ_ONLY;
            }
            userProfile.setUserRole(role);
            this.updateUserProfileForToken(token, userProfile);

        }

        userProfile.setUserRole(role);

        //5. Create a security context class that implements the crazy interface 
        //and set it here.
        requestContext.setSecurityContext(new ModSecurityContext(userProfile));

    }
    catch(Exception e){
        requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
    }
}

Yeah I am not sure how it would work in an EE environment, even making the resource class an stateless bean. 是的,我不确定它在EE环境中如何工作,甚至使资源类成为无状态的Bean。 The @RolesAllowed annotation is meant to be used for ejbs. @RolesAllowed批注旨在用于ejb。 In which case the principal is retrieved from the servlet request (I believe). 在这种情况下,从Servlet请求中检索主体(我相信)。 What I would do is just implements your own authorization filter that looks up the annotation and checks against the principal in the security context. 我要做的只是实现您自己的授权过滤器,该过滤器将查找注释并对照安全上下文中的主体进行检查。

You can see how Jersey implements it . 您可以看到Jersey如何实现它 Nothing is really Jersey specific about it except the AnnotatedMethod class. 除了AnnotatedMethod类之外,没有什么东西是真正针对泽西岛的。 For that you can just do some reflection with java.lang.reflect.Method (resourceInfo.getResourceMethod()) instead. 为此,您可以使用java.lang.reflect.Method (resourceInfo.getResourceMethod())进行一些反射。 Other than that, you can pretty much copy the code as is. 除此之外,您几乎可以照原样复制代码。 Once you're done, just register the RolesAllowedDynamicFeature with the application. 完成后,只需向应用程序注册RolesAllowedDynamicFeature Or just annotate it with @Provider to be scanned for. 或者只是使用@Provider对其进行注释以进行扫描。

You will also need to make sure your authentication filter is annotated with @Priority(Priorities.AUTHENTICATION) so that it is called before the authorization filter, which is annotated with @Priority(Priorities.AUTHORIZATION) . 您还需要确保使用@Priority(Priorities.AUTHENTICATION)注释认证过滤器,以便在使用@Priority(Priorities.AUTHORIZATION)注释的授权过滤器之前调用它。


UPDATE UPDATE

Here is a refactor of the the code I linked to, so It doesn't use an Jersey specific classes. 这是我链接到的代码的重构,因此它不使用Jersey特定的类。 The AnnotatedMethod is just changed to Method . AnnotatedMethod更改为Method

@Provider
public class RolesAllowedFeature implements DynamicFeature {

    @Override
    public void configure(ResourceInfo resourceInfo, FeatureContext configuration) {
        Method resourceMethod = resourceInfo.getResourceMethod();

        if (resourceMethod.isAnnotationPresent(DenyAll.class)) {
            configuration.register(new RolesAllowedRequestFilter());
            return;
        }

        RolesAllowed ra = resourceMethod.getAnnotation(RolesAllowed.class);
        if (ra != null) {
            configuration.register(new RolesAllowedRequestFilter(ra.value()));
            return;
        }

        if (resourceMethod.isAnnotationPresent(PermitAll.class)) {
            return;
        }

        ra = resourceInfo.getResourceClass().getAnnotation(RolesAllowed.class);
        if (ra != null) {
             configuration.register(new RolesAllowedRequestFilter(ra.value()));
        }
    }

    @Priority(Priorities.AUTHORIZATION) // authorization filter - should go after any authentication filters
    private static class RolesAllowedRequestFilter implements ContainerRequestFilter {

        private final boolean denyAll;
        private final String[] rolesAllowed;

        RolesAllowedRequestFilter() {
            this.denyAll = true;
            this.rolesAllowed = null;
        }

        RolesAllowedRequestFilter(final String[] rolesAllowed) {
            this.denyAll = false;
            this.rolesAllowed = (rolesAllowed != null) ? rolesAllowed : new String[]{};
        }

        @Override
        public void filter(final ContainerRequestContext requestContext) throws IOException {
            if (!denyAll) {
                if (rolesAllowed.length > 0 && !isAuthenticated(requestContext)) {
                    throw new ForbiddenException("Not Authorized");
                }

                for (final String role : rolesAllowed) {
                    if (requestContext.getSecurityContext().isUserInRole(role)) {
                        return;
                    }
                }
            }

            throw new ForbiddenException("Not Authorized");
        }

        private static boolean isAuthenticated(final ContainerRequestContext requestContext) {
            return requestContext.getSecurityContext().getUserPrincipal() != null;
        }
    }
}

First let me explain a bit about how the DynamicFeature works. 首先,让我解释一下DynamicFeature工作原理。 For that let's first change the context of discussion to your current implementation of your AuthenticationFilter . 为此,我们首先将讨论的上下文更改为AuthenticationFilter当前实现。

Right now it is a filter that is processed for every request. 现在,它是为每个请求处理的过滤器。 But let's say we introduced a custom @Authenticated annotation 但是,假设我们引入了自定义@Authenticated批注

@Target({METHOD, TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Authenticated{}

We could use this annotation to annotate different methods and classes. 我们可以使用此注释来注释不同的方法和类。 To make it so that only the methods and classes annotated are filtered by the filter, we can introduce a DynamicFeature that checks for the annotation, then only register the filter when the annotation is found. 为了使过滤器仅过滤带注释的方法和类,我们可以引入DynamicFeature来检查注释,然后仅在找到注释时注册过滤器。 For example 例如

@Provider
public class AuthenticationDynamicFeature implements DynamicFeature {

    @Override
    public void configure(ResourceInfo resourceInfo, FeatureContext configuration) {
        if (resourceInfo.getResourceMethod().isAnnotationPresent(Authenticated.class)) {
            configuration.register(new AuthenticationFilter());
            return;
        }

        if (resourceInfo.getResourceClass().isAnnotationPresent(Authenticated.class)) {
            configuration.register(new AuthenticationFilter());
        }
    } 
}

Once we register this AuthenticationDynamicFeature class, it will make it so that only methods and classes annotated with @Authenticated will be filtered. 一旦我们注册了AuthenticationDynamicFeature类,它将进行注册,以便仅使用@Authenticated注释的方法和类将被过滤。

Alternatively, this can even be done within the filter. 另外,这甚至可以过滤器完成。 We can get a reference to the ResourceInfo from within the AuthenticationFilter . 我们可以从AuthenticationFilter获取对ResourceInfo的引用。 For example check for the annotation, if is not there, then move on. 例如,检查注解(如果不存在),然后继续。

@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {

    @Context
    private ResourceInfo resourceInfo;

    @Override
    public void filter(ContainerRequestContext context) throws IOException {

        boolean hasAnnotation = false;
        if (resourceInfo.getResourceMethod().isAnnotationPresent(Authenticated.class)
                || resourceInfo.getResourceClass().isAnnotationPresent(Authenticated.class)) {
            hasAnnotation = true;
        }
        if (!hasAnnotation) return;

        // process authentication is annotation is present

This way we could completely forget about the DynamicFeature . 这样,我们就可以完全忘记DynamicFeature It's better to just use the DynamicFeature , I was just giving an example for demonstration purposes. 最好只使用DynamicFeature ,我只是举一个例子进行演示。

But that being said, if we look at the first block of code with the RolesAllowedDynamicFeature , you can better understand what is going on. 话虽这么说,如果我们使用RolesAllowedDynamicFeature代码,则可以更好地了解发生了什么。 It only registers the filter for methods and classes annotated with @RolesAllowed and @DenyAll . 它仅为使用@RolesAllowed@DenyAll注释的方法和类注册过滤器。 You could even refactor it to have all the annotation logic in the filter instead of the feature. 您甚至可以对其进行重构,以将所有注释逻辑(而不是功能)包含在过滤器中。 You only have the filter. 您只有过滤器。 Just like I did with the AuthenticationFilter example above. 就像我对上面的AuthenticationFilter示例所做的一样。 Again this would be just for example purposes. 同样,这只是出于示例目的。

Now as far as the registration of the DynamicFeature , it works the same way as registering any other resource class or provider class (eg your authentication filter). 现在,就DynamicFeature的注册而言,它的工作方式与注册任何其他资源类或提供程序类(例如,身份验证筛选器)相同。 So however you register those, just register the RolesAllowedDynamicFeature the same way. 因此,无论您如何注册,都可以以相同的方式注册RolesAllowedDynamicFeature There is scanning, where @Path and @Provider annotations are scanned for. 在扫描过程中,将扫描@Path@Provider批注。 If this is what you are current using, then just annotating the feature class with @Provider should register it. 如果这是当前使用的功能,则只需使用@Provider注释要素类@Provider注册它。 For example just having an empty Application subclass will cause scanning to happen 例如,只有一个空的Application子类将导致扫描发生

@ApplicationPath("/api")
public class RestApplication extends Application {}

Then there is explicit registration in your Application subclass. 然后,在您的Application子类中进行了显式注册。 For example 例如

@ApplicationPath("/api")
public class RestApplication extends Application {

    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> classes = new HashSet<>();
        classes.add(AuthenticationFilter.class);
        classes.add(RolesAllowedFeature.class);
        classes.add(SomeResource.class);
        return classes;
    }
}

Note that when doing this, you disable any scanning that goes on. 请注意,执行此操作时,将禁用所有继续进行的扫描。

So a couple other things to make sure after all the above is clear it still isn't working. 因此,通过其他几件事可以确保上述所有内容都清楚了,但仍然无法正常工作。

  1. Make sure your current AuthenticationFilter is annotated with @Priority(Priorities.AUTHENTICATION) . 确保使用@Priority(Priorities.AUTHENTICATION)注释当前的AuthenticationFilter This is to ensure that your authentication filter is called before the authorization filter. 这是为了确保在授权过滤器之前调用身份验证过滤器。 This needs to happen because the authentication filter is what sets the security context, and the authorization filter checks it. 这需要发生,因为身份验证过滤器是设置安全上下文的工具,而授权过滤器会对其进行检查。

  2. Make sure you are creating the security context correctly. 确保正确创建安全上下文。 The authorization filter will call the SecurityContext.isUserInRole(role) passing in roles from the @RolesAllowed annotation. 授权过滤器将调用SecurityContext.isUserInRole(role) ,以从@RolesAllowed批注中传入角色。 So you need to make sure to implements the isUserInRole correctly. 因此,您需要确保正确实现isUserInRole

如果我有积分,我会支持上面的帖子 xD

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

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