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: loginSecurity 配置
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 有哪些模式?
- 授权码模式(最安全,推荐)
- 简化模式(已废弃)
- 密码模式(信任应用)
- 客户端模式(机器对机器)
Q2: 为什么授权码模式最安全?
授权码通过浏览器传递,Token 通过后端传递,避免 Token 泄露
Q3: Access Token 和 Refresh Token 区别?
- Access Token:访问资源
- Refresh Token:获取新的 Access Token
六、下一章预告
下一章我们将学习 Feign 远程调用:
- OpenFeign 声明式 HTTP 客户端
- 负载均衡与熔断
- 请求拦截器