programing

스프링 부트(보안) 및 키클로크로 역할 인증을 사용하시겠습니까?

newsource 2023. 7. 9. 11:12

스프링 부트(보안) 및 키클로크로 역할 인증을 사용하시겠습니까?

저는 간단한 일을 하려고 합니다.

단일 엔드포인트에 요청을 하고 클라이언트에서 베어러 토큰을 보내려면 엔드포인트에서 키클록 수락/거부 요청에 할당된 역할에 따라 이 토큰의 유효성을 확인해야 합니다.

저는 많은 튜토리얼과 심지어 책들을 따라다녔지만 대부분 이해할 수 없습니다.

다음을 수행하여 내 키클록 정보(역할, 사용자)를 설정했습니다. https://medium.com/ @bcarunmail/filen-rest-api-using-keyclock-and-spring-oauth2-6ddf3a1efcc2

그렇게,

저는 기본적으로 특정 역할 "사용자"를 가진 사용자인 클라이언트와 키클록을 설정하고 다음과 같이 구성했습니다.

@Configuration
@KeycloakConfiguration
//@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class SecurityConf extends KeycloakWebSecurityConfigurerAdapter
{
    /**
     * Registers the KeycloakAuthenticationProvider with the authentication manager.
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(keycloakAuthenticationProvider());
    }

    /**
     * Defines the session authentication strategy.
     */
    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Bean
    public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

    @Bean
    public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(
            KeycloakAuthenticationProcessingFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(
            KeycloakPreAuthActionsFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        super.configure(http);
        http
                .authorizeRequests()
                .antMatchers("/user/*").hasRole("admin")
                .antMatchers("/admin*").hasRole("user")

    }
}

많은 튜토리얼에서 마지막 규칙으로 표시되는 이유를 이해할 수 없습니다.

.anyRequest().permitAll();

기본적으로 보안이 없다고 설정하면 베어러 토큰 없이 엔드포인트를 호출할 수 있습니다.

하지만 이것을 마지막 규칙으로 추가할 때

 .anyRequest().denyAll();

저는 항상 403점을 받습니다.

디버깅을 통해 다음을 찾았습니다.

인증 처리 요청입니다.

f.KeycloakAuthenticationProcessingFilter : Attempting Keycloak authentication
o.k.a.BearerTokenRequestAuthenticator    : Found [1] values in authorization header, selecting the first value for Bearer.
o.k.a.BearerTokenRequestAuthenticator    : Verifying access_token
o.k.a.BearerTokenRequestAuthenticator    : successful authorized
a.s.a.SpringSecurityRequestAuthenticator : Completing bearer authentication. Bearer roles: [] 
o.k.adapters.RequestAuthenticator        : User 'testuser' invoking 'http://localhost:9090/api/user/123' on client 'users'
o.k.adapters.RequestAuthenticator        : Bearer AUTHENTICATED
f.KeycloakAuthenticationProcessingFilter : Auth outcome: AUTHENTICATED
o.s.s.authentication.ProviderManager     : Authentication attempt using org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider
o.s.s.core.session.SessionRegistryImpl   : Registering session 5B871A0E2AF55B70DC8E3B7436D79333, for principal testuser
f.KeycloakAuthenticationProcessingFilter : Authentication success using bearer token/basic authentication. Updating SecurityContextHolder to contain: org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken@355f68d6: Principal: testuser; Credentials: [PROTECTED]; Authenticated: true; Details: org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount@5d7a32a9; Not granted any authorities
[nio-9090-exec-3] o.s.security.web.FilterChainProxy        : /api/user/123 at position 8 of 15 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
nio-9090-exec-3] o.s.s.w.s.DefaultSavedRequest            : pathInfo: both null (property equals)
[nio-9090-exec-3] o.s.s.w.s.DefaultSavedRequest            : queryString: both null (property equals)

무기명 역할이 없는 것 같은데요...

내 종속성:

        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-boot-starter</artifactId>
            <version>6.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-security-adapter</artifactId>
            <version>6.0.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

내 문제?

액세스 토큰 전송을 요청합니다.

client_id -> my client from keycloak
username -> my user from keycloak
password -> my password from keycloak
grant_type -> password
client_secret -> from keycloak

저는 토큰을 받은 다음 제 약속 장소에 요청할 때 사용합니다.내 요청은 사용하는 엔드포인트(역할 사용자 또는 역할 관리자)에 관계없이 항상 유효합니다.

내 소유지에는 다음과 같은 것이 있습니다.

keycloak:
  auth-server-url: http://localhost:8080/auth/
  resource: users-api
  credentials:
    secret : my-secret
  use-resource-role-mappings : true
  realm: my-realm
  realmKey:  my-key
  public-client: true
  principal-attribute: preferred_username
  bearer-only: true

이 경우에 실제로 역할을 활성화하는 방법이 있습니까?

JWT를 사용하도록 클라이언트를 구성해야 합니까?무슨 생각이 있습니까?

엔드포인트에 주석도 추가했습니다.

@Secured("admin")
@PreAuthorize("hasAnyAuthority('admin')")

하지만 그들은 아무것도 하지 않는 것 같아요...

편집 --

URL을 리소스와 일치하도록 수정한 후에도 403이 표시됩니다.

"realm_access": {
    "roles": [
      "offline_access",
      "admin",
      "uma_authorization"
    ]
  },
  "resource_access": {
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },

resource_access와 내 문제가 관련이 있습니까?

디버스: 요./api/user/123보안 구성에서 보안을 유지할 수 있습니다./user/*보안을 다음으로 변경합니다.

.antMatchers("/api/user/*").hasRole("user")
                .antMatchers("/api/admin*").hasRole("admin")

추신: 등록할 필요가 없습니다.KeycloakAuthenticationProcessingFilter그리고.KeycloakPreAuthActionsFilter

오래된 게시물인 건 알지만, 다른 사람들도 같은 문제가 생길 경우를 대비해 나중에 참고하기 위해 이 글을 씁니다.

로그를 보면 키클록이 액세스 토큰을 성공적으로 인증했지만 부여된 권한이 없습니다.Spring이 요청을 승인하지 않고 HTTP 403 Forbidden:

f.KeycloakAuthenticationProcessingFilter : Authentication success using bearer token/basic authentication. Updating SecurityContextHolder to contain: org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken@355f68d6: Principal: testuser; Credentials: [PROTECTED]; Authenticated: true; Details: org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount@5d7a32a9; Not granted any authorities

이는 Keyclock 어댑터가 영역 수준 역할 매핑 대신 리소스(즉, 클라이언트 수준) 역할 매핑을 사용하도록 구성되었기 때문입니다.

use-resource-role-message:true로 설정하면 어댑터가 토큰 내부에서 사용자에 대한 애플리케이션 수준 역할 매핑을 찾습니다.false인 경우 사용자 역할 매핑의 영역 수준을 확인합니다.이것은 선택 사항입니다.기본값은 false입니다.

다음은 어댑터 구성에 대한 링크입니다.

따라서 영역 역할을 통해 권한을 부여하려면 속성이 다음과 같아야 합니다.

keycloak:
  auth-server-url: http://localhost:8080/auth/
  resource: users-api
  credentials:
    secret : my-secret
  use-resource-role-mappings : false
  realm: my-realm
  realmKey:  my-key
  public-client: true
  principal-attribute: preferred_username
  bearer-only: true

참고: 영역 수준 및 클라이언트 수준 역할 매핑을 모두 사용하려면 Keyclock을 재정의해야 합니다.인증 공급자입니다.authenticate(인증) 방법을 사용하여 필요한 역할을 직접 결합하여 제공합니다.

2022년 업데이트

스프링용 키클로크 어댑터는 더 이상 사용하지 않습니다.사용하지 마세요.사용하다spring-boot-starter-oauth2-resource-server대신.

쉬운 해결책

아주 편리한 립 세트를 위에 얹은 채로.spring-boot-starter-oauth2-resource-server구성은 다음과 같이 간단할 수 있습니다.

@EnableMethodSecurity
@Configuration
public static class SecurityConfig {
    @Bean
    ExpressionInterceptUrlRegistryPostProcessor expressionInterceptUrlRegistryPostProcessor() {
        return (ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) -> registry
            .antMatchers("/api/user/**").hasAuthority("USER")
            .antMatchers("/api/admin/**").hasAuthority("ADMIN")
            .anyRequest().authenticated();
        }
    }
}
com.c4-soft.springaddons.security.issuers[0].location=https://localhost:8443/realms/master
com.c4-soft.springaddons.security.issuers[0].authorities.claims=realm_access.roles,resource_access.employee-service.roles,resource_access.other-client.roles
com.c4-soft.springaddons.security.cors[0].path=/api/**
com.c4-soft.springaddons.security.permit-all=/actuator/health/readiness,/actuator/health/liveness,/v3/api-docs/**

스프링 전용 솔루션

동일한 작업을 수행하는 방법spring-boot-starter-oauth2-resource-server 작성할 가 꽤 : 지가, 다과같쓸자바꽤다있단습니이음가▁only단.

@EnableWebSecurity
@EnableMethodSecurity
@Configuration
public class SecurityConfig {

    public interface Jwt2AuthoritiesConverter extends Converter<Jwt, Collection<? extends GrantedAuthority>> {
    }

    @SuppressWarnings("unchecked")
    @Bean
    public Jwt2AuthoritiesConverter authoritiesConverter() {
        // This is a converter for roles as embedded in the JWT by a Keycloak server
        // Roles are taken from both realm_access.roles & resource_access.{client}.roles
        return jwt -> {
            final var realmAccess = (Map<String, Object>) jwt.getClaims().getOrDefault("realm_access", Map.of());
            final var realmRoles = (Collection<String>) realmAccess.getOrDefault("roles", List.of());

            final var resourceAccess = (Map<String, Object>) jwt.getClaims().getOrDefault("resource_access", Map.of());
            // We assume here you have "employee-service" (as in the tutorial referenced in the question) and "other-client" clients configured with "client roles" mapper in Keycloak
            final var confidentialClientAccess = (Map<String, Object>) resourceAccess.getOrDefault("employee-service", Map.of());
            final var confidentialClientRoles = (Collection<String>) confidentialClientAccess.getOrDefault("roles", List.of());
            final var publicClientAccess = (Map<String, Object>) resourceAccess.getOrDefault("other-client", Map.of());
            final var publicClientRoles = (Collection<String>) publicClientAccess.getOrDefault("roles", List.of());

            return Stream.concat(realmRoles.stream(), Stream.concat(confidentialClientRoles.stream(), publicClientRoles.stream()))
                    .map(SimpleGrantedAuthority::new).toList();
        };
    }

    public interface Jwt2AuthenticationConverter extends Converter<Jwt, AbstractAuthenticationToken> {
    }

    @Bean
    public Jwt2AuthenticationConverter authenticationConverter(Jwt2AuthoritiesConverter authoritiesConverter) {
        return jwt -> new JwtAuthenticationToken(jwt, authoritiesConverter.convert(jwt));
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, Jwt2AuthenticationConverter authenticationConverter, ServerProperties serverProperties)
            throws Exception {

        // Enable OAuth2 with custom authorities mapping
        http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(authenticationConverter);

        // Enable anonymous
        http.anonymous();

        // Enable and configure CORS
        http.cors().configurationSource(corsConfigurationSource());

        // State-less session (state in access-token only)
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // Enable CSRF with cookie repo because of state-less session-management
        http.csrf().disable();

        // Return 401 (unauthorized) instead of 403 (redirect to login) when authorization is missing or invalid
        http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
            response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Content\"");
            response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
        });

        // If SSL enabled, disable http (https only)
        if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
            http.requiresChannel().anyRequest().requiresSecure();
        } else {
            http.requiresChannel().anyRequest().requiresInsecure();
        }

        // Route security: authenticated to all routes but actuator and Swagger-UI
        // @formatter:off
        http.authorizeRequests()
            .antMatchers("/actuator/health/readiness", "/actuator/health/liveness", "/v3/api-docs", "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
            .antMatchers("/api/user/**").hasAuthority("USER")
            .antMatchers("/api/admin/**").hasAuthority("ADMIN")
            .anyRequest().authenticated();
        // @formatter:on

        return http.build();
    }

    private CorsConfigurationSource corsConfigurationSource() {
        // Very permissive CORS config...
        final var configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("*"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setExposedHeaders(Arrays.asList("*"));

        // Limited to API routes (neither actuator nor Swagger-UI)
        final var source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", configuration);

        return source;
    }
}
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://localhost:8443/realms/master

중요사항

의 두 모두 는 변경되지 않음, " " " 위의두변키역지변할로모클않환다습되니럭경우없않구은두음경지되성변▁both▁((다않니,습▁unchanged없▁no경▁make음caseclo▁keyuration▁above,우않▁no▁transformak▁config▁roles은경지ation▁to, ")).ROLE_, 사용 이유hasAuthority(...)hasRole(...).

또한 다음 수준에서 정의된 역할만 고려됩니다.

  • "스캐너덜너덜
  • "interval-service" 클라이언트(질문에서 참조한 자습서에 정의됨)
  • "other-client"(다른 임의 클라이언트를 사용할 수 있음을 보여주기 위한 것)

모두 허용:

특정 리소스/URL에 대한 액세스 요청을 허용하려면 항상 permitAll을 사용할 수 있습니다.예를 들어 로그인 URL은 모든 사용자가 액세스할 수 있어야 합니다.

모두 거부:

요청이 어디서 오는지, 누가 요청하는지에 관계없이 특정 URL의 액세스를 차단하고자 할 때마다(ADMIN)

또한 URL 및 역할과 일치하지 않는 경우가 있습니다(USER 및 vise-versa에 관리자 URL을 부여하는 경우).(역할을 ROLE_ADMIN, ADMIN 또는 USER로 사용하는 것이 좋습니다) 스택에 권한이 부여되지 않음이 표시되므로 권한으로 코드를 다시 확인하십시오.

 http
         .csrf().disable()
         .authorizeRequests()
         .antMatchers("/api/user/**").hasRole("ADMIN")
         .antMatchers("/api/admin/**").hasRole("USER")
         .anyRequest().authenticated();
  1. 당신은 그것 없이 시도합니까.@Configuration 생각에 은 내각생에당단지은신▁need단만 있으면 될 것 .@KeycloakConfiguration당신의 에대주에 을 달기SecurityConf학생들

  2. 당신의 개미 Matchers는 대소문자의 민감성을 존중합니까?

 http
    .csrf().disable()
    .authorizeRequests()
    .antMatchers("/api/user/**").hasRole("user")
    .antMatchers("/api/admin/**").hasRole("admin")
    .anyRequest().authenticated();
  1. Java에 의해 정의된 ROLE_* 규칙을 제거하려면 다음 구성도 시도하십시오.
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        // SimpleAuthorityMapper is used to remove the ROLE_* conventions defined by Java so
        // we can use only admin or user instead of ROLE_ADMIN and ROLE_USER
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }
  1. 모든 엔드포인트의 논리가 동일한 경우 보안 구성으로 충분해야 하며 다른 주석이 필요하지 않습니다.그러나 "/api/admin" 컨트롤러에 없는 관리자 역할을 가진 다른 끝점이 있는 경우 다음을 시도할 수 있습니다.
@PreAuthorize("hasRole('admin')")

답변이 늦었지만, 같은 문제에 직면한 다른 사람들에게 도움이 되기를 바랍니다.저도 당신과 똑같은 문제에 직면해 있었고, 저는 구성 수업에서 기본 키클로크를 변경해야 합니다.AuthenticationProvider가 부여된 권한 매퍼를 설정하여 다음을 수행합니다(@Override 메서드는 디버깅 전용입니다).

@Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
  KeycloakAuthenticationProvider keycloakAuthenticationProvider = new KeycloakAuthenticationProvider() {
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
      System.out.println("===========+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> authenticate ");
      KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) authentication;
      for (String role : token.getAccount().getRoles()) {
        System.out.println("===========+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Role : " + role);
      }

      return super.authenticate(authentication);
    }
  };
  keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
  auth.authenticationProvider(keycloakAuthenticationProvider);
}

}

언급URL : https://stackoverflow.com/questions/56214991/enable-role-authentication-with-spring-boot-security-and-keycloak