Java实现第三方登录案例

wen java案例 3

本文目录导读:

Java实现第三方登录案例

  1. 项目结构
  2. Maven依赖配置 (pom.xml)
  3. 配置文件 (application.yml)
  4. 配置类
  5. 实体类
  6. JWT工具类
  7. OAuth服务层
  8. 用户服务层
  9. 控制器
  10. 安全配置
  11. 主应用类
  12. 前端示例 (HTML)
  13. SQL脚本
  14. GitHub OAuth应用配置
  15. 使用说明
  16. 扩展其他平台

我将为您提供一个完整的Java第三方登录实现案例,以GitHub登录为例进行说明。

项目结构

third-party-login-demo/
├── pom.xml
├── src/main/java/com/example/oauth/
│   ├── controller/
│   │   └── OAuthController.java
│   ├── config/
│   │   └── OAuthConfig.java
│   ├── service/
│   │   ├── GitHubOAuthService.java
│   │   └── UserService.java
│   ├── model/
│   │   ├── GitHubUser.java
│   │   └── AuthToken.java
│   └── util/
│       └── JwtUtil.java
├── src/main/resources/
│   └── application.yml

Maven依赖配置 (pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>third-party-login</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
    </parent>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!-- Spring Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Spring Data JPA -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>
        <!-- JWT -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        <!-- Spring Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- HTTP Client -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.14</version>
        </dependency>
    </dependencies>
</project>

配置文件 (application.yml)

server:
  port: 8080
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/oauth_demo?useSSL=false&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL8Dialect
oauth:
  github:
    client-id: your-github-client-id
    client-secret: your-github-client-secret
    redirect-uri: http://localhost:8080/oauth/github/callback
    authorize-url: https://github.com/login/oauth/authorize
    token-url: https://github.com/login/oauth/access_token
    user-url: https://api.github.com/user
jwt:
  secret: your-secret-key-at-least-256-bits-long-for-hs256
  expiration: 86400000  # 24小时
app:
  frontend-callback: http://localhost:3000/oauth/callback

配置类

package com.example.oauth.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "oauth.github")
public class GitHubOAuthConfig {
    private String clientId;
    private String clientSecret;
    private String redirectUri;
    private String authorizeUrl;
    private String tokenUrl;
    private String userUrl;
}
package com.example.oauth.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "jwt")
public class JwtConfig {
    private String secret;
    private long expiration;
}

实体类

package com.example.oauth.model;
import lombok.Data;
import javax.persistence.*;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(unique = true)
    private String email;
    private String username;
    private String avatarUrl;
    @Column(name = "provider")
    private String provider;  // github, google, etc
    @Column(name = "provider_id", unique = true)
    private String providerId;
    @Column(name = "access_token")
    private String accessToken;
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    @Column(name = "last_login")
    private LocalDateTime lastLogin;
    @PrePersist
    protected void onCreate() {
        createdAt = LocalDateTime.now();
    }
}

JWT工具类

package com.example.oauth.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtUtil {
    @Value("${jwt.secret}")
    private String secret;
    @Value("${jwt.expiration}")
    private long expiration;
    private SecretKey getSigningKey() {
        byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
        return Keys.hmacShaKeyFor(keyBytes);
    }
    public String generateToken(Long userId, String username, String provider) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + expiration);
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userId);
        claims.put("username", username);
        claims.put("provider", provider);
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(username)
                .setIssuedAt(now)
                .setExpiration(expiryDate)
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }
    public Claims parseToken(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

OAuth服务层

package com.example.oauth.service;
import com.example.oauth.config.GitHubOAuthConfig;
import com.example.oauth.model.GitHubUser;
import com.example.oauth.model.User;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
@Service
public class GitHubOAuthService {
    @Autowired
    private GitHubOAuthConfig config;
    @Autowired
    private UserService userService;
    private final RestTemplate restTemplate = new RestTemplate();
    private final ObjectMapper objectMapper = new ObjectMapper();
    /**
     * 获取授权URL
     */
    public String getAuthorizationUrl() {
        return config.getAuthorizeUrl() + "?client_id=" + config.getClientId() 
               + "&redirect_uri=" + config.getRedirectUri()
               + "&scope=user:email";
    }
    /**
     * 获取访问令牌
     */
    public String getAccessToken(String code) {
        String tokenUrl = config.getTokenUrl();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.setAccept(java.util.Collections.singletonList(MediaType.APPLICATION_JSON));
        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("client_id", config.getClientId());
        body.add("client_secret", config.getClientSecret());
        body.add("code", code);
        body.add("redirect_uri", config.getRedirectUri());
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);
        ResponseEntity<String> response = restTemplate.postForEntity(tokenUrl, request, String.class);
        try {
            JsonNode jsonNode = objectMapper.readTree(response.getBody());
            return jsonNode.get("access_token").asText();
        } catch (Exception e) {
            throw new RuntimeException("Failed to get access token", e);
        }
    }
    /**
     * 获取GitHub用户信息
     */
    public GitHubUser getGitHubUser(String accessToken) {
        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(accessToken);
        headers.setAccept(java.util.Collections.singletonList(MediaType.APPLICATION_JSON));
        HttpEntity<String> entity = new HttpEntity<>(headers);
        ResponseEntity<GitHubUser> response = restTemplate.exchange(
            config.getUserUrl(),
            HttpMethod.GET,
            entity,
            GitHubUser.class
        );
        return response.getBody();
    }
    /**
     * 处理GitHub OAuth回调
     */
    public User handleOAuthCallback(String code) {
        // 获取访问令牌
        String accessToken = getAccessToken(code);
        // 获取GitHub用户信息
        GitHubUser githubUser = getGitHubUser(accessToken);
        // 查找或创建用户
        User user = userService.findOrCreateUser(githubUser, accessToken);
        return user;
    }
}
package com.example.oauth.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class GitHubUser {
    private Long id;
    private String login;
    private String name;
    private String email;
    private String avatarUrl;
    private String bio;
}

用户服务层

package com.example.oauth.service;
import com.example.oauth.model.GitHubUser;
import com.example.oauth.model.User;
import com.example.oauth.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Optional;
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    /**
     * 查找或创建GitHub用户
     */
    public User findOrCreateUser(GitHubUser githubUser, String accessToken) {
        Optional<User> existingUser = userRepository.findByProviderId(String.valueOf(githubUser.getId()));
        User user;
        if (existingUser.isPresent()) {
            user = existingUser.get();
            // 更新用户信息
            user.setUsername(githubUser.getLogin() != null ? githubUser.getLogin() : githubUser.getName());
            user.setAvatarUrl(githubUser.getAvatarUrl());
            user.setAccessToken(accessToken);
            user.setLastLogin(LocalDateTime.now());
        } else {
            // 创建新用户
            user = new User();
            user.setProvider("github");
            user.setProviderId(String.valueOf(githubUser.getId()));
            user.setUsername(githubUser.getLogin() != null ? githubUser.getLogin() : githubUser.getName());
            user.setEmail(githubUser.getEmail());
            user.setAvatarUrl(githubUser.getAvatarUrl());
            user.setAccessToken(accessToken);
            user.setLastLogin(LocalDateTime.now());
        }
        return userRepository.save(user);
    }
    /**
     * 通过ID查找用户
     */
    public Optional<User> findById(Long id) {
        return userRepository.findById(id);
    }
}
package com.example.oauth.repository;
import com.example.oauth.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByProviderId(String providerId);
    Optional<User> findByEmail(String email);
}

控制器

package com.example.oauth.controller;
import com.example.oauth.model.User;
import com.example.oauth.service.GitHubOAuthService;
import com.example.oauth.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/oauth")
public class OAuthController {
    @Autowired
    private GitHubOAuthService gitHubOAuthService;
    @Autowired
    private JwtUtil jwtUtil;
    @Value("${app.frontend-callback}")
    private String frontendCallback;
    /**
     * 发起GitHub登录
     */
    @GetMapping("/github/login")
    public void githubLogin(HttpServletResponse response) throws IOException {
        String authorizationUrl = gitHubOAuthService.getAuthorizationUrl();
        response.sendRedirect(authorizationUrl);
    }
    /**
     * GitHub回调
     */
    @GetMapping("/github/callback")
    public ResponseEntity<?> githubCallback(
            @RequestParam(value = "code") String code,
            @RequestParam(value = "state", required = false) String state,
            HttpServletResponse response) throws IOException {
        try {
            // 处理OAuth回调
            User user = gitHubOAuthService.handleOAuthCallback(code);
            // 生成JWT令牌
            String token = jwtUtil.generateToken(user.getId(), user.getUsername(), user.getProvider());
            // 构建响应数据
            Map<String, Object> authResponse = new HashMap<>();
            authResponse.put("token", token);
            authResponse.put("userId", user.getId());
            authResponse.put("username", user.getUsername());
            authResponse.put("avatarUrl", user.getAvatarUrl());
            authResponse.put("provider", user.getProvider());
            // 重定向到前端并携带token
            String redirectUrl = frontendCallback + "?token=" + token 
                    + "&userId=" + user.getId()
                    + "&username=" + user.getUsername();
            response.sendRedirect(redirectUrl);
            return null; // 已经重定向
        } catch (Exception e) {
            e.printStackTrace();
            response.sendRedirect(frontendCallback + "?error=authentication_failed");
            return null;
        }
    }
    /**
     * 获取当前用户信息
     */
    @GetMapping("/user/info")
    public ResponseEntity<?> getUserInfo(@RequestHeader("Authorization") String authorization) {
        try {
            String token = authorization.replace("Bearer ", "");
            if (jwtUtil.validateToken(token)) {
                org.springframework.security.core.userdetails.User user = 
                    org.springframework.security.core.userdetails.User
                    .withUsername("placeholder")
                    .password("")
                    .roles("USER")
                    .build();
                Map<String, Object> response = new HashMap<>();
                response.put("valid", true);
                response.put("tokenInfo", jwtUtil.parseToken(token));
                return ResponseEntity.ok(response);
            } else {
                return ResponseEntity.status(401).body("Invalid token");
            }
        } catch (Exception e) {
            return ResponseEntity.status(401).body("Invalid token");
        }
    }
}

安全配置

package com.example.oauth.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .cors().and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            .antMatchers("/oauth/github/login", "/oauth/github/callback").permitAll()
            .anyRequest().authenticated()
            .and()
            .httpBasic()
            .and()
            .formLogin().disable();
        return http.build();
    }
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

主应用类

package com.example.oauth;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@EnableConfigurationProperties
public class OAuthApplication {
    public static void main(String[] args) {
        SpringApplication.run(OAuthApplication.class, args);
    }
}

前端示例 (HTML)

<!DOCTYPE html>
<html>
<head>OAuth Login Demo</title>
</head>
<body>
    <h1>第三方登录示例</h1>
    <button onclick="loginWithGitHub()">使用GitHub登录</button>
    <div id="userInfo" style="display: none;">
        <h2>用户信息</h2>
        <p>用户名: <span id="username"></span></p>
        <img id="avatar" width="100" height="100"/>
    </div>
    <script>
        // 获取URL参数
        function getUrlParam(name) {
            const url = new URL(window.location.href);
            return url.searchParams.get(name);
        }
        // 登录函数
        function loginWithGitHub() {
            window.location.href = 'http://localhost:8080/oauth/github/login';
        }
        // 处理登录回调
        window.onload = function() {
            const token = getUrlParam('token');
            const username = getUrlParam('username');
            if (token && username) {
                document.getElementById('username').textContent = username;
                document.getElementById('userInfo').style.display = 'block';
                // 存储token
                localStorage.setItem('auth_token', token);
                // 获取完整用户信息
                fetchUserInfo(token);
                // 清理URL参数
                window.history.replaceState({}, document.title, window.location.pathname);
            }
        };
        // 获取用户详细信息
        async function fetchUserInfo(token) {
            try {
                const response = await fetch('http://localhost:8080/oauth/user/info', {
                    headers: {
                        'Authorization': `Bearer ${token}`
                    }
                });
                const userData = await response.json();
                console.log('User data:', userData);
            } catch (error) {
                console.error('Error fetching user info:', error);
            }
        }
    </script>
</body>
</html>

SQL脚本

-- 创建数据库
CREATE DATABASE IF NOT EXISTS oauth_demo DEFAULT CHARACTER SET utf8mb4;
USE oauth_demo;
-- 用户表
CREATE TABLE IF NOT EXISTS users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255),
    username VARCHAR(255),
    avatar_url VARCHAR(500),
    provider VARCHAR(50),
    provider_id VARCHAR(100),
    access_token TEXT,
    created_at DATETIME,
    last_login DATETIME,
    UNIQUE KEY uk_provider_id (provider_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

GitHub OAuth应用配置

  1. 登录GitHub
  2. 进入 Settings → Developer settings → OAuth Apps
  3. 点击 "New OAuth App"
  4. 填写配置:
    • Application name: 你的应用名称
    • Homepage URL: http://localhost:8080
    • Authorization callback URL: http://localhost:8080/oauth/github/callback
  5. 获取 Client ID 和 Client Secret

使用说明

  1. 获取GitHub OAuth凭据:在GitHub开发者设置中创建OAuth应用,获取Client ID和Client Secret。

  2. 配置应用:更新application.yml中的GitHub凭据。

  3. 运行数据库:启动MySQL,执行SQL脚本。

  4. 启动应用:运行OAuthApplication主类。

  5. 测试流程

    • 访问前端页面
    • 点击"使用GitHub登录"
    • 跳转到GitHub授权页面
    • 授权后回调到我们的应用
    • 应用创建用户并返回JWT令牌

扩展其他平台

按类似方式可以扩展Google、微信等平台的登录:

public interface OAuthService {
    String getAuthorizationUrl();
    String getAccessToken(String code);
    Object getUserInfo(String accessToken);
    User handleOAuthCallback(String code);
}
// Google OAuth Service 实现示例
@Service
public class GoogleOAuthService implements OAuthService {
    // 实现gooogle OAuth逻辑
}

这个案例提供了完整的GitHub第三方登录实现,包括OAuth授权流程、用户管理、JWT生成验证等核心功能,可以根据需要扩展其他平台的支持。

抱歉,评论功能暂时关闭!