簡體   English   中英

Wildfly 中僅承載身份驗證,不使用 Keycloak

[英]Bearer only authentication in Wildfly without using Keycloak

我想在 Wildfly 中自己實現 Bearer-only 身份驗證。 本質上,我將執行以下步驟:

  1. 當我收到一個請求時,我會檢查它是否有一個 Authorization 標頭。

  2. 我獲取令牌並檢查數據庫(在這種情況下,我將使用 Redis)檢查它的有效性。

  3. 我從數據庫中獲取該用戶的角色。

  4. 我希望能夠在我的休息服務上使用@RolesAllowed注釋。

我該怎么做? 如何修改 Wildfly 配置文件? 我需要實現哪些接口? 如何將用戶的角色傳遞給安全上下文,以便@RolesAllowed為我檢查 @RolesAllowed?

如果回答,請考慮我是一位經驗豐富的 Java 程序員,但對 Wildfly 不熟悉,因此您可以跳過有關編程邏輯的詳細信息,但不能跳過有關 Wildfly 配置的詳細信息。 同樣在您的回答中,不要擔心令牌首先是如何到達 Redis 的,或者客戶端是如何獲得它的。

編輯

這就是我所做的,但還沒有運氣。 我已經實現了一個實現ContainerRequestFilterAuthenticationFilter (我在下面只包括我已經實現的主要過濾器功能。請注意,有一些從數據庫中獲取角色的輔助功能不包括在內)。 即使在函數結束時,我使用用戶配置文件(包含角色)設置請求上下文的安全上下文,我也無法在我的 JAX-RS 休息服務上使用@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請求中檢索主體(我相信)。 我要做的只是實現您自己的授權過濾器,該過濾器將查找注釋並對照安全上下文中的主體進行檢查。

您可以看到Jersey如何實現它 除了AnnotatedMethod類之外,沒有什么東西是真正針對澤西島的。 為此,您可以使用java.lang.reflect.Method (resourceInfo.getResourceMethod())進行一些反射。 除此之外,您幾乎可以照原樣復制代碼。 完成后,只需向應用程序注冊RolesAllowedDynamicFeature 或者只是使用@Provider對其進行注釋以進行掃描。

您還需要確保使用@Priority(Priorities.AUTHENTICATION)注釋認證過濾器,以便在使用@Priority(Priorities.AUTHORIZATION)注釋的授權過濾器之前調用它。


UPDATE

這是我鏈接到的代碼的重構,因此它不使用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當前實現。

現在,它是為每個請求處理的過濾器。 但是,假設我們引入了自定義@Authenticated批注

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

一旦我們注冊了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注釋要素類@Provider注冊它。 例如,只有一個空的Application子類將導致掃描發生

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

然后,在您的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. 確保使用@Priority(Priorities.AUTHENTICATION)注釋當前的AuthenticationFilter 這是為了確保在授權過濾器之前調用身份驗證過濾器。 這需要發生,因為身份驗證過濾器是設置安全上下文的工具,而授權過濾器會對其進行檢查。

  2. 確保正確創建安全上下文。 授權過濾器將調用SecurityContext.isUserInRole(role) ,以從@RolesAllowed批注中傳入角色。 因此,您需要確保正確實現isUserInRole

如果我有積分,我會支持上面的帖子 xD

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM