我构建了一个 Web 服务,支持通过网站上的表单本地登录和注册。现在,我想通过允许用户使用 Facebook 帐户登录/注册来补充这一点。我设法让 Facebook 的 OAuth 登录流程正常工作,但现在我试图弄清楚该流程的“注册”部分。我知道我可以添加自定义成功处理程序以将用户信息保存到数据库中。作为一个黑客示例:
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http
.formLogin {
it.loginPage("/login")
it.usernameParameter("email")
it.passwordParameter("password")
}
.oauth2Login {
it.loginPage("/login")
it.clientRegistrationRepository(clientRegistrationRepository())
it.successHandler { _, _, authentication ->
if (authentication.isAuthenticated) {
userRepository.save(...) // persist registration details
}
}
}
但是,我对这种方法有一些疑问,并确保我以惯用的“springboot”方式执行此操作。
处理角色的最佳策略是什么?对于直接通过网站注册的用户,我分配一个默认的
ROLE_USER
,但管理员也可以授予其他角色,例如:ROLE_EDITOR
等。通过Facebook登录时,spring只是将权限OAUTH2_USER
分配给用户。我想用用户在数据库中拥有的角色来扩充或替换它。是不是就像在 successHandler 中添加逻辑来从数据存储中获取用户信息并将数据存储中的角色添加到主体对象一样简单?
我应该如何处理在网站上生成链接以便用户可以查看他们的个人资料?目前我这样做:
<a sec:authorize="hasRole('USER')" th:href="@{/profile/{id}(id=${#authentication.getPrincipal().id})}">View Profile</a>
但是,OAuth2User 没有 ID。与用户角色不同,它似乎也不是我可以设置的值。理想情况下,我希望避免在视图和其他地方使用自定义逻辑来确定用户是否通过 OAuth 进行身份验证。
第 1 部分:处理角色
在 UserDetails 实现中执行类似的操作来注入自定义角色
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return service.getRoles()
.stream()
.map(role -> new SimpleGrantedAuthority(role.getRole()))
.collect(Collectors.toList());
}
第 2 部分 - 处理用户 ID
好吧,这就是我最终所做的。
OAuth2UserService
接口OAuth用户服务
class OAuth2EmailExistsException(override val message: String): AuthenticationException(message)
@Service
class FacebookOAuth2UserService(
private val userRepository: UserRepository,
private val clockService: ClockService,
private val idService: IdService,
private val defaultOAuth2UserService: DefaultOAuth2UserService
): OAuth2UserService<OAuth2UserRequest, OAuth2User> {
override fun loadUser(userRequest: OAuth2UserRequest): OAuth2User {
val oauthUser = defaultOAuth2UserService.loadUser(userRequest)
val id = oauthUser.name
val email = oauthUser.attributes["email"] as String
val name = oauthUser.attributes["name"] as String
val persistedUser = userRepository.findByoAuthId(id).getOrElse {
if (userRepository.existsByEmail(email)) {
throw OAuth2EmailExistsException("The email associated with this Facebook account is already present in the system")
}
userRepository.save(User(
id = idService.objectId(),
firstName = name,
lastName = "",
password = "",
email = email,
status = UserStatus.ACTIVE,
roles = setOf(SimpleGrantedAuthority(AuthRoles.USER.roleName())),
joinDate = clockService.now().toEpochMilli(),
timeOfPreviousNameUpdate = 0,
oAuthId = id,
source = RegistrationSource.FACEBOOK
))
}
return FacebookOAuth2User(persistedUser, oauthUser.attributes)
}
}
新 OAuth2 用户
class FacebookOAuth2User(
private val user: User,
private val attributes: MutableMap<String, Any>
): OAuth2User, AuthUserDetails {
val id = user.id
override fun getUserId(): String = user.id
// Facebook serves the name as a single entity, so we'll just store it in the
// first name column
override fun getName(): String = user.firstName
override fun getAttributes(): MutableMap<String, Any> = attributes
override fun getAuthorities(): Set<GrantedAuthority> = user.authorities
override fun getPassword(): String = user.password
override fun getUsername(): String = user.oAuthId!!
override fun isAccountNonExpired() = user.isAccountNonLocked
override fun isAccountNonLocked() = user.isAccountNonLocked
override fun isCredentialsNonExpired() = user.isCredentialsNonExpired
override fun isEnabled() = user.isEnabled
}
OAuth2配置
@Configuration
class OAuth2Configuration() {
@Bean
fun defaultOAuth2UserService(): DefaultOAuth2UserService = DefaultOAuth2UserService()
}
安全配置
@Configuration
class SecurityConfiguration(
private val facebookOAuth2UserService: FacebookOAuth2UserService,
private val environment: Environment
) {
fun clientRegistrationRepository(): ClientRegistrationRepository {
return InMemoryClientRegistrationRepository(
CommonOAuth2Provider.FACEBOOK.getBuilder("facebook")
.clientId(environment.getRequiredProperty("FACEBOOK_APP_KEY"))
.clientSecret(environment.getRequiredProperty("FACEBOOK_APP_SECRET"))
.build()
)
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http
.formLogin {
it.loginPage("/login")
it.usernameParameter("email")
it.passwordParameter("password")
}
.oauth2Login {
it.loginPage("/login")
it.clientRegistrationRepository(clientRegistrationRepository())
it.userInfoEndpoint {
it.userService(facebookOAuth2UserService)
}
it.failureHandler { _, response, exception ->
val errorParam = when (exception) {
is OAuth2EmailExistsException -> "oauthEmailExists"
else -> "oauthError"
}
response.sendRedirect("/login?$errorParam")
}
}
...