스프링 부트(보안) 및 키클로크로 역할 인증을 사용하시겠습니까?
저는 간단한 일을 하려고 합니다.
단일 엔드포인트에 요청을 하고 클라이언트에서 베어러 토큰을 보내려면 엔드포인트에서 키클록 수락/거부 요청에 할당된 역할에 따라 이 토큰의 유효성을 확인해야 합니다.
저는 많은 튜토리얼과 심지어 책들을 따라다녔지만 대부분 이해할 수 없습니다.
다음을 수행하여 내 키클록 정보(역할, 사용자)를 설정했습니다. 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();
당신은 그것 없이 시도합니까.
@Configuration
생각에 은 내각생에당단지은신▁need단만 있으면 될 것 .@KeycloakConfiguration
당신의 에대주에 을 달기SecurityConf
학생들당신의 개미 Matchers는 대소문자의 민감성을 존중합니까?
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/user/**").hasRole("user")
.antMatchers("/api/admin/**").hasRole("admin")
.anyRequest().authenticated();
- 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);
}
- 모든 엔드포인트의 논리가 동일한 경우 보안 구성으로 충분해야 하며 다른 주석이 필요하지 않습니다.그러나 "/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
'programing' 카테고리의 다른 글
JPA 및 최대 절전 모드를 사용하여 Java 부울 열을 Oracle 번호 열에 매핑 (0) | 2023.07.09 |
---|---|
연관 테이블을 사용하는 것보다 플래그를 비트 마스크로 저장하는 것이 더 나은 경우는 언제입니까? (0) | 2023.07.09 |
Oracle 저장 프로시저에 대한 varchar2 입력의 기본 크기는 얼마이며 변경할 수 있습니까? (0) | 2023.07.09 |
메서드가 여러 개 중 하나의 인수로 호출되었다고 주장합니다. (0) | 2023.07.09 |
C에서 헤더 파일의 요점은 무엇입니까? (0) | 2023.07.09 |