Spring Security Keyclock springboot对于未经身份验证的请求返回403而不是401

gmxoilav  于 2023-08-05  发布在  Spring
关注(0)|答案(3)|浏览(123)

启用策略实施器配置时,Keyclock返回403,而不是401,用于未经身份验证的请求。删除策略实施器配置时,返回401。
这个配置我得到一个403空响应.

keycloak:
  realm: ${KEYCLOAK_REALM}
  auth-server-url: ${KEYCLOAK_AUTH_SERVER_URL}
  ssl-required: external
  resource: ${KEYCLOAK_CLIENT_ID}
  credentials.secret: ${KEYCLOAK_CLIENT_SECRET}
  use-resource-role-mappings: true
  cors: true
  public-client: false
  bearer-only: true
  policy-enforcer-config:
    lazy-load-paths: true
    http-method-as-scope: true
    path-cache-config:
      max-entries: 1000
      lifespan: 1000
    paths:
      - name: Insecure Resource
        path: /
        enforcement-mode: DISABLED
      - name: Swagger UI
        path: /swagger-ui/*
        enforcement-mode: DISABLED
      - name: Swagger Resources
        path: /swagger-resources/*
        enforcement-mode: DISABLED
      - name: Swagger api Resources
        path: /api-docs
        enforcement-mode: DISABLED
  securityConstraints:
    - authRoles:
       - '*'
      securityCollections:
        - name: protected
          patterns:
            - '/v1/*'
            - '/intranet/*'

字符串
如果像这样删除策略强制器

keycloak:
  realm: ${KEYCLOAK_REALM}
  auth-server-url: ${KEYCLOAK_AUTH_SERVER_URL}
  ssl-required: external
  resource: ${KEYCLOAK_CLIENT_ID}
  credentials.secret: ${KEYCLOAK_CLIENT_SECRET}
  use-resource-role-mappings: true
  cors: true
  public-client: false
  bearer-only: true
#  policy-enforcer-config:
#    lazy-load-paths: true
#    http-method-as-scope: true
#    path-cache-config:
#      max-entries: 1000
#      lifespan: 1000
#    paths:
#      - name: Insecure Resource
#        path: /
#        enforcement-mode: DISABLED
#      - name: Swagger UI
#        path: /swagger-ui/*
#        enforcement-mode: DISABLED
#      - name: Swagger Resources
#        path: /swagger-resources/*
#        enforcement-mode: DISABLED
#      - name: Swagger api Resources
#        path: /api-docs
#        enforcement-mode: DISABLED
  securityConstraints:
    - authRoles:
       - '*'
      securityCollections:
        - name: protected
          patterns:
            - '/v1/*'
            - '/intranet/*'


返回401

{
    "timestamp": "2021-10-05T11:25:33.116+0000",
    "status": 401,
    "error": "Unauthorized",
    "message": "No message available",
    "path": "/v1/approve-documents"
}


对所有请求执行策略,即使请求未经过身份验证或未经过身份验证。如果令牌无效或丢失,如何返回401。
完整代码https://github.com/prajintst/keyclock-permissions

0ve6wy6x

0ve6wy6x1#

根据Keycloak架构图,策略执行检查发生在授权/身份验证之前。因此,您无法使用策略强制实现预期的输出。x1c 0d1x的数据
我建议您使用策略评估者/提供者或使用基于角色的授权来实现这一点。

p4rjhz4m

p4rjhz4m2#

从这篇文章中看到我的答案:Spring安全插件应该响应401而不是403
它帮助我正确设置状态代码

8tntrjer

8tntrjer3#

一个简单但有效的解决方案。
在你的spring Boot 项目中创建一个包:

org.keycloak.adapters.tomcat

字符串
在上面的包中创建一个名为AbstractKeycloakAuthenticatorValve.java的JAVA文件,内容如下:

package org.keycloak.adapters.tomcat;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.catalina.*;
import org.apache.catalina.authenticator.FormAuthenticator;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.*;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.enums.TokenStore;
import org.springframework.http.MediaType;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.security.Principal;
import java.util.HashMap;
import java.util.Map;

public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticator implements LifecycleListener {
    public static final String TOKEN_STORE_NOTE = "TOKEN_STORE_NOTE";
    private static final Logger log = Logger.getLogger(AbstractKeycloakAuthenticatorValve.class);
    protected CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement();
    protected AdapterDeploymentContext deploymentContext;
    protected NodesRegistrationManagement nodesRegistrationManagement;

    public AbstractKeycloakAuthenticatorValve() {
    }

    public void lifecycleEvent(LifecycleEvent event) {
        if ("start".equals(event.getType())) {
            this.cache = false;
        } else if ("after_start".equals(event.getType())) {
            this.keycloakInit();
        } else if (event.getType() == "before_stop") {
            this.beforeStop();
        }

    }

    protected void logoutInternal(Request request) {
        KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
        if (ksc != null) {
            CatalinaHttpFacade facade = new OIDCCatalinaHttpFacade(request, (HttpServletResponse)null);
            KeycloakDeployment deployment = this.deploymentContext.resolveDeployment(facade);
            if (ksc instanceof RefreshableKeycloakSecurityContext) {
                ((RefreshableKeycloakSecurityContext)ksc).logout(deployment);
            }

            AdapterTokenStore tokenStore = this.getTokenStore(request, facade, deployment);
            tokenStore.logout();
            request.removeAttribute(KeycloakSecurityContext.class.getName());
        }

        request.setUserPrincipal((Principal)null);
    }

    protected void beforeStop() {
        if (this.nodesRegistrationManagement != null) {
            this.nodesRegistrationManagement.stop();
        }

    }

    public void keycloakInit() {
        String configResolverClass = this.context.getServletContext().getInitParameter("keycloak.config.resolver");
        if (configResolverClass != null) {
            try {
                KeycloakConfigResolver configResolver = (KeycloakConfigResolver)this.context.getLoader().getClassLoader().loadClass(configResolverClass).newInstance();
                this.deploymentContext = new AdapterDeploymentContext(configResolver);
                log.debugv("Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
            } catch (Exception var4) {
                log.errorv("The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", configResolverClass, var4.getMessage());
                this.deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
            }
        } else {
            InputStream configInputStream = getConfigInputStream(this.context);
            KeycloakDeployment kd;
            if (configInputStream == null) {
                log.warn("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
                kd = new KeycloakDeployment();
            } else {
                kd = KeycloakDeploymentBuilder.build(configInputStream);
            }

            this.deploymentContext = new AdapterDeploymentContext(kd);
            log.debug("Keycloak is using a per-deployment configuration.");
        }

        this.context.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), this.deploymentContext);
        AbstractAuthenticatedActionsValve actions = this.createAuthenticatedActionsValve(this.deploymentContext, this.getNext(), this.getContainer());
        this.setNext(actions);
        this.nodesRegistrationManagement = new NodesRegistrationManagement();
    }

    private static InputStream getJSONFromServletContext(ServletContext servletContext) {
        String json = servletContext.getInitParameter("org.keycloak.json.adapterConfig");
        if (json == null) {
            return null;
        } else {
            log.trace("**** using org.keycloak.json.adapterConfig");
            return new ByteArrayInputStream(json.getBytes());
        }
    }

    private static InputStream getConfigInputStream(Context context) {
        InputStream is = getJSONFromServletContext(context.getServletContext());
        if (is == null) {
            String path = context.getServletContext().getInitParameter("keycloak.config.file");
            if (path == null) {
                log.trace("**** using /WEB-INF/keycloak.json");
                is = context.getServletContext().getResourceAsStream("/WEB-INF/keycloak.json");
            } else {
                try {
                    is = new FileInputStream(path);
                } catch (FileNotFoundException var4) {
                    log.errorv("NOT FOUND {0}", path);
                    throw new RuntimeException(var4);
                }
            }
        }

        return (InputStream)is;
    }

    public void invoke(Request request, Response response) throws IOException, ServletException {
        CatalinaHttpFacade facade = new OIDCCatalinaHttpFacade(request, response);
        Manager sessionManager = request.getContext().getManager();
        CatalinaUserSessionManagementWrapper sessionManagementWrapper = new CatalinaUserSessionManagementWrapper(this.userSessionManagement, sessionManager);
        PreAuthActionsHandler handler = new PreAuthActionsHandler(sessionManagementWrapper, this.deploymentContext, facade);
        if (!handler.handleRequest()) {
            this.checkKeycloakSession(request, facade);
            super.invoke(request, response);
        }
    }

    protected abstract PrincipalFactory createPrincipalFactory();

    protected abstract boolean forwardToErrorPageInternal(Request var1, HttpServletResponse var2, Object var3) throws IOException;

    protected abstract AbstractAuthenticatedActionsValve createAuthenticatedActionsValve(AdapterDeploymentContext var1, Valve var2, Container var3);

    protected boolean authenticateInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
        CatalinaHttpFacade facade = new OIDCCatalinaHttpFacade(request, response);
        KeycloakDeployment deployment = this.deploymentContext.resolveDeployment(facade);
        if (deployment != null && deployment.isConfigured()) {
            AdapterTokenStore tokenStore = this.getTokenStore(request, facade, deployment);
            this.nodesRegistrationManagement.tryRegister(deployment);
            CatalinaRequestAuthenticator authenticator = this.createRequestAuthenticator(request, facade, deployment, tokenStore);
            AuthOutcome outcome = authenticator.authenticate();
            if (outcome == AuthOutcome.AUTHENTICATED) {
                return !facade.isEnded();
            } else {
                AuthChallenge challenge = authenticator.getChallenge();
                if (challenge != null) {
                    challenge.challenge(facade);
                }
                writeResponse(response, 401, "Failed to verify token");
                return false;
            }
        } else {
            facade.getResponse().sendError(401);
            return false;
        }
    }

    private void writeResponse(HttpServletResponse response, int status, String message) throws IOException {

        // Generate your intended response here, e.g.:
        ObjectMapper objectMapper = new ObjectMapper();
        response.setStatus(status);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        Map<String, String> body = new HashMap<>();
        body.put("message", message);
        response.getOutputStream().println(objectMapper.writerWithDefaultPrettyPrinter()
                .writeValueAsString(body));
    }

    protected CatalinaRequestAuthenticator createRequestAuthenticator(Request request, CatalinaHttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
        return new CatalinaRequestAuthenticator(deployment, tokenStore, facade, request, this.createPrincipalFactory());
    }

    protected void checkKeycloakSession(Request request, HttpFacade facade) {
        KeycloakDeployment deployment = this.deploymentContext.resolveDeployment(facade);
        AdapterTokenStore tokenStore = this.getTokenStore(request, facade, deployment);
        tokenStore.checkCurrentToken();
    }

    public void keycloakSaveRequest(Request request) throws IOException {
        this.saveRequest(request, request.getSessionInternal(true));
    }

    public boolean keycloakRestoreRequest(Request request) {
        try {
            return this.restoreRequest(request, request.getSessionInternal());
        } catch (IOException var3) {
            throw new RuntimeException(var3);
        }
    }

    protected AdapterTokenStore getTokenStore(Request request, HttpFacade facade, KeycloakDeployment resolvedDeployment) {
        AdapterTokenStore store = (AdapterTokenStore)request.getNote("TOKEN_STORE_NOTE");
        if (store != null) {
            return store;
        } else {
            Object store1;
            if (resolvedDeployment.getTokenStore() == TokenStore.SESSION) {
                store1 = this.createSessionTokenStore(request, resolvedDeployment);
            } else {
                store1 = new CatalinaCookieTokenStore(request, facade, resolvedDeployment, this.createPrincipalFactory());
            }

            request.setNote("TOKEN_STORE_NOTE", store1);
            return (AdapterTokenStore)store1;
        }
    }

    private AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) {
        AdapterTokenStore store = new CatalinaSessionTokenStore(request, resolvedDeployment, this.userSessionManagement, this.createPrincipalFactory(), this);
        return store;
    }
}


基本上,我们正在修改Keycloak中的AbstractKeycloakAuthenticatorValve文件,以处理失败的身份验证。

相关问题