带有 Keycloak 的 Spring 应用程序返回 401 错误

Posted

技术标签:

【中文标题】带有 Keycloak 的 Spring 应用程序返回 401 错误【英文标题】:Spring app with Keycloak returns 401 error 【发布时间】:2019-12-19 23:46:24 【问题描述】:

我正在尝试通过 Keycloak 访问 Spring App,但我总是收到 401 Unauthorized 错误。基本上我有一个单独工作的聊天模块,但是一旦我添加了 Keycloak,由于 401 错误,我无法访问该应用程序。 我已经关注了大约 3 个教程,这些教程显示了与我所做的类似的事情,但我仍然不知道我做错了什么。

这是我应用的配置:

keycloak: 
    enabled: true
    realm: myReal
    resource: myReal-api
    public-client: true
    bearer-only: true
    auth-server-url: http://localhost:8080/auth
    credentials:
      secret: 82eXXXXX-3XXX-4XXX-XXX7-287aXXXXXXXX
    principal-attribute: preferred_username
    cors: true

来自localhost:port/ 我有第一个接口(没有Keycloak 安全),它有一个指向我的服务的链接,即localhost:port/index/topicName。现在,当我单击该链接时,我应该得到 Keycloak 身份验证屏幕,但我得到了 401 错误。

我检查了请求的标头,将 HttpServletRequest 作为参数添加到我的 displayMessage 方法中,实际上我可以在我的 IDE 控制台中显示 access_token 和 X-Auth-Token。但似乎当我点击该链接时,它发送的请求没有令牌。

这是我的控制器方法(我的 Controller 类用 @Controller 注释:

@GetMapping(path = "/")
    public String index() 
        return "external";
    

    @GetMapping(path = "/index/topicName",
            produces = MediaType.APPLICATION_JSON_VALUE)
    public String displayMessages(Model model, 
            @PathVariable String topicName)        

            //HttpHeaders headers = new HttpHeaders();

            //headers.set("Authorization", request.getHeader("Authorization"));
            //header = request.getHeader("Authorization");
            //System.out.println(" T O K E N "+request.getHeader("X-Auth-Token"));

            projectServiceImpl.findByName(topicName);
            List<Message> messages = messageServiceImpl.findAllMessagesByProjectName(topicName);
            model.addAttribute("topic", topicName);
            model.addAttribute("message",messages);

            return "index";
    

我的 Keycloak 配置文件的灵感来自于我读过的 tuto,因此其中可能存在我不知道的错误(不确定方法 accesshasRole 之间的区别是什么):

@Configuration
@ComponentScan(
        basePackageClasses = KeycloakSecurityComponents.class,
        excludeFilters = @ComponentScan.Filter(
                type = FilterType.REGEX, 
                pattern = "org.keycloak.adapters.springsecurity.management.HttpSessionManager"))
@EnableWebSecurity
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter 


    private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);

    @Bean
    public HttpSessionIdResolver httpSessionIdResolver()  //replace HttpSessionStrategy
        return HeaderHttpSessionIdResolver.xAuthToken();
    

    //Registers the KeycloakAuthenticationProvider with the authentication manager.
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 

        try 
            SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
            grantedAuthorityMapper.setPrefix("ROLE_");
            grantedAuthorityMapper.setConvertToUpperCase(true);

            KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
            keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);

            auth.authenticationProvider(keycloakAuthenticationProvider());

         catch(Exception ex)  
            logger.error("SecurityConfig.configureGlobal: " + ex);

        

         /*try 
                KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
                keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
                auth.authenticationProvider(keycloakAuthenticationProvider);
            catch(Exception ex)
                logger.error("SecurityConfig.configureGlobal: " +ex);
            */
    

    //Load Keycloak properties from service config-file
    @Bean
    public KeycloakSpringBootConfigResolver KeycloakConfigResolver() 
        return new KeycloakSpringBootConfigResolver();
    

    //Defines the session authentication strategy.
    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() 
        //Public or Confidential application keycloak/OpenID Connect client
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
        //Bearer mode only keycloak/OpenID Connect client without keycloak session -> stateless behavior
        //return new NullAuthenticatedSessionStrategy();
    


    @Override
    protected void configure(HttpSecurity http) throws Exception
    
        super.configure(http);
        http.authorizeRequests()
        //BEGIN
            //USER -done to be tested

            .antMatchers(HttpMethod.GET,"/index**").access("hasAuthority('ADMIN')")
            .antMatchers(HttpMethod.GET,"/").access("hasAuthority('ADMIN')")
            .antMatchers(HttpMethod.GET,"/").access("hasAnyAuthority('ADMIN','MANAGER','EXPERT','STANDARD')")



            .anyRequest().authenticated() 

            .and()
            .cors()
            .and()
            .csrf().disable()
            //BEGIN Login/Logout
            .formLogin()
                .permitAll()//.successHandler(authenticationSuccessHandler) //
            .and()
            .logout()//.clearAuthentication(true) //Add .clearAuthentication(true) to logout()
                //.logoutUrl("/custom-logout")
                .addLogoutHandler(keycloakLogoutHandler())
                //.addLogoutHandler(new LogoutHandlerImpl())
                .clearAuthentication(true)
                .invalidateHttpSession(true)
                .permitAll();
            //END Login/Logout

        //BEGIN Session
        http
        .sessionManagement()
            //.sessionCreationPolicy(SessionCreationPolicy.ALWAYS) //BY default IF_REQUIRED
            .maximumSessions(1)
                .maxSessionsPreventsLogin(false) // if true generate an error when user login after reaching maximumSession (SessionAuthenticationStrategy rejected the authentication object / SessionAuthenticationException: Maximum sessions of 1 for this principal exceeded)
                //.expiredUrl("/auth/login")
                .sessionRegistry(sessionRegistry());   



    

     @Bean
     @Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
     public AccessToken accessToken() 
         HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
         return ((KeycloakSecurityContext) ((KeycloakAuthenticationToken) request.getUserPrincipal()).getCredentials()).getToken();
     

    ///BEGIN session     
     @Bean
     public SessionRegistry sessionRegistry() 
        SessionRegistry sessionRegistry = new SessionRegistryImpl();
        return sessionRegistry;
     


    @Bean
    public RegisterSessionAuthenticationStrategy registerSessionAuthStr( ) 
        return new RegisterSessionAuthenticationStrategy( sessionRegistry( ) );
    

    // Register HttpSessionEventPublisher
    @Bean
    public static ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() 
        return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
    

我真的不知道我还应该改变什么才能让它工作,但我相信那里一定有什么问题。但我想如果我在尝试访问我的服务时可以有 Keycloak 身份验证屏幕,那就没问题了。

【问题讨论】:

“myReal-api”的配置呢,是否定义了Valid Redirect URIs 我将“访问类型”设置为bearer only。在这种情况下,您不必设置有效的重定向 URI,因此我不必配置该选项 访问类型附近的工具提示说:"Bearer-only" clients are web services that never initiate login。但是您实际上是在尝试启动登录并执行 SSO。 SSO 使用访问类型为我工作:机密并正确设置重定向 URI。 谢谢,我没有注意到这个细节。但是为什么我不需要登录就无法访问我的应用程序?我注意到了别的东西,我刚刚从我的配置中注释了bearer-only: true 行,重新启动我的应用程序会导致Bearer-only applications are not allowed to initiate browser login 错误消息。我是否必须取消注释(考虑到如果我这样做,我会收到 401 错误)? 所以您在应用程序中注释了 bearer-only,但是您是否从 Keycloak 管理控制台更改了访问类型并将其设置为机密(并添加了 URI)? 【参考方案1】:

我收到了同样的错误,要仔细检查的一件事是auth-server-url 与服务器相同,而客户端获取令牌。

即如果一个是 dns 名称,一个是 IP 地址,它将不授权。 (在我的情况下,我有 localhost 和 127.0.0.1 所以授权失败)

服务器,src/main/resources/application.yml

邮递员/客户:

【讨论】:

敲了几个小时后,我终于找到了这个完美的答案。非常感谢。 嗨,非常感谢!这解决了我的问题。 我在这上面浪费了几个小时,谢谢伙计!【参考方案2】:

这解决了我的问题。我在 docker 容器中使用,必须同时匹配 host.docker.internal

【讨论】:

以上是关于带有 Keycloak 的 Spring 应用程序返回 401 错误的主要内容,如果未能解决你的问题,请参考以下文章

带有 keycloak 的 Spring Cloud 微服务

带有 keycloak 设置的 Spring Security (HttpSecurity)

带有嵌入式 keycloak 和 spring boot 应用程序的默认配置

带有 Keycloak 的 Spring Security OAuth2 - 访问用户信息

带有 Keycloak 的 Angular 和 Spring Boot REST API 的 CORS 问题

带有 Keycloak 的 Angular 和 Spring Boot REST API 的 CORS 问题