基于真实角色的访问不起作用,Spring Security 6和Keycloak

juud5qan  于 2023-01-20  发布在  Spring
关注(0)|答案(2)|浏览(183)

我想根据领域角色给予访问权限。我正在使用Keycloak和Sping Boot 3(Spring Security 6)。
我尝试使用hasRole()/hasAnyRole/hasAuthority/hasAnyAuthority来获得所需的角色名称,字母大小写相同。
默认情况下,use-resource-role-mappings为false(“如果为false,它将查看用户角色Map的领域级别”)。
我想在java配置文件中设置这个,因为我会发现添加每个控制器@PreAuthorize注解非常耗时。
我一直得到403作为响应。我做错了什么?错误:

Retrieved SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=Name: [firstname.lastname], Granted Authorities: [[OIDC_USER, SCOPE_ProjectNameClientScope, SCOPE_email, SCOPE_openid, SCOPE_profile]], 
User Attributes: [{at_hash=hash-hash-hash, sub=sub-sub-sub-sub-sub, email_verified=true, iss=https://keycloak.domain.ac.at:port/realms/ProjectNameRealm, groups=[/pn_administratoren], 
Roles=[cafeteria, obw, ROLE_obw, courseplanning], typ=ID, preferred_username=firstname.lastname, given_name=firstname, nonce=nonce-y-nonce-nonce, sid=sid-sid-sid-sid-sid, aud=[po-ms], acr=1, persId=number, azp=po-ms, auth_time=2023-01-18T13:36:38Z, name=firstname lastname, exp=2023-01-18T13:41:38Z, 
session_state=sss-sss-sss-ss-sss, family_name=lastname, iat=2023-01-18T13:36:38Z, email=firstname.lastname@domain.ac.at, jti=jti-jti-jti-jti-jti}], 
Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=xxx.xxx.xx.xx, SessionId=sesId], 
Granted Authorities=[OIDC_USER, SCOPE_ProjectNameClientScope, SCOPE_email, SCOPE_openid, SCOPE_profile]]]

下面是我的代码:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

@Configuration
@EnableWebSecurity
public class SpringSecurity2 {
    
    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 "spring-addons-confidential" and "spring-addons-public" clients configured with "client roles" mapper in Keycloak
            final var confidentialClientAccess = (Map<String, Object>) resourceAccess.getOrDefault("spring-addons-confidential", Map.of());
            final var confidentialClientRoles = (Collection<String>) confidentialClientAccess.getOrDefault("roles", List.of());
            final var publicClientAccess = (Map<String, Object>) resourceAccess.getOrDefault("spring-addons-public", 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
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, Converter<Jwt, AbstractAuthenticationToken> authenticationConverter, KeycloakLogoutHandler keycloakLogoutHandler) throws Exception {

        CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
        XorCsrfTokenRequestAttributeHandler delegate = new XorCsrfTokenRequestAttributeHandler();
        delegate.setCsrfRequestAttributeName("_csrf");

        CsrfTokenRequestHandler requestHandler = delegate::handle;

        http.authorizeHttpRequests(auth -> {
            auth.requestMatchers("/firstpath/**", "/secondpath/**").permitAll();
            auth.requestMatchers("/thirdpath/**").hasAnyRole("obw");
            auth.anyRequest().authenticated();
        });

        http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(authenticationConverter);
        http.oauth2Login()
                .and()
                .logout()
                .addLogoutHandler(keycloakLogoutHandler)
                .logoutSuccessUrl("/");

        http.csrf(csrf -> csrf
                .csrfTokenRepository(tokenRepository)
                .csrfTokenRequestHandler(requestHandler));

        return http.build();
    }
    
}

登录用户的角色

ROLE_obw也是如此...
这是访问令牌的一部分:

vawmfj5a

vawmfj5a1#

默认情况下,Spring安全性只提供基于作用域的Map,用于处理通过JWT令牌的访问。对于授权/角色,需要自定义Map解决方案来从JWT令牌中提取并放入安全上下文。
有一篇很好的文章解释了这一点。
Map Authorities from JWT

i7uq4tfw

i7uq4tfw2#

登录(和注销)由OAuth2客户端处理,而不是由资源服务器(REST API)处理。对受资源服务器保护的资源的请求应该具有访问令牌。
从资源服务器安全过滤器链中删除登录和注销配置(您也可以使其无会话并禁用CSRF),并在Authorization报头中发送带有Bearer访问令牌的请求。
如果您的应用还提供服务器端呈现的UI,请为客户端配置定义第二个SecurityFilterChain bean(启用登录、注销、会话和CSRF保护)。
我在回答另一个问题时的详细内容如下:将KeycloakSpring适配器与 Spring Boot 3配合使用

相关问题