无法恢复jwt keycloak

问题描述 投票:0回答:1

我无法从 keycloak 恢复我的 jwt,因此我无法访问需要 jwt 和管理员角色(我拥有的角色)的产品页面。

控制台 IDE:

2024-04-14T19:47:19.921+02:00 错误 19172 --- [客户应用程序] [nio-8084-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service( )对于路径 [] 上下文中的 servlet [dispatcherServlet] 抛出异常 [请求处理失败:org.springframework.web.client.HttpClientErrorException$Unauthorized: 401 : [no body]] 其根本原因

org.springframework.web.client.HttpClientErrorException$Unauthorized:401:[无正文]

日志浏览器:

产品控制器:

@RestController
public class ProductRestController {
    private final ProductRepository productRepository;

    public ProductRestController(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }
    @GetMapping("/products")
    @PreAuthorize("hasAuthority('ADMIN')")
    public List<Product> products() {
        return productRepository.findAll();
    }
}

产品模块安全配置:

@Configuration
@EnableMethodSecurity(prePostEnabled = true)//protection par role
@EnableWebSecurity
public class SecurityConfig {

    private final JwtAuthConverter jwtAuthConverter;

    public SecurityConfig(JwtAuthConverter jwtAuthConverter) {
        this.jwtAuthConverter = jwtAuthConverter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, OAuth2ResourceServerProperties oAuth2ResourceServerProperties) throws Exception {
        return http
                .authorizeHttpRequests(ar-> ar.anyRequest().authenticated())//tte req necessite une authentification
                .oauth2ResourceServer(o2->o2.jwt(jwt->jwt.jwtAuthenticationConverter(jwtAuthConverter)))//indique dans quelle partie du jwt on peut recuperer les roles
                        .headers(h->h.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
                .csrf(csrf->csrf.ignoringRequestMatchers("/h2-console/**"))
                .build();

    }

产品模块的jwt转换器:

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

    @Override
    public AbstractAuthenticationToken convert(Jwt jwt) {
        Collection<GrantedAuthority> authorities= Stream.concat(
                jwtGrantedAuthoritiesConverter.convert(jwt).stream(),
                extractResourceRoles(jwt).stream()).collect(Collectors.toSet());
        return new JwtAuthenticationToken(jwt, authorities, jwt.getClaim("preferred_username"));
    }

    private Collection<GrantedAuthority> extractResourceRoles(Jwt jwt) {
        Map<String, Object> realmAccess;
        Collection<String> roles;
        if(jwt.getClaim("realm_access") == null) {
            return Set.of();
        }
        realmAccess =  jwt.getClaim("realm_access");
        roles=(Collection<String>) realmAccess.get("roles");
        return roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet());
    }

    @Override
    public <U> Converter<Jwt, U> andThen(Converter<? super AbstractAuthenticationToken, ? extends U> after) {
        return Converter.super.andThen(after);
    }

产品模块属性:

spring.application.name=inventory-service
server.port=8085
spring.datasource.url=jdbc:h2:mem:products-db
spring.h2.console.enabled=true

spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/realms/sdia3
#lit le token en recuperant la public key a cette uri
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:8080/auth/realms/sdia3/protocol/openid-connect/certs

客户模块的属性

spring.application.name=customer-app
server.port=8084
spring.security.oauth2.client.registration.google.client-id=290479700275-7t9jjpd5agua1u7n881fubvd2ceqg7aq.apps.googleusercontent.com
spring.security.oauth2.client.registration.google.client-secret=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
spring.security.oauth2.client.provider.github.user-name-attribute=login

spring.security.oauth2.client.registration.github.client-id=Iv1.3369f41e3d67c0a3
spring.security.oauth2.client.registration.github.client-secret=f7cb85296f581390511d1bf4aa29bcc43af7b163
spring.security.oauth2.client.provider.google.user-name-attribute=email
spring.datasource.url=jdbc:h2:mem:customers-db
spring.h2.console.enabled=true

spring.security.oauth2.client.registration.keycloak.client-name=keycloak
spring.security.oauth2.client.registration.keycloak.client-id=client-cree-customer
spring.security.oauth2.client.registration.keycloak.client-secret=C6JJFHCOkDSvaZwLAy673yHVnXkDu6k3
spring.security.oauth2.client.registration.keycloak.scope=openid, profile, email, offline_access
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.redirect-uri=http://localhost:8084/login/oauth2/code/sdia3
spring.security.oauth2.client.provider.keycloak.issuer-uri=http://localhost:8080/realms/sdia3
spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username

客户模块的安全配置:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)

public class SecurityConfig  {
    //stock les diff providers conf dans l'appli pour la connection clients
    private final ClientRegistrationRepository clientRegistrationRepository;
    //inj de dependance via constructeur
    public  SecurityConfig(ClientRegistrationRepository clientRegistrationRepository){
        this.clientRegistrationRepository=clientRegistrationRepository;
    }

    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf(Customizer.withDefaults())//config par def.
                .authorizeHttpRequests(ar-> ar.anyRequest().authenticated())//toutes requetes necessite une authentification
                .headers(h->h.frameOptions(fo->fo.disable()))//dit a spring de desactiver la securite antiFrame pour h2-console
                .csrf(crsf->crsf.ignoringRequestMatchers("/h2-console/**"))//desactive crsf pour h2-console
                .authorizeHttpRequests(ar->ar.requestMatchers("/","/webjars/**","/h2-console/**",  "/oauth2Login/**").permitAll())//toute url sans besoin de s'authentifier:parefeu
                .oauth2Login(al->al.loginPage("/oauth2Login").defaultSuccessUrl("/"))
                .logout((logout)->logout
                        .logoutSuccessHandler(oidcLogoutSuccessHandler())
                        .logoutSuccessUrl("/").permitAll()
                        .clearAuthentication(true)
                        .deleteCookies("JSESSIONID"))
                        .exceptionHandling(eh->eh.accessDeniedPage("/notAuthorized"));
        return http.build();

    }

    private OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler() {
        final OidcClientInitiatedLogoutSuccessHandler oidcClientInitiatedLogoutSuccessHandler=
                new OidcClientInitiatedLogoutSuccessHandler(this.clientRegistrationRepository);
        oidcClientInitiatedLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}?logoutsuccess=true");
        return oidcClientInitiatedLogoutSuccessHandler;
    }
    @Bean
    public GrantedAuthoritiesMapper userAuthoritiesMapper() {
        return (authorities -> {
            final Set<GrantedAuthority> mappedAuthorities=new HashSet<>();
            authorities.forEach((authority)->{
                if (authority instanceof OidcUserAuthority oidcAuth){
                    mappedAuthorities.addAll(mappedAuthorities(oidcAuth.getIdToken().getClaims()));
                    System.out.println(oidcAuth.getAttributes());
                } else if (authority instanceof OAuth2UserAuthority oauth2Auth) {
                    mappedAuthorities.addAll(mappedAuthorities(oauth2Auth.getAttributes()));
                }
            });
        return mappedAuthorities;

    });
    }

    private List<SimpleGrantedAuthority> mappedAuthorities(final Map<String, Object> attributes) {
        final Map<String, Object> realmAccess= (Map<String, Object>) attributes.getOrDefault("realm_access", Collections.emptyMap());
        final Collection<String> roles=((Collection<String>)realmAccess.getOrDefault("roles", Collections.emptyList()));
        return roles.stream()
                .map(SimpleGrantedAuthority::new)
                .toList();
    }
}

客户模块的客户控制器:

@Controller   //rendu html cote serveur, donc c un controller et non un RestController
    public class CustomerController {
        /**
         * acces a interface
         */
        private  CustomerRepository customerRepository;
        private ClientRegistrationRepository clientRegistrationRepository;
        public CustomerController(CustomerRepository customerRepository, ClientRegistrationRepository registrationRepository) {
            this.customerRepository = customerRepository;
            this.clientRegistrationRepository = clientRegistrationRepository;
        }


        @GetMapping("/customers")
        @PreAuthorize("hasAuthority('ADMIN')")//precise le role d'acces a la page
        public String customers(Model model){  //rendu html cote serveur, on a besoin du model
            List<Customer> customersList = customerRepository.findAll();
            model.addAttribute("customers", customersList);//ajout dans model d'un att customersList qui stock la liste recupere plus haut
            return "customers";//return attributeName du model=CustomerList
        }

        @GetMapping("/products")
        //rendu html cote serveur, on a besoin du model
        public String products(Model model){
            SecurityContext context= SecurityContextHolder.getContext();
            Authentication authentication = context.getAuthentication();
            OAuth2AuthenticationToken oAuth2AuthenticationToken = (OAuth2AuthenticationToken) authentication;
            DefaultOidcUser oidcUser= (DefaultOidcUser) oAuth2AuthenticationToken.getPrincipal();
            String jwtTokenValue=oidcUser.getIdToken().getTokenValue();
            RestClient restClient= RestClient.create("http://localhost:8085");
            List<Product> products=restClient.get()
                    .uri("/products")
                    .headers(httpHeaders ->httpHeaders.set(HttpHeaders.AUTHORIZATION,"Bearer"+"jwtTokenValue"))
                    .retrieve()
                    .body(new ParameterizedTypeReference<>() {});
            model.addAttribute("products", products);
            return "products";
        }


        //retourne l'user authentifier
        @GetMapping("/auth")
        @ResponseBody//vu que ce n'est pas un rest controller, il doit y avoir cette anotation pour retourner la reponse sous format json
        public Authentication auth(Authentication authentication){
            return authentication;
        }
        //si url =/
        @GetMapping("/")
        public String index(){
            return "index";
        }

        @GetMapping("/notAuthorized")
        public String notAuthorized(){
            return "notAuthorized";
        }

        @GetMapping("/oauth2Login")
        public String oauth2Login(Model model){
            String authorizationRequestBaseUri="oauth2/authorization";
            Map<String, String> oauth2AuthenticationUrls=new HashMap();
            Iterable<ClientRegistration> clientRegistrations=(Iterable<ClientRegistration>)clientRegistrationRepository;
            clientRegistrations.forEach(clientRegistration->{
                oauth2AuthenticationUrls.put(clientRegistration.getClientName(),
                        authorizationRequestBaseUri+"/"+ clientRegistration.getRegistrationId());
            });
            model.addAttribute("urls",oauth2AuthenticationUrls);
            return "oauth2Login";
        }

角色:

结果,我恢复了访问客户资源页面的令牌,但无法恢复产品的令牌。 我有两个模块,一个用于客户端:包含控制器、安全配置,另一个用于具有相同功能的产品,但添加了一个 jwtconverter 类,该类从当前用户登录中检索令牌。

提前非常感谢。

spring jwt resources keycloak token
1个回答
0
投票

您可能想要

httpHeaders.set(HttpHeaders.AUTHORIZATION,"Bearer " + jwtTokenValue))
而不是
httpHeaders.set(HttpHeaders.AUTHORIZATION,"Bearer"+"jwtTokenValue"))

© www.soinside.com 2019 - 2024. All rights reserved.