如何保存使用OAuth 2(春季)登录的用户?

Gab*_*lus 5 java spring oauth spring-mvc spring-security

我的主要目的是在每个用户使用google登录后存储每个用户的client-id。到目前为止,这个github仓库包含了我需要的大部分内容。需要关注的两个主要文件是OAuthSecurityConfig.javaUserRestController.java

当我导航到时/user,主体将包含我需要的有关用户的所有详细信息。因此,我可以使用以下代码片段获取所需的数据:

Authentication a = SecurityContextHolder.getContext().getAuthentication();
String clientId = ((OAuth2Authentication) a).getOAuth2Request().getClientId();
Run Code Online (Sandbox Code Playgroud)

然后,我可以将clientId存储在仓库中

User user = new User(clientId);
userRepository.save(user);
Run Code Online (Sandbox Code Playgroud)

问题在于用户不必导航到/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);
}
}
Run Code Online (Sandbox Code Playgroud)

并覆盖WebSecurityConfigurerAdapter

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(customUserDetailsService);
}
Run Code Online (Sandbox Code Playgroud)

用户登录时不会调用这两个覆盖的方法(我用进行了检查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());
}
Run Code Online (Sandbox Code Playgroud)

仍然没有调用这两种方法,而且我觉得我离解决方案还很近。任何帮助将不胜感激。

Adr*_*Bob 15

这里的方法是提供自定义 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");
    }
}
Run Code Online (Sandbox Code Playgroud)

然后使用 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

并在安全配置类中注册自定义的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);
    }
}
Run Code Online (Sandbox Code Playgroud)

模式的详细解释可以在文档中找到:

https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2login-advanced-oidc-user-service


小智 3

万一其他人遇到这个问题,我的解决方案是创建一个扩展的自定义类, OAuth2ClientAuthenticationProcessingFilter然后重写该successfulAuthentication方法以获取用户身份验证详细信息并将其保存到我的数据库中。

示例(科特林):

在您的 ssoFilter 方法(如果您遵循本教程https://spring.io/guides/tutorials/spring-boot-oauth2)或您用来注册 ouath 客户端的任何地方,更改使用

val googleFilter = Auth2ClientAuthenticationProcessingFilter("/login/google");
Run Code Online (Sandbox Code Playgroud)

为您定制课程

val googleFilter = CustomAuthProcessingFilter("login/google")
Run Code Online (Sandbox Code Playgroud)

当然还要声明 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<*, *>
    }
}
Run Code Online (Sandbox Code Playgroud)