我无法从 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 类,该类从当前用户登录中检索令牌。
提前非常感谢。
您可能想要
httpHeaders.set(HttpHeaders.AUTHORIZATION,"Bearer " + jwtTokenValue))
而不是 httpHeaders.set(HttpHeaders.AUTHORIZATION,"Bearer"+"jwtTokenValue"))