spring 如何使用第三方Oauth2提供程序验证对ResourceServer的请求

ws51t4hk  于 2023-02-07  发布在  Spring
关注(0)|答案(2)|浏览(189)

我正在开发一个API服务,它可以完成以下任务:
1.允许用户通过Google登录。
1.根据检索到的信息在数据库中创建用户。
1.为用户提供一个用于身份验证的JWT令牌,以便对所述用户的请求进行唯一标识。
1.允许用户能够使用获取的令牌对我的服务执行API请求。

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: ${GOOGLE_CLIENT_ID}
            client-secret: ${GOOGLE_CLIENT_SECRET}

我不确定我该怎么做,我到底需要什么。到目前为止,我有以下内容

主应用程序类:

@SpringBootApplication
@EnableWebSecurity
@Configuration
class ApiServiceApplication {

    @Bean
    fun filterChain(http: HttpSecurity): SecurityFilterChain {
        http.authorizeHttpRequests {
            it.antMatchers("/", "/login", "/error", "/webjars/**").permitAll().anyRequest().authenticated()
        }
            .logout {
                it.logoutSuccessUrl("/").permitAll()
            }
            .exceptionHandling {
                it.authenticationEntryPoint(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
            }
            .oauth2Login { oauth2Login ->
                oauth2Login.loginPage("/login")
                oauth2Login.defaultSuccessUrl("/user", true)
            }
            .oauth2Client { oauth2Client -> }
            .csrf {
                it.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            }
        return http.build()
    }
}

fun main(args: Array<String>) {
    runApplication<ApiServiceApplication>(*args)
}

用于将用户保存到DB的用户服务类

@RestController
class UserService : OidcUserService() {

    @Autowired
    lateinit var userRepository: UserRepository

    @Autowired
    lateinit var loginRepository: LoginRepository

    private val oauth2UserService = DefaultOAuth2UserService()

    @GetMapping("/login")
    fun authenticate(): RedirectView {
        return RedirectView("/oauth2/authorization/google")
    }

    override fun loadUser(userRequest: OidcUserRequest?): OidcUser {
        val loadedUser = oauth2UserService.loadUser(userRequest)
        val username = loadedUser.attributes["email"] as String
        var user = userRepository.findByUsername(username)
        if (user == null) {
            user = OauthUser()
            user.username = username
        }

        loadedUser.attributes.forEach { loadedAttribute ->
            val userAttribute = user.oauthAttributes.find { loadedAttribute.key == it.attributeKey && it.active }
            val newAttribute = OauthAttribute(loadedAttribute.key, loadedAttribute.value?.toString())
            if(userAttribute == null){
                user.oauthAttributes.add(newAttribute)
            }
            else if(userAttribute.attributeValue != loadedAttribute.value?.toString()){
                userAttribute.active = false
                user.oauthAttributes.add(newAttribute)
            }
        }
        user.oauthAuthorities = loadedUser.authorities.map { OauthAuthority(it.authority) }.toMutableList()
        user.oauthToken = OauthToken(
            userRequest?.accessToken?.tokenValue!!,
            Date.from(userRequest.accessToken.issuedAt),
            Date.from(userRequest.accessToken.expiresAt)
        )
        userRepository.save(user)
        val login = Login(user)
        loginRepository.save(login)
        return user
    }
}

我没有提供数据类和相应的存储库,因为上面的操作很好--在访问/login端点时,用户被重定向到Google,在那里,经过身份验证后,用户连同相应的信息沿着被保存在数据库中。
我的主要问题是我不确定如何验证每个请求。我尝试在Postman中提供一个验证载体,它与从Google通过loadUser方法获得的验证载体相同,但我得到了401个未授权代码。当我通过浏览器访问服务器并进行验证时,我可以很好地访问所有端点。但我猜只有我的会话是经过认证的。

wj8zmpe1

wj8zmpe11#

您正在尝试将资源服务器(REST API服务资源)配置为UI客户端(应用程序消耗资源)。这将无法工作。
您不应该在resource-server上实现oauth2登录和注销,这是UI客户端问题,应该从您的Java配置中删除。一个例外是,如果您的应用程序还提供带有Thymeleaf、JSF或其他服务器端呈现UI的UI,在这种情况下,您应该创建第二个“客户端”安全过滤器链bean,并将登录和注销移动到那里,如此处所述:将KeycloakSpring适配器与 Spring Boot 一起使用3)。
除非您处于上述“异常”(UI客户端)中,或者使用由spring-boot自动配置的REST客户端WebClient@FeignClientRestTemplate)消耗其他资源服务器的资源,否则请从yaml文件中删除所有spring.security.oauth2.client属性,并从依赖项中删除spring-boot-starter-oauth2-client
配置资源服务器的详细信息请参见上面链接的答案(适用于任何OIDC授权服务器,而不仅仅是Keycloak)或this repo of mine的教程。

jk9hmnmh

jk9hmnmh2#

我已经通过以下方式实现了我的目标:
spring.security.oauth2配置添加资源服务器定义:

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: ${GOOGLE_CLIENT_ID}
            client-secret: ${GOOGLE_CLIENT_SECRET}
      resourceserver:
        jwt:
          issuer-uri: https://accounts.google.com
          jwk-set-uri: https://www.googleapis.com/oauth2/v3/certs

添加OAuth2ResourceServerConfigurer并通过.oauth2ResourceServer().jwt()指定默认的JwtConfigurer,并指定要由JWT保护的路径的授权匹配。由于ch4mp的注解,我还拆分了过滤器链,以便只有/api端点通过JWT保护:

@Bean
@Order(HIGHEST_PRECEDENCE)
fun apiFilterChain(http: HttpSecurity): SecurityFilterChain {
    http.antMatcher("/api/**").authorizeRequests { authorize ->
        authorize.antMatchers("/api/**").authenticated()
    }.exceptionHandling {
        it.authenticationEntryPoint(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
    }
        .csrf().disable()
        .oauth2ResourceServer().jwt()
    return http.build()
}

@Bean
fun uiFilterChain(http: HttpSecurity): SecurityFilterChain {
    http.authorizeRequests { authorize ->
        authorize.antMatchers("/", "/login", "/error", "/webjars/**").permitAll().anyRequest()
            .authenticated()
    }.logout {
        it.logoutSuccessUrl("/").permitAll()
    }.exceptionHandling {
        it.authenticationEntryPoint(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
    }.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
        .and()
        .oauth2Login { oauth2Login ->
            oauth2Login.loginPage("/login")
            oauth2Login.defaultSuccessUrl("/", true)
        }.oauth2Client()
    return http.build()
}

现在,在Map到路径的方法中,我可以执行一些更具体的身份验证逻辑:

@GetMapping("/api/securedByJWT")
fun getResponse(@AuthenticationPrincipal jwt: Jwt): ResponseEntity<String> {
    val email = jwt.claims["email"] as String
    val oauthUser = userRepository.findByUsername(email)
    if(oauthUser == null){
        return ResponseEntity("User not registered.", UNAUTHORIZED)
    }
    return ResponseEntity("Hello world!", HttpStatus.OK)
}

相关问题