关于身份验证:受Keycloak保护的Spring Boot应用程序:尝试配置可公开访问的页面

Keycloak secured Spring Boot Application: Try to configure publicly available pages

我们有一个Keycloak服务器,用于保护我们的Spring Boot应用程序。到目前为止,一切正常。但是,我们现在需要一个忘记密码页面,该页面必须无需登录即可访问。我们无法做到这一点。
我们正在实现KeycloakWebSecurityConfigurerAdapter并覆盖configure(HttpSecurity)方法。实现看起来像这样:

1
2
3
4
5
6
super.configure(http);
http.csrf().disable()
    .exceptionHandling()
    .accessDeniedPage("/accessDenied");
http.anonymous.disable();
http.authorizeRequests();

仅使用该代码,实际上除根页面外,每个页面都可以自由访问。一旦我们添加对antMatcher()anyRequest()方法的调用,后跟permitAll()fullyAuthenticated()的调用,只是为了实现允许和不允许页面的区分,所有页面都将得到保护/禁止。我们进行了很多次尝试,试图在这里和其他任何地方寻求帮助,但没有找到解决方案。当前实现的示例是:

1
2
http.authorizeRequests().antMatchers(HttpMethod.GET,"/public/forgotPassword").permitAll()
        .anyRequest().fullyAuthenticated();

如上所述,结果是每个页面都需要身份验证,也需要public / forgotPassword页面。
有谁知道可能是什么问题?

提前谢谢!


我已经实现了springboot.keycloak.mre1,以简化的方式演示了我以前从事的项目如何类似地实现了我认为您所要求的内容。
简而言之,解决方案的要点是...

1
2
3
4
5
6
7
8
9
10
11
12
13
14

public class SecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
       
        super.configure(http);
   
            http.authorizeRequests().antMatchers("/login","/login.html")
                   .permitAll().antMatchers("/dashboard","/dashboard.html")
                     .authenticated();              
    }
    …
}

构建和运行MRE的步骤很简单。但是,如果您在构建或运行它时遇到困难,请告诉我是否可以提供任何帮助。

如果我完全误解了您的要求,请随时克隆和修改项目,使其更像您的用例。如果您随后上传修改,并在存储库的"问题"区域中详细说明用例的细节,我将进行调查并与您联系。

1MRE使用docker-compose,因为其原始项目基于其基础。


在我的应用程序中,我正在使用以下配置方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.wissance.orgstructure.application.configuration;

import com.goodt.drive.goals.application.authentication.AppAuthenticationEntryPoint;
import com.goodt.drive.goals.application.services.users.KeyCloakUserInfoExtractorService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.resource.PrincipalExtractor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;


@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
       
        http.headers().frameOptions().sameOrigin();  // it is to fix issue with h2-console access
        http.cors();
        http.csrf().disable()
            .authorizeRequests().antMatchers("/","/callback","/login**","/webjars/**","/error**").permitAll()
            .and()
            .authorizeRequests().antMatchers("/api/**").authenticated()
            .and()
            .authorizeRequests().antMatchers("/h2-console/**").permitAll()
            .and()
            .authorizeRequests().antMatchers("/swagger-ui.html").permitAll()
            .and()
            .authorizeRequests().antMatchers("/swagger-ui/**").permitAll()
            .and()
            .exceptionHandling().authenticationEntryPoint(new AppAuthenticationEntryPoint())
            .and()
            .logout().permitAll().logoutSuccessUrl("/");
    }
   
    @Bean
    public PrincipalExtractor getPrincipalExtractor(){
        return new KeyCloakUserInfoExtractorService();
    }
   
    @Autowired
    private ResourceServerTokenServices resourceServerTokenServices;
}

@ControllerAdvice
public class AppAuthenticationEntryPoint implements AuthenticationEntryPoint{

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        // 401
        logger.debug(String.format("Access to resource is denied (401) for request: "%s" message: "%s"", request.getRequestURL(), authException.getMessage()));
        setResponseError(response, HttpServletResponse.SC_UNAUTHORIZED,"Authentication Failed");
    }
   
    @ExceptionHandler (value = {AccessDeniedException.class})
    public void commence(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        // 403
        logger.debug(String.format("Access to resource is forbidden (403) for request: "%s" message: "%s"", request.getRequestURL(), accessDeniedException.getMessage()));
        setResponseError(response, HttpServletResponse.SC_FORBIDDEN, String.format("Access Denies: %s", accessDeniedException.getMessage()));
    }
   
    @ExceptionHandler (value = {NotFoundException.class})
    public void commence(HttpServletRequest request, HttpServletResponse response, NotFoundException notFoundException) throws IOException {
        // 404
        logger.debug(String.format("Object was not found (404) for request: "%s" message: "%s"", request.getRequestURL(), notFoundException.getMessage()));
        setResponseError(response, HttpServletResponse.SC_NOT_FOUND, String.format("Not found: %s", notFoundException.getMessage()));
    }
   
    @ExceptionHandler (value = {Exception.class})
    public void commence(HttpServletRequest request, HttpServletResponse response, Exception exception) throws IOException {
        logger.error(String.format("An error occurred during request: %s %s error message: %s",
                     request.getMethod(), request.getRequestURL(), exception.getMessage()));
        // 500
        setResponseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, String.format("Internal Server Error: %s", exception.getMessage()));
    }
   
    private void setResponseError(HttpServletResponse response, int errorCode, String errorMessage) throws IOException{
        response.setStatus(errorCode);
        response.getWriter().write(errorMessage);
        response.getWriter().flush();
        response.getWriter().close();
    }
   
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
}

下面列出了与KeyCloak相关的spring security(application-local.yml)的

Config,在我的应用中,我至少有3个不同的keycloak服务器,并且不时切换它们,我所有的KeyCloak值都从基本设置传递( application.yml)当前在appConfig.keyCloak.using中定义为选定密钥斗篷的yml占位符? Spring Security配置部分的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
security:
  basic:
    enabled: false
  oauth2:
    client:
      clientId: ${appConfig.keyCloak.using.clientId}
      clientSecret: ${appConfig.keyCloak.using.clientSecret}
      accessTokenUri: ${appConfig.keyCloak.using.baseUrl}/protocol/openid-connect/token
      userAuthorizationUri: ${appConfig.keyCloak.using.baseUrl}/protocol/openid-connect/auth
      authorizedGrantTypes: code token
      scope: local
      username: ${appConfig.keyCloak.using.serviceUsername}
      password: ${appConfig.keyCloak.using.servicePassword}
    resource:
      userInfoUri: ${appConfig.keyCloak.using.baseUrl}/protocol/openid-connect/userinfo

KeyCloak服务器配置之一的示例:

1
2
3
4
5
      baseUrl: http://99.220.112.131:8080/auth/realms/master
      clientId: api-service-agent
      clientSecret: f4901a37-efda-4110-9ba5-e3ff3b221abc
      serviceUsername: api-service-agent
      servicePassword: x34yui9034*&1

在上面的示例中,URL中具有/api路径的所有页面(即/api/employee/api/employee/find/?或其他页面)只有在经过身份验证授权后才能访问。所有Swaggers页面或登录页面无需任何身份验证即可使用。