Java Wildfly中的仅承载身份验证,无需使用密钥斗篷

Java Wildfly中的仅承载身份验证,无需使用密钥斗篷,java,authentication,jax-rs,wildfly,bearer-token,Java,Authentication,Jax Rs,Wildfly,Bearer Token,我想在Wildfly中实现我自己的仅承载身份验证。本质上,我将执行以下步骤: 当我收到一个请求时,我会检查它是否有授权头 我获取令牌并对照数据库(在本例中,我将使用Redis)检查其有效性 我从数据库中获取该用户的角色 我希望能够在我的rest服务上使用@RolesAllowed注释 我该怎么做呢?我需要如何修改Wildfly配置文件?我需要实现哪些接口?如何将用户角色传递给安全上下文,以便Wildfly为我检查@RolesAllowed 如果回答,我是一个经验丰富的java程序员,但Wildf

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

  • 当我收到一个请求时,我会检查它是否有授权头

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

  • 我从数据库中获取该用户的角色

  • 我希望能够在我的rest服务上使用
    @RolesAllowed
    注释

  • 我该怎么做呢?我需要如何修改Wildfly配置文件?我需要实现哪些接口?如何将用户角色传递给安全上下文,以便Wildfly为我检查
    @RolesAllowed

    <>如果回答,我是一个经验丰富的java程序员,但Wildfly新手,所以你可以跳过编程逻辑的细节,而不是跳转配置。同样,在您的回答中,不要担心令牌最初是如何到达Redis的,也不要担心客户是如何获得它的

    编辑

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

    注意:我没有修改任何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());
        }
    }
    

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

    你可以看到。除了
    AnnotatedMethod
    类之外,它没有什么特别之处。为此,您只需使用
    java.lang.reflect.Method
    (resourceInfo.getResourceMethod())进行一些反射即可。除此之外,您几乎可以按原样复制代码。完成后,只需向应用程序注册
    RolesAllowedDynamicFeature
    。或者只是用要扫描的
    @Provider
    对其进行注释


    您还需要确保使用
    @Priority(Priorities.authentication)
    注释身份验证筛选器,以便在使用
    @Priority(Priorities.authentication)
    注释的授权筛选器之前调用它


    更新 这里是我链接到的代码的重构,所以它不使用特定于Jersey的类。
    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;
            }
        }
    }
    
    首先让我解释一下
    DynamicFeature
    是如何工作的。为此,我们首先将讨论的内容更改为您的
    AuthenticationFilter
    的当前实现

    现在,它是为每个请求处理的过滤器。但是假设我们引入了一个定制的
    @认证的
    注释

    @Target({METHOD, TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Authenticated{}
    
    我们可以使用这个注释来注释不同的方法和类。为了使过滤器只过滤被注释的方法和类,我们可以引入一个检查注释的
    DynamicFeature
    ,然后只在找到注释时注册过滤器。比如说

    @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());
            }
        } 
    }
    
    @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;
        }
    }
    
    一旦我们注册了这个
    AuthenticationDynamicFeature
    类,它将使它成为只过滤带有
    @Authenticated
    注释的方法和类

    或者,这甚至可以在过滤器内完成。我们可以从
    AuthenticationFilter
    中获取对
    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
    
    这样我们就可以完全忘记
    DynamicFeature
    。最好只使用
    DynamicFeature
    ,我只是举了一个示例进行演示

    但是也就是说,如果我们使用
    RolesAllowedDynamicFeature
    查看第一个代码块,您可以更好地了解发生了什么。它只为带有
    @RolesAllowed
    @DenyAll
    注释的方法和类注册筛选器。您甚至可以重构它,使所有注释逻辑都位于过滤器中,而不是特性中。你只有过滤器。就像我对上面的
    AuthenticationFilter
    示例所做的那样。同样,这只是为了举例

    现在,就注册
    DynamicFeature
    而言,其工作方式与注册任何其他资源类或提供程序类(例如,您的身份验证过滤器)相同。因此,无论您如何注册,只需以相同的方式注册
    RolesAllowedDynamicFeature
    。还有扫描,其中扫描
    @Path
    @Provider
    注释。如果这是您当前正在使用的,则仅使用
    @Provider
    注释要素类就应该注册它。例如,仅仅拥有一个空的
    应用程序
    子类就会导致扫描发生

    @ApplicationPath("/api")
    public class RestApplication extends Application {}
    
    然后在
    应用程序
    子类中显式注册。比如说

    @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());
            }
        } 
    }
    
    @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;
        }
    }
    
    @ApplicationPath(“/api”)
    公共类重新启动应用程序扩展了应用程序{
    @凌驾
    public Set>classes=new HashSet();
    类。添加(AuthenticationFilte)