Jak przejść z JWT na keycloak?

0

Cześć wszystkim.

Od kilku dni męczę się z problemem dotyczącym keycloak.

Wcześniej moja aplikacja stała na JWT tokenie i tutaj wszystko działało tak jak trzeba.
Postanowiłem przejść na KeyCloak i tutaj pojawia się problem.

KeyCloak skonfigurowany, strzał na endpoint zwraca token, jednak gdy próbuję uwierzytelnić się tym tokenem w postmanie cały czas dostaję 401 unauthorized

Próbowałem już wszystkich możliwych sposobów, ale wciąż dostaję taki zwrot.

W klasie JwtAuthConverter dodałem sout'a zeby zobaczyć czy prawidłowo odczytuje z niego role i w konsoli dostaję zwrot: Resource roles: [client_admin]
czyli to czego oczekuję w WebSecurityConfigu (.hasAnyRoles("client_admin","client_user").

Znalazłem gdzieś, że może być konflikt, z powodu braku prefixu ROLE_ w roli, więc go dodaje w extractResourceRoles, ale w dalszym ciągu nic to nie zmienia.

Nie mam już pomysłu, a myślę, że to jakaś pierdoła, która blokuje mnie żeby pójść dalej.

Będę bardzo wdzięczny za pomoc jakiejś mądrej głowy 😃
Miłego weekendu!

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Value("${jwt.auth.converter.principleAttribute}")
    private String principleAttributeName;

    @Value("${jwt.auth.converter.resource-id}")
    private String resourceId;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/auth/login").permitAll()
                        .requestMatchers("/currencies").permitAll()
                        .requestMatchers("/currencies/getRate/*").permitAll()
                        .requestMatchers("/currencies/exchange").hasAnyRole("client_user", "client_admin")
                        .requestMatchers("/currencies/exchangeAdmin").hasRole("ADMIN")
                        .anyRequest().authenticated()
                )
                .oauth2ResourceServer(oauth2 -> oauth2
                        .jwt(jwt -> jwt
                                .jwtAuthenticationConverter(jwtAuthConverter())
                                .jwkSetUri("http://localhost:8080/realms/exchange-api/protocol/openid-connect/certs")
                        )
                )
                .sessionManagement(sessionManagement ->
                        sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                );

        return httpSecurity.build();
    }

    @Bean
    public Converter<Jwt, AbstractAuthenticationToken> jwtAuthConverter() {
        return new JwtAuthConverter();
    }

    public class JwtAuthConverter implements Converter<Jwt, AbstractAuthenticationToken> {
        private final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();

        @Override
        public AbstractAuthenticationToken convert(@NonNull Jwt jwt) {
            Collection<GrantedAuthority> authorities = Stream.concat(
                    jwtGrantedAuthoritiesConverter.convert(jwt).stream(),
                    extractResourceRoles(jwt).stream()
            ).collect(Collectors.toSet());

            return new JwtAuthenticationToken(jwt, authorities, getPrincipleClaimName(jwt));
        }

        private String getPrincipleClaimName(Jwt jwt) {
            String claimName = JwtClaimNames.SUB;
            if (principleAttributeName != null) {
                claimName = principleAttributeName;
            }
            return jwt.getClaim(claimName);
        }

        private Collection<? extends GrantedAuthority> extractResourceRoles(Jwt jwt) {
            Map<String, Object> resourceAccess;
            Map<String, Object> resource;
            Collection<String> resourceRoles;
            if (jwt.getClaim("resource_access") == null) {
                return Set.of();
            }
            resourceAccess = jwt.getClaim("resource_access");
            if (resourceAccess.get(resourceId) == null) {
                return Set.of();
            }
            resource = (Map<String, Object>) resourceAccess.get(resourceId);

            resourceRoles = (Collection<String>) resource.get("roles");

            System.out.println("Resource roles: " + resourceRoles);
            return resourceRoles.stream()
                    .map(role -> "ROLE_" + role)
                    .map(SimpleGrantedAuthority::new)
                    .collect(Collectors.toSet());
        }
    }
}

1

A ogarniasz że Keycloak też Ci wróci JWT? 😉
Sprawdź, którego tokenu używasz do autoryzacji przez Postmana - być może nie tego, co trzeba 😉

Polecam dokumentację, każde flow powinno być wytłumaczone. 😊

0

Tak ogarniam - po logowaniu w postmanie dostaję tokena, który po rozkodowaniu prezentuje się mniej więcej tak:

  "exp": 1719046942,
  "iat": 1719046642,
  "jti": "c88dd1ef-62c9-4e75-aa83-390a9f328e03",
  "iss": "http://localhost:8080/realms/exchange-api",
  "aud": "account",
  "sub": "6b7140b3-f80b-4965-860b-68aee2739b9a",
  "typ": "Bearer",
  "azp": "exchange-rest-api",
  "sid": "fd7bf9d1-aab2-44c2-beaa-b750af5eb26f",
  "acr": "1",
  "allowed-origins": [
    "*"
  ],
  "realm_access": {
    "roles": [
      "offline_access",
      "admin",
      "uma_authorization",
      "default-roles-exchange-api"
    ]
  },
  "resource_access": {
    "exchange-rest-api": {
      "roles": [
        "client_admin"
      ]
    },
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "scope": "email profile",
  "email_verified": true,
  "name": "xxx xxx",
  "preferred_username": "user",
  "given_name": "xxx",
  "family_name": "xxx",
  "email": "xxxxx"
}

Następnie podaje go w postmanie jako uwierzytelnienie i mimo wszystko cały czas mam 401 unauthorized.
Jak widać role przypisaną user ma, w websecurityconfigu "client_admin" również jest wpisane a mimo wszystko nie mogę uzyskać dostępu do endpointa.

0

@xwns: być może musisz zrobić transformację tych claimow. Z mojego .NETowego światka trzeba to było robić. Nie wiem jak w Springu.

1 użytkowników online, w tym zalogowanych: 0, gości: 1