我试图在我的Spring Boot 后端为我的认证系统使用 Spring 安全和keycloak。keycloak部分正在做它的工作,我有一个keycloak服务器运行在localhost:8180,我可以在那里创建角色等。当后端用户没有连接时,keycloak webview弹出,让我连接或注册一个新用户。当显示OAuth2 AuthenticationToken的inide时,我得到了所有正确的信息,包括用户的角色(屏幕截图1)。
问题是,当使用@preAuthorize()(AppController.kt)保护路由时,hasRole函数会触发hasAnyRole和hasAnyAuthorityName(屏幕截图2)。但是当查看roleSet时,调用(getAuthoritySet())我没有看到PREFIX_client_test。当在jwtAuthenticationCOnverter()中放置断点时,我发现它只在应用程序启动时触发,而不是在访问受保护的路由时触发。我不知道你是否有任何线索,我会提供给你的代码和截图下,谢谢你的回答。
OAuth2 AuthenticationToken Result of OAuth2AuthenticationToken的结果
hasAnyAuthorityName中的断点Breakpoint in hasAnyAuthorityName
AppController.kt
package com.linkedout.backend.controller
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/")
open class AppController {
@GetMapping
open fun hello(token: OAuth2AuthenticationToken): String {
return "Token: ${token}"
}
@GetMapping("candidate")
@PreAuthorize("hasRole('client_candidate')")
open fun helloProtected(): String {
return "Hello premium user"
}
@GetMapping("test")
@PreAuthorize("hasRole('client_test')")
open fun test(): String {
return "This is a test"
}
@GetMapping("admin")
@PreAuthorize("hasRole('client_admin')")
open fun helloProtectedAdmin(): String {
return "Hello admin user"
}
}
SecurityConfig.kt
package com.linkedout.backend.config
import com.linkedout.backend.KeycloakLogoutHandler
import lombok.RequiredArgsConstructor
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter
import org.springframework.security.web.SecurityFilterChain
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
internal open class SecurityConfig() {
@Bean
@Throws(Exception::class)
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http.csrf { csrf -> csrf.disable() }
.authorizeHttpRequests { auth -> auth.anyRequest().fullyAuthenticated() }
.oauth2Login { }
.oauth2ResourceServer { oauth2 -> oauth2.jwt { jwtConfigurer -> jwtConfigurer.jwtAuthenticationConverter(jwtAuthenticationConverter()) } }
return http.build()
}
open fun jwtAuthenticationConverter(): JwtAuthenticationConverter {
val jwtGrantedAuthoritiesConverter = JwtGrantedAuthoritiesConverter()
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("roles")
jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_")
val jwtAuthenticationConverter = JwtAuthenticationConverter()
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter)
return jwtAuthenticationConverter
}
}
application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8180/realms/Linkedout
jwk-set-uri: http://localhost:8180/realms/Linkedout/protocol/openid-connect/certs
client:
registration:
keycloak:
client-id: backend
client-secret: PjkVtHOWmu7BEekOqJnY8TopHrJO0iCE
authorization-grant-type: authorization_code
scope: openid
provider:
keycloak:
issuer-uri: http://localhost:8180/realms/LinkedOut
user-name-attribute: preferred_username
server:
port: 9090
1条答案
按热度按时间2ledvvac1#
根据文档,OAuth2客户端中的权限Map是使用
GrantedAuthoritiesMapper
或OAuth2UserService
完成的。在浏览器中使用oauth2Login
时,JwtAuthenticationConverter
或更一般的任何Converter<Jwt, ? extends AbstractAuthenticationToken>
都不可能被调用:使用调试工具检查查询,您不会发现包含JWT的授权头(您将只有JSESSIONID
cookie)。您将OAuth2客户端和资源服务器配置添加到单个安全过滤器链中。这是一个坏主意,并导致你正在努力解决的那种困惑:
oauth2Login
是客户关注的问题。它需要基于会话的安全性,因此应针对CSRF进行保护:使用oauth2Login
在安全过滤器链中禁用CSRF保护是错误的。由这样的过滤器链构建的Authentication
实现是OAuth2AuthenticationToken
,就像你在运行时看到的那样(或者如果用户没有经过身份验证,则是AnonymousAuthenticationToken
)JwtAuthenticationConverter
由资源服务器的默认身份验证管理器使用:使用JWT访问令牌授权请求时(非会话),构建JwtAuthenticationToken
(非OAuth2AuthenticationToken
)您应该确定您的安全过滤器链是用于OAuth2客户端还是OAuth2资源服务器,并删除不符合您需要的部分。
仅作为REST API的应用程序可能只包含资源服务器配置。登录和注销不应该是它的责任:这应该由查询它的OAuth2客户端(Postman等工具或
RestTemplate
、@FeignClient
、WebClient
或RestClient
等REST客户端)处理。如果您的应用程序需要既是OAuth2客户端又是OAuth2资源服务器,那么您应该定义两个不同的
SecurityFilterChain
bean:一个具有客户端配置(会话、CSRF保护、登录和注销),另一个具有资源服务器配置(无会话、无CSRF保护、无登录、无注销)。例如,在公开提供Thymeleaf模板(使用oauth2Login
)的MVC应用程序和由OAuth2客户端(具有REST客户端的MVC控制器,或任何其他发送带有Bearer Authorization报头的REST请求的微服务)查询的REST API中,可能需要这一点。请注意,根据最新的建议,OAuth2客户端(将查询发送到OAuth2资源服务器)应该是“机密的”,这要求它在您信任的服务器上运行。这不包括SPA和移动的应用程序(无法保密,这是许多教程中将其配置为“公共”客户端的原因)。对于这样的应用程序,可以使用BFF,例如配置为OAuth2客户端的
spring-cloud-gateway
,带有登录和TokenRelay
过滤器(在将请求从前端转发到资源服务器之前,用包含会话中访问令牌的授权头替换会话cookie)。另一种选择是将REST API配置为OAuth2客户端,但这没有什么意义(为什么要费心发布和存储从未使用过的令牌),并且具有这种配置的团队可能应该质疑他们对OAuth2的使用(表单登录可能更适合)。有关更多信息和示例配置,您可以找到我的教程有用。