如何保存使用 OAuth 2 (Spring) 登录的用户?
Posted
技术标签:
【中文标题】如何保存使用 OAuth 2 (Spring) 登录的用户?【英文标题】:How do you save users who have logged in with OAuth 2 (Spring)? 【发布时间】:2018-07-13 21:12:15 【问题描述】:我的主要目标是存储每个用户的客户端 ID,一旦他们使用谷歌登录。这个github repo 包含了我到目前为止所需要的大部分内容。关注的两个主要文件是OAuthSecurityConfig.java 和UserRestController.java。
当我导航到/user
时,主体包含我需要的有关用户的所有详细信息。因此我可以使用以下 sn-ps 来获取我需要的数据:
Authentication a = SecurityContextHolder.getContext().getAuthentication();
String clientId = ((OAuth2Authentication) a).getOAuth2Request().getClientId();
然后我可以将 clientId 存储在 repo 中
User user = new User(clientId);
userRepository.save(user);
这样做的问题是用户不必导航到/user
。因此,无需注册即可导航到/score/user1
。
此 API 旨在成为未来 android 应用程序的后端,因此将 jquery 重定向到 /user
将不安全且无法正常工作。
我尝试过的事情:
尝试 1
我创建了以下类:
@Service
public class CustomUserDetailsService implements UserDetailsService
private final UserRepository userRepository;
@Autowired
public CustomUserDetailsService(UserRepository userRepository)
this.userRepository = userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
User user = userRepository.findByUsername(username);
if (user == null)
throw new UsernameNotFoundException(String.format("User %s does not exist!", username));
return new UserRepositoryUserDetails(user);
并覆盖WebSecurityConfigurerAdapter
with:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
auth.userDetailsService(customUserDetailsService);
当用户登录时,这两个被覆盖的方法都不会被调用(我用System.out.println
检查过)
尝试 2
我尝试添加.userDetailsService(customUserDetailsService)
到:
@Override
protected void configure(HttpSecurity http) throws Exception
http
// Starts authorizing configurations.
.authorizeRequests()
// Do not require auth for the "/" and "/index.html" URLs
.antMatchers("/", "/**.html", "/**.js").permitAll()
// Authenticate all remaining URLs.
.anyRequest().fullyAuthenticated()
.and()
.userDetailsService(customUserDetailsService)
// Setting the logout URL "/logout" - default logout URL.
.logout()
// After successful logout the application will redirect to "/" path.
.logoutSuccessUrl("/")
.permitAll()
.and()
// Setting the filter for the URL "/google/login".
.addFilterAt(filter(), BasicAuthenticationFilter.class)
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
这两种方法都没有被调用,我不觉得我离解决方案更近了。任何帮助将不胜感激。
【问题讨论】:
【参考方案1】:您可以收听AuthenticationSuccessEvent
。例如:
@Bean
ApplicationListener<AuthenticationSuccessEvent> doSomething()
return new ApplicationListener<AuthenticationSuccessEvent>()
@Override
void onApplicationEvent(AuthenticationSuccessEvent event)
OAuth2Authentication authentication = (OAuth2Authentication) event.authentication;
// get required details from OAuth2Authentication instance and proceed further
;
【讨论】:
【参考方案2】:这里的方法是提供一个自定义的 OidcUserService 并覆盖 loadUser() 方法,因为 Google 登录是基于 OpenId Connect 的。
首先定义一个模型类来保存提取的数据,如下所示:
public class GoogleUserInfo
private Map<String, Object> attributes;
public GoogleUserInfo(Map<String, Object> attributes)
this.attributes = attributes;
public String getId()
return (String) attributes.get("sub");
public String getName()
return (String) attributes.get("name");
public String getEmail()
return (String) attributes.get("email");
然后使用 loadUser() 方法创建自定义 OidcUserService,该方法首先调用提供的框架实现,然后添加您自己的逻辑来持久化您需要的用户数据,如下所示:
@Service
public class CustomOidcUserService extends OidcUserService
@Autowired
private UserRepository userRepository;
@Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException
OidcUser oidcUser = super.loadUser(userRequest);
try
return processOidcUser(userRequest, oidcUser);
catch (Exception ex)
throw new InternalAuthenticationServiceException(ex.getMessage(), ex.getCause());
private OidcUser processOidcUser(OidcUserRequest userRequest, OidcUser oidcUser)
GoogleUserInfo googleUserInfo = new GoogleUserInfo(oidcUser.getAttributes());
// see what other data from userRequest or oidcUser you need
Optional<User> userOptional = userRepository.findByEmail(googleUserInfo.getEmail());
if (!userOptional.isPresent())
User user = new User();
user.setEmail(googleUserInfo.getEmail());
user.setName(googleUserInfo.getName());
// set other needed data
userRepository.save(user);
return oidcUser;
并在安全配置类中注册自定义的OidcUserService:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
@Autowired
private CustomOidcUserService customOidcUserService;
@Override
public void configure(HttpSecurity http) throws Exception
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login()
.userInfoEndpoint()
.oidcUserService(customOidcUserService);
模式详细解释可参考文档:
https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2login-advanced-oidc-user-service
【讨论】:
【参考方案3】:如果其他人对此感到困惑,我的解决方案是创建一个自定义类,从
OAuth2ClientAuthenticationProcessingFilter
然后重写 successfulAuthentication
方法以获取用户身份验证详细信息并将其保存到我的数据库中。
示例(kotlin):
在您的 ssoFilter 方法(如果您按照本教程 https://spring.io/guides/tutorials/spring-boot-oauth2 操作)或您用于注册 ouath 客户端的 wharever 上,更改使用
val googleFilter = Auth2ClientAuthenticationProcessingFilter("/login/google");
为您的自定义类
val googleFilter = CustomAuthProcessingFilter("login/google")
当然还要声明 CustomAuthProcessingFilter 类
class CustomAuthProcessingFilter(defaultFilterProcessesUrl: String?)
: OAuth2ClientAuthenticationProcessingFilter(defaultFilterProcessesUrl)
override fun successfulAuthentication(request: HttpServletRequest?, response: HttpServletResponse?, chain: FilterChain?, authResult: Authentication?)
super.successfulAuthentication(request, response, chain, authResult)
// Check if user is authenticated.
if (authResult === null || !authResult.isAuthenticated)
return
// Use userDetails to grab the values you need like socialId, email, userName, etc...
val userDetails: LinkedHashMap<*, *> = userAuthentication.details as LinkedHashMap<*, *>
【讨论】:
以上是关于如何保存使用 OAuth 2 (Spring) 登录的用户?的主要内容,如果未能解决你的问题,请参考以下文章
使用 OAuth2 实现 Spring RESTful Web 服务 - 将 id 令牌保存为 id 会话
okta oauth2 Spring security 所有受保护的页面重定向到登录
如何使用 redis 使用 spring-security-oauth2 持久化令牌