代码之家  ›  专栏  ›  技术社区  ›  Jadiel de Armas

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

  •  1
  • Jadiel de Armas  · 技术社区  · 9 年前

    我想在Wildfly中实现我自己的仅承载身份验证。本质上,我将执行以下步骤:

    1. 当我收到请求时,我会检查它是否有Authorization标头。

    2. 我获取令牌,并对照数据库(在本例中,我将使用Redis)检查其有效性。

    3. 我从数据库中获取该用户的角色。

    4. 我希望能够使用 @RolesAllowed 我的休息服务注释。

    我该怎么做?我需要如何修改Wildfly配置文件?我需要实现哪些接口?如何将用户的角色传递给安全上下文,以便Wildfly执行 @允许的角色 帮我检查一下?

    如果回答,请考虑我是一名经验丰富的Java程序员,但对Wildfly是新手,因此您可以跳过编程逻辑的详细信息,而不是Wildfly配置。同样,在您的回答中,不要担心令牌最初是如何到达Redis的,或者客户端是如何获得它的。

    编辑

    这是我所做的,但还没有运气。我已经实施了 AuthenticationFilter 实现 ContainerRequestFilter 。(我在下面只包括我已经实现的主筛选器函数。注意,有一些助手函数可以从数据库中获取未包含的角色)。即使在函数结束时,我用用户配置文件(包含角色)设置了请求上下文的安全上下文,我也无法使用 @允许的角色 JAX-RS rest服务上的注释。我该怎么做?

    注意:我没有修改任何Wildfly配置文件或web。xml文件。我知道每个请求都会调用过滤器,因为我能够在每个请求上记录来自过滤器的消息。

    /** 
     * (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());
        }
    }
    
    1 回复  |  直到 9 年前
        1
  •  2
  •   Community PPrice    4 年前

    是的,我不确定它在EE环境中如何工作,甚至将资源类变成无状态bean。这个 @RolesAllowed 注释用于ejb。在这种情况下,将从servlet请求中检索主体(我相信)。我要做的只是实现您自己的授权过滤器,该过滤器查找注释并在安全上下文中检查主体。

    你可以看到 how Jersey implements it 。除了 AnnotatedMethod 班为此,你可以用 java.lang.reflect.Method (resourceInfo.getResourceMethod())。除此之外,您几乎可以原样复制代码。完成后,只需注册 RolesAllowedDynamicFeature 使用应用程序。或者用 @Provider 以进行扫描。

    您还需要确保您的身份验证过滤器带有注释 @Priority(Priorities.AUTHENTICATION) 以便在授权过滤器之前调用它 @Priority(Priorities.AUTHORIZATION) .


    更新

    这里是我链接到的代码的重构,所以它不使用Jersey特定的类。这个 注释的方法 刚更改为 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;
            }
        }
    }
    

    首先让我解释一下 DynamicFeature 作品为此,我们首先将讨论的上下文更改为您当前的 AuthenticationFilter .

    现在,它是为每个请求处理的过滤器。但假设我们引入了一种习俗 @Authenticated 注释

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

    我们可以使用这个注释来注释不同的方法和类。为了使过滤器仅过滤带注释的方法和类,我们可以引入 动态功能 它检查注释,然后仅在找到注释时注册过滤器。例如

    @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());
            }
        } 
    }
    

    一旦我们注册了这个 AuthenticationDynamicFeature 类,它将使其仅使用 @已验证 将被过滤。

    或者,甚至可以这样做 在内部 过滤器。我们可以参考 ResourceInfo 来自 身份验证筛选器 。例如,检查注释,如果不存在,则继续。

    @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
    

    这样我们就可以完全忘记 动态功能 。最好只使用 动态功能 ,我只是举一个例子来演示。

    尽管如此,如果我们用 角色允许的动态功能 ,您可以更好地了解发生了什么。它只为带注释的方法和类注册筛选器 @允许的角色 @DenyAll 。您甚至可以对其进行重构,使其在过滤器中包含所有注释逻辑,而不是功能。你只有过滤器。就像我对 身份验证筛选器 上面的示例。同样,这只是出于示例目的。

    现在,就 动态功能 ,它的工作方式与注册任何其他资源类或提供程序类(例如身份验证筛选器)相同。因此,无论您如何注册这些,只需注册 角色允许的动态功能 同样的方式。有扫描,其中 @Path @提供商 扫描批注。如果这是您当前使用的,则只需使用 @提供商 应将其注册。例如,只有一个空 Application 子类将导致扫描发生

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

    然后在您的 应用 子类。例如

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

    请注意,在执行此操作时,您将禁用任何正在进行的扫描。

    因此,在上述情况清楚之后,需要做一些其他事情来确保它仍然不起作用。

    1. 确保您的当前 身份验证筛选器 注释为 @优先级(优先级.认证) 。这是为了确保在授权筛选器之前调用身份验证筛选器。这需要发生,因为身份验证过滤器设置安全上下文,而授权过滤器检查它。

    2. 确保正确创建了安全上下文。授权筛选器将调用 SecurityContext.isUserInRole(role) @允许的角色 注释。因此,您需要确保实现 isUserInRole 正确地