Skip to content

OAuth2 第三方登录

一、OAuth2 概述

OAuth2 角色

┌─────────────────────────────────────────────────────────────────┐
│                      OAuth2 角色                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌──────────┐                                                   │
│  │  资源所有者 │  ← 用户(你)                                    │
│  └────┬─────┘                                                   │
│       │ 授权                                                   │
│  ┌────▼─────┐                                                   │
│  │   客户端   │  ← 第三方应用(如我们的网站)                       │
│  └────┬─────┘                                                   │
│       │ 请求授权                                                │
│  ┌────▼─────────┐                                               │
│  │   授权服务器  │  ← GitHub/微信等                               │
│  └────┬─────────┘                                               │
│       │ 颁发令牌                                                │
│  ┌────▼─────────┐                                               │
│  │   资源服务器  │  ← GitHub/微信等                               │
│  └──────────────┘                                               │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

二、授权码模式

┌─────────────────────────────────────────────────────────────────┐
│                    授权码模式流程                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. 用户点击"用 GitHub 登录"                                    │
│     ↓                                                             │
│  2. 跳转到 GitHub 授权页                                        │
│     https://github.com/login/oauth/authorize?                    │
│       client_id=xxx&redirect_uri=回调URL&scope=user:email        │
│     ↓                                                             │
│  3. 用户确认授权                                                 │
│     ↓                                                             │
│  4. GitHub 跳回回调 URL,带上授权码                               │
│     https://our-site.com/callback?code=abc123                    │
│     ↓                                                             │
│  5. 我们的服务器用授权码换 Access Token                         │
│     POST https://github.com/login/oauth/access_token             │
│     ↓                                                             │
│  6. 用 Access Token 获取用户信息                                 │
│     GET https://api.github.com/user                             │
│     ↓                                                             │
│  7. 创建/更新用户,登录成功                                      │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

三、GitHub 登录集成

GitHub OAuth App 配置

1. GitHub → Settings → Developer settings → OAuth Apps
2. New OAuth App
3. 填写:
   - Application name: Our Site
   - Homepage URL: http://localhost:8080
   - Authorization callback URL: http://localhost:8080/oauth/callback/github
4. 获取 Client ID 和 Client Secret

配置

yaml
spring:
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: your-client-id
            client-secret: your-client-secret
            scope: read:user,user:email
        provider:
          github:
            authorization-uri: https://github.com/login/oauth/authorize
            token-uri: https://github.com/login/oauth/access_token
            user-info-uri: https://api.github.com/user
            user-name-attribute: login

Security 配置

java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .oauth2Login(oauth2 -> oauth2
                .loginPage("/login")           // 登录页
                .defaultSuccessUrl("/home")    // 成功后的默认页
                .failureUrl("/login?error")    // 失败后的页
            )
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/login", "/oauth/**").permitAll()
                .anyRequest().authenticated()
            );
        
        return http.build();
    }
}

获取用户信息

java
@RestController
public class OAuthController {
    
    @GetMapping("/oauth/callback/github")
    public String githubCallback(@AuthenticationPrincipal OAuth2User oauth2User) {
        String githubId = oauth2User.getName();
        String email = oauth2User.getAttribute("email");
        String avatar = oauth2User.getAttribute("avatar_url");
        
        // 查找或创建用户
        User user = userService.findOrCreateByGithubId(githubId, email, avatar);
        
        // 登录
        return "redirect:/home";
    }
}

四、自定义 OAuth2 登录

实现

java
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
    
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2User oauth2User = new DefaultOAuth2UserService().loadUser(userRequest);
        
        String registrationId = userRequest.getClientRegistration().getRegistrationId();
        
        // 根据不同 provider 映射用户信息
        Map<String, Object> attributes = oauth2User.getAttributes();
        
        if ("github".equals(registrationId)) {
            return new GithubUser(attributes);
        } else if ("wechat".equals(registrationId)) {
            return new WechatUser(attributes);
        }
        
        return oauth2User;
    }
}

public class GithubUser implements OAuth2User {
    
    private Map<String, Object> attributes;
    private Set<GrantedAuthority> authorities;
    
    public GithubUser(Map<String, Object> attributes) {
        this.attributes = attributes;
        this.authorities = Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"));
    }
    
    @Override
    public Map<String, Object> getAttributes() {
        return attributes;
    }
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }
    
    @Override
    public String getName() {
        return (String) attributes.get("login");
    }
    
    public String getGithubId() {
        return String.valueOf(attributes.get("id"));
    }
}

五、面试高频问题

Q1: OAuth2 有哪些模式?

  1. 授权码模式(最安全,推荐)
  2. 简化模式(已废弃)
  3. 密码模式(信任应用)
  4. 客户端模式(机器对机器)

Q2: 为什么授权码模式最安全?

授权码通过浏览器传递,Token 通过后端传递,避免 Token 泄露

Q3: Access Token 和 Refresh Token 区别?

  • Access Token:访问资源
  • Refresh Token:获取新的 Access Token

六、下一章预告

下一章我们将学习 Feign 远程调用

  • OpenFeign 声明式 HTTP 客户端
  • 负载均衡与熔断
  • 请求拦截器

基于 MIT 许可发布