**已关闭。**此问题需要debugging details。目前不接受回答。
编辑问题以包括desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem。这将帮助其他人回答问题。
8天前关闭。
Improve this question的
我尝试用Sping Boot 和Keycloak实现oauth2 authorization_code flow。我的Sping Boot 应用程序充当客户端和资源服务器,Keycloak是授权提供者。
我通过浏览器请求资源,然后Keycloak的登录页面出现,我输入我的用户的凭据,我得到资源。我想做的是基于角色检索资源。所以,我创建了一个自定义转换器来添加角色值(添加前缀ROLE_),我想进入授权。问题是我的转换器似乎从来没有被调用,导致403响应
SecurityConfig.java
@RequiredArgsConstructor
@EnableWebSecurity
@EnableMethodSecurity
@Configuration
public class SecurityConfig {
private final JwtAuthConverter jwtAuthConverter;
@Value(value = "${jwt.auth.converter.issuer-uri}")
private String issuerUri;
@Bean
public SecurityFilterChain clientFilterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(req-> req
.anyRequest()
.authenticated())
.oauth2Login(Customizer.withDefaults())
.oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer
.jwt(jwt ->
// here I set my custom converter, but it's never called
jwt.jwtAuthenticationConverter(jwtAuthConverter)
)
)
.build();
}
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withIssuerLocation(issuerUri).build();
}
}
字符串
application.yml
jwt:
auth:
converter:
resource-id: john-rest-api
principal-attribute: preferred_username
issuer-uri: http://localhost:8180/realms/john
spring:
security:
oauth2:
client:
registration:
john-rest-api:
client-id: john-rest-api
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8080/login/oauth2/code/john-rest-api
scope: openid, profile, roles
provider:
john-rest-api:
issuer-uri: http://localhost:8180/realms/john
resourceserver:
jwt:
issuer-uri: http://localhost:8180/realms/john
jwk-set-uri: ${spring.security.oauth2.resourceserver.jwt.issuer-uri}/protocol/openid-connect/certs
型
JwtAuthConverter.java自定义转换器)
@Component
public class JwtAuthConverter implements Converter<Jwt, AbstractAuthenticationToken>{
private final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter =
new JwtGrantedAuthoritiesConverter();
@Value(value = "${jwt.auth.converter.principal-attribute}")
private String principalAttribute;
@Value(value = "${jwt.auth.converter.resource-id}")
private String resourceId;
@Override
public AbstractAuthenticationToken convert(Jwt jwt) {
// ...
// never been here !!!
// code to add roles that i want to grant authorities, adding to them the prefix "ROLE_"
}
}
型
DemoController.java
@GetMapping("/hello-2")
@PreAuthorize("hasRole('client_admin')")
public String hello() {
return "hello";
型
任何关于这方面的想法都会很有帮助。另外,如果有关于如何使用Keycloak和Sping Boot 实现authorization_code flow的教程(我找不到),也会很有帮助。
日志
2023-11-06T14:38:22.468+02:00 DEBUG 7100 --- [nio-8080-exec-7] o.s.security.web.FilterChainProxy : Securing GET /login/oauth2/code/john-rest-api?state=482b447Sm8xpm__xVJzRgLzLIaEl1CwUBi79b7C2MZ8%3D&session_state=06bb0831-88be-4580-8717-e08985098f2c&code=c4800a89-9de1-4f6e-8c80-f74b30825286.06bb0831-88be-4580-8717-e08985098f2c.06b90e8a-c3a3-40fe-877a-41d5f3131066
2023-11-06T14:38:22.666+02:00 DEBUG 7100 --- [nio-8080-exec-7] .s.ChangeSessionIdAuthenticationStrategy : Changed session id from F4F280CA99D66DCFD5C20757B8D61BEA
2023-11-06T14:38:22.667+02:00 DEBUG 7100 --- [nio-8080-exec-7] w.c.HttpSessionSecurityContextRepository : Stored SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=Name: [f865bdf4-8438-4ac5-a587-917ed06fb82e], Granted Authorities: [[OIDC_USER, SCOPE_email, SCOPE_openid, SCOPE_profile]], User Attributes: [{at_hash=hdODzgXtXVwmIIC8I0_-Lg, sub=f865bdf4-8438-4ac5-a587-917ed06fb82e, email_verified=false, iss=http://localhost:8180/realms/john, typ=ID, preferred_username=john, given_name=, nonce=BoS9yhBYY4d-aY0yHdbX-3IK_LF6uBl3uFEQ1vp6L6c, sid=06bb0831-88be-4580-8717-e08985098f2c, aud=[john-rest-api], acr=1, azp=john-rest-api, auth_time=2023-11-06T12:38:22Z, exp=2023-11-06T12:43:22Z, session_state=06bb0831-88be-4580-8717-e08985098f2c, family_name=, iat=2023-11-06T12:38:22Z, jti=3951b6f2-fda1-4374-8e81-556881d7c9a6}], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=F4F280CA99D66DCFD5C20757B8D61BEA], Granted Authorities=[OIDC_USER, SCOPE_email, SCOPE_openid, SCOPE_profile]]] to HttpSession [org.apache.catalina.session.StandardSessionFacade@4f0bbf2a]
2023-11-06T14:38:22.668+02:00 DEBUG 7100 --- [nio-8080-exec-7] .s.o.c.w.OAuth2LoginAuthenticationFilter : Set SecurityContextHolder to OAuth2AuthenticationToken [Principal=Name: [f865bdf4-8438-4ac5-a587-917ed06fb82e], Granted Authorities: [[OIDC_USER, SCOPE_email, SCOPE_openid, SCOPE_profile]], User Attributes: [{at_hash=hdODzgXtXVwmIIC8I0_-Lg, sub=f865bdf4-8438-4ac5-a587-917ed06fb82e, email_verified=false, iss=http://localhost:8180/realms/john, typ=ID, preferred_username=john, given_name=, nonce=BoS9yhBYY4d-aY0yHdbX-3IK_LF6uBl3uFEQ1vp6L6c, sid=06bb0831-88be-4580-8717-e08985098f2c, aud=[john-rest-api], acr=1, azp=john-rest-api, auth_time=2023-11-06T12:38:22Z, exp=2023-11-06T12:43:22Z, session_state=06bb0831-88be-4580-8717-e08985098f2c, family_name=, iat=2023-11-06T12:38:22Z, jti=3951b6f2-fda1-4374-8e81-556881d7c9a6}], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=F4F280CA99D66DCFD5C20757B8D61BEA], Granted Authorities=[OIDC_USER, SCOPE_email, SCOPE_openid, SCOPE_profile]]
2023-11-06T14:38:22.669+02:00 DEBUG 7100 --- [nio-8080-exec-7] o.s.s.web.DefaultRedirectStrategy : Redirecting to http://localhost:8080/api/v1/demo/hello-2?continue
2023-11-06T14:38:22.699+02:00 DEBUG 7100 --- [nio-8080-exec-7] o.s.security.web.FilterChainProxy : Securing GET /api/v1/demo/hello-2?continue
2023-11-06T14:38:22.700+02:00 DEBUG 7100 --- [nio-8080-exec-7] o.s.s.w.s.HttpSessionRequestCache : Loaded matching saved request http://localhost:8080/api/v1/demo/hello-2?continue
2023-11-06T14:38:22.702+02:00 DEBUG 7100 --- [nio-8080-exec-7] w.c.HttpSessionSecurityContextRepository : Retrieved SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=Name: [f865bdf4-8438-4ac5-a587-917ed06fb82e], Granted Authorities: [[OIDC_USER, SCOPE_email, SCOPE_openid, SCOPE_profile]], User Attributes: [{at_hash=hdODzgXtXVwmIIC8I0_-Lg, sub=f865bdf4-8438-4ac5-a587-917ed06fb82e, email_verified=false, iss=http://localhost:8180/realms/john, typ=ID, preferred_username=john, given_name=, nonce=BoS9yhBYY4d-aY0yHdbX-3IK_LF6uBl3uFEQ1vp6L6c, sid=06bb0831-88be-4580-8717-e08985098f2c, aud=[john-rest-api], acr=1, azp=john-rest-api, auth_time=2023-11-06T12:38:22Z, exp=2023-11-06T12:43:22Z, session_state=06bb0831-88be-4580-8717-e08985098f2c, family_name=, iat=2023-11-06T12:38:22Z, jti=3951b6f2-fda1-4374-8e81-556881d7c9a6}], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=F4F280CA99D66DCFD5C20757B8D61BEA], Granted Authorities=[OIDC_USER, SCOPE_email, SCOPE_openid, SCOPE_profile]]]
2023-11-06T14:38:22.702+02:00 DEBUG 7100 --- [nio-8080-exec-7] o.s.security.web.FilterChainProxy : Secured GET /api/v1/demo/hello-2?continue
2023-11-06T14:38:22.704+02:00 DEBUG 7100 --- [nio-8080-exec-7] horizationManagerBeforeMethodInterceptor : Authorizing method invocation ReflectiveMethodInvocation: public java.util.Map com.example.keycloak.DemoController.hello2(); target is of class [com.example.keycloak.DemoController]
2023-11-06T14:38:22.705+02:00 DEBUG 7100 --- [nio-8080-exec-7] horizationManagerBeforeMethodInterceptor : Failed to authorize ReflectiveMethodInvocation: public java.util.Map com.example.keycloak.DemoController.hello2(); target is of class [com.example.keycloak.DemoController] with authorization manager org.springframework.security.config.annotation.method.configuration.DeferringObservationAuthorizationManager@53e70692 and decision ExpressionAuthorizationDecision [granted=false, expressionAttribute=hasRole('client_admin')]
型
2条答案
按热度按时间5tmbdcev1#
对于具有登录的OAuth2客户端和OAuth2资源服务器,权限Map的执行方式不同。
使用
Converter<Jwt, AbstractAuthenticationToken>
(如JwtAuthenticationConverter
)仅适用于带有JWT解码器的OAuth2资源服务器(但既不适用于带有内省的OAuth2资源服务器,也不适用于带有oauth2Login
的OAuth2客户端)。对于使用
oauth2Login
的OAuth2客户端,可以使用GrantedAuthoritiesMapper
或OAuth2UserService
(在大多数情况下,第一种更简单,也足够了)。作为一个旁注,你不应该在同一个安全过滤器链中混合使用
oauth2Login
和resourceServer
。配置要求太不同了(安全性是基于登录的客户端的会话,这使得CSRF保护成为必要,而安全性是基于资源服务器的令牌,这使得会话和CSRF保护成为不必要)。由于您使用的是
oauth2Login
,我猜您实际上没有使用令牌(检查您的浏览器调试工具,你会发现会话cookie,但没有任何包含Bearer
令牌的Authorization
头)。resourceServer
配置只有在OAuth2客户端使用访问令牌发送请求时才有意义。浏览器本身不能成为OAuth2客户端。它需要一个JavaScript框架为此配置,它需要将此框架配置为 public OAuth2客户端,现在不鼓励这样做。如果你的UI是一个带有Thymeleaf(或任何其他服务器端渲染框架)的Spring应用程序,有两个选项:
WebClient
、RestClient
、RestTemplate
、@FeignClient
等)对暴露给其他OAuth2客户端的API进行内部REST调用,然后在两个不同的SecurityFilterChain
bean中分离安全性:一个用于OAuth2客户端登录(MVC控制器端点呈现模板),另一个用于REST API的OAuth2资源服务器配置。如果你的UI是SPA,那么也有两个选择:
resourceServer
配置(仅将其用作具有登录和会话的OAuth2客户端),但这对于可伸缩性和容错性来说并不理想epfja78i2#
上面的代码完整吗?我看到
private final JwtAuthConverter jwtAuthConverter;
,但没有代码给出它的初始值。另一种方法是创建
org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter
类型的Bean并对其进行自定义:字符串
其中
CustomJwtGrantedAuthoritiesConverter
是一个自定义类,用于处理授予的权限、ROLE_ etc