簡體   English   中英

使用 spring 引導自定義 keycloak 錯誤頁面

[英]Customize keycloak error page with spring boot

我使用keycloak-spring-boot-starter來保護我的休息服務免受未經授權的訪問。 身份驗證按預期工作,但如果身份驗證失敗,則返回空響應。

但是,我想返回類似於我所有其他錯誤處理程序的 json 錯誤響應。 我已經嘗試定義@ExceptionHandler(Throwable.class)ErrorControllerErrorViewResolver或通過WebServerCustomizer配置ErrorPage ,但這根本不起作用。

我很好,如果我可以為它定義一個 static 響應。

似乎有一個名為delegateBearerErrorResponseSending的屬性,但我找不到在哪里設置它。 它不存在於 spring-boot 的屬性中。 我什至不確定電話將被委派到哪里。

有一個名為policy-enforcer-config.on-deny-redirect-to的屬性,但重定向不是 rest 服務的預期行為。

  • spring-boot:2.3.1.RELEASE
  • keycloak-spring-boot-starter:10.0.2

TLDR:如何配置/自定義 keycloak 的錯誤頁面。

我在當前的 spring-security-web (5.4+) 版本中找到了一種在一定程度上做到這一點的方法。

/**
 * A {@link RequestRejectedHandler} for spring security web's application firewall.
 */
@Component
public class FirewallRequestRejectedHandler implements RequestRejectedHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(FirewallRequestRejectedHandler.class);

    @Override
    public void handle(
            final HttpServletRequest request,
            final HttpServletResponse response,
            final RequestRejectedException requestRejectedException) throws IOException {

        // Optionally write a warning to the logs
        LOGGER.warn("Application firewall: {}", requestRejectedException.getMessage(),
                LOGGER.isDebugEnabled() ? requestRejectedException : null);

        // Make the exception accessible to the ErrorController
        request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, requestRejectedException );

        // Call error controller
        response.sendError(401, "Access denied");
    }

}

這將調用包含我的后備錯誤處理邏輯的ErrorController ,從而發送正確的響應。

朋友你好!覆蓋 class 方法challengeResponse()得到解決

org.keycloak.adapters.BearerTokenRequestAuthenticator.challengeResponse( HttpFacade facade, final OIDCAuthenticationError.Reason reason, final String error, final String description )

這就是我增強keycloak-json-response-spring-boot-starter

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-json-response-spring-boot-starter</artifactId>
    <version>18.0.0</version>
    <name>keycloak-json-response-spring-boot-starter</name>
    <description>keycloak-json-response-spring-boot-starter</description>
    
    <!-- Omit some configuration ... -->

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.keycloak.bom</groupId>
                <artifactId>keycloak-adapter-bom</artifactId>
                <version>${keycloak.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

    <!-- Omit some configuration ... -->

創建相同的 package 和 class 然后在方法中嵌入代碼

package org.keycloak.adapters;

@SuppressWarnings( "unused" )
public class BearerTokenRequestAuthenticator {

    protected AuthChallenge challengeResponse( HttpFacade facade, final OIDCAuthenticationError.Reason reason, final String error, final String description ) {
        StringBuilder header = new StringBuilder( "Bearer realm=\"" );
        header.append( deployment.getRealm( ) ).append( "\"" );

        // addon begin
        Map< String, String > responseBody = new LinkedHashMap<>( );
        HttpFacade.Response response = facade.getResponse( );
        // addon end

        // addon begin
        if ( reason == OIDCAuthenticationError.Reason.NO_BEARER_TOKEN ) {
            response.setStatus( 401 );
            responseBody.put( "error", "unauthorized" );
            responseBody.put( "error_description", "Not Token! To login." );
        }
        // addon end

        if ( error != null ) {
            header.append( ", error=\"" ).append( error ).append( "\"" );
            // addon begin
            responseBody.put( "error", error );
            response.setStatus( 403 );
            // addon end
        }
        if ( description != null ) {
            header.append( ", error_description=\"" ).append( description ).append( "\"" );
            // addon begin
            responseBody.put( "error_description", description );
            response.setStatus( 403 );
            // addon end
        }
        final String challenge = header.toString( );

        // addon begin
        try {
            if ( !responseBody.isEmpty( ) ) {
                response.setHeader( "Content-Type", "application/json" );
                OutputStream responseOutputStream = response.getOutputStream( );
                responseOutputStream.write( new ObjectMapper( ).writeValueAsBytes( responseBody ) );
                responseOutputStream.flush( );
            }
        } catch ( IOException e ) {
            e.printStackTrace( );
        }
        // addon end


        return new AuthChallenge( ) {
            @Override
            public int getResponseCode( ) {
                return 401;
            }

            @Override
            public boolean challenge( HttpFacade facade ) {
                if ( deployment.getPolicyEnforcer( ) != null ) {
                    deployment.getPolicyEnforcer( ).enforce( ( OIDCHttpFacade ) facade );
                    return true;
                }
                OIDCAuthenticationError error = new OIDCAuthenticationError( reason, description );
                facade.getRequest( ).setError( error );
                facade.getResponse( ).addHeader( "WWW-Authenticate", challenge );
                if ( deployment.isDelegateBearerErrorResponseSending( ) ) {
                    facade.getResponse( ).setStatus( 401 );
                } else {
                    facade.getResponse( ).sendError( 401 );
                }
                return true;
            }
        };
    }

}

不要忘記復制 class 中代碼的 rest 並將您的keycloak-spring-boot-starter替換為keycloak-json-response-spring-boot-starter

我不知道最近的 keycloak/spring-boot 版本是否發生了變化,但現在我必須在org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter中配置以下內容:

    @Override
    protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
        final KeycloakAuthenticationProcessingFilter filter = super.keycloakAuthenticationProcessingFilter();
        // Don't run for error handling
        filter.setRequiresAuthenticationRequestMatcher(new AndRequestMatcher(
                KeycloakAuthenticationProcessingFilter.DEFAULT_REQUEST_MATCHER,
                new NegatedRequestMatcher(new AntPathRequestMatcher(ERROR_HANDLING_PATH))));
        
        return filter;
    }
  • spring-boot 2.6
  • 鑰匙斗篷 16

暫無
暫無

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

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