Implement OAuth2 Authorization Code Flow with GitHub, Google, and WeChat Login in Spring Security

This tutorial walks through adding OAuth2 authorization‑code flow and third‑party social login (GitHub, Google, WeChat) to a Spring Security authentication server, covering architecture, project structure, configuration, code implementation, testing steps, and common pitfalls.

Coder Trainee
Coder Trainee
Coder Trainee
Implement OAuth2 Authorization Code Flow with GitHub, Google, and WeChat Login in Spring Security

After the previous episode that explored JWT enhancement and blacklist mechanisms, this article shows how to add the most common social‑login scenario—users signing in with GitHub, Google, or WeChat—by implementing the OAuth2 authorization‑code grant type in a Spring Security authentication server.

1. Goal and Architecture

The target is to support the authorization‑code flow together with third‑party login. The high‑level architecture is illustrated as:

┌─────────────────────────────────────────────────────────────────┐
│               第三方登录架构                                   │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   用户 ──▶ 我们的系统 ──▶ GitHub/微信/Google                     │
│          │            │            │                           │
│          │ 1. 跳转到第三方 │            │                           │
│          │ ◄───────────────── │            │                           │
│          │            │            │                           │
│          │ 2. 用户授权确认 │            │                           │
│          │ ──────────────────▶ │            │                           │
│          │            │            │                           │
│          │ 3. 返回授权码   │            │                           │
│          │ ◄───────────────── │            │                           │
│          │            │            │                           │
│          │ 4. 用授权码换Token │            │                           │
│          │ ──────────────────▶ │            │                           │
│          │            │            │                           │
│          │ 5. 获取用户信息 │            │                           │
│          │ ◄───────────────── │            │                           │
│                                                                 │
│   ✅ 支持 GitHub 登录                                            │
│   ✅ 支持微信登录                                                │
│   ✅ 支持 Google 登录                                            │
│   ✅ 统一返回 JWT                                               │
└─────────────────────────────────────────────────────────────────┘

2. OAuth2 Authorization‑Code Flow Details

┌─────────────┐      ┌─────────────┐      ┌─────────────┐
│    客户端    │      │  授权服务器  │      │   资源所有者 │
│ (前端应用)   │      │ (认证中心)   │      │   (用户)    │
└──────┬──────┘      └──────┬──────┘      └──────┬──────┘
       │                 │                 │
       │ 1. 授权请求      │                 │
       ─────────────────▶│                 │
       │                 │ 2. 登录确认      │
       │                 ─────────────────▶│
       │                 │                 │
       │                 │ 3. 用户授权      │
       │                 ◄──────────────────│
       │                 │                 │
       │ 4. 返回授权码   │                 │
       ◄──────────────────│                 │
       │                 │                 │
       │ 5. 用授权码换Token│                 │
       ─────────────────▶│                 │
       │                 │ 6. 返回 AccessToken│
       ◄──────────────────│                 │
       │                 │                 │

3. Project Structure (new social module)

spring-security-oauth2-ep03/
├── auth-server/                # Authentication & authorization center (extended)
│   ├── src/main/java/.../auth/
│   │   ├── config/
│   │   │   ├── SecurityConfig.java
│   │   │   ├── AuthorizationServerConfig.java
│   │   │   └── SocialConfig.java      # ★ New: social‑login configuration
│   │   ├── controller/
│   │   │   └── SocialLoginController.java # ★ New: social‑login controller
│   │   ├── service/
│   │   │   └── SocialUserService.java   # ★ New: social‑user service
│   │   └── AuthServerApplication.java
│   └── resources/
│       └── application.yml
├── resource-server/            # Resource service (unchanged)
└── scripts/
    └── test.sh

4. Register OAuth Apps

4.1 GitHub

Visit

GitHub Settings → Developer settings → OAuth Apps → New OAuth App

Fill in the form:

Application name: Teaching Auth Homepage URL: http://localhost:8080 Authorization callback URL: http://localhost:8080/login/oauth2/code/github Obtain the Client ID and Client Secret.

4.2 WeChat (brief)

Requires enterprise certification; obtain AppID and AppSecret.

4.3 Google (brief)

Obtain Client ID and Client Secret and configure authorized redirect URIs.

5. Dependency Configuration

<!-- auth-server/pom.xml 新增 -->
<dependencies>
    <!-- Spring Security OAuth2 Client (core for social login) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>

    <!-- WebFlux (used to call third‑party APIs) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <!-- other existing dependencies ... -->
</dependencies>

6. Application.yml (provider configuration)

# auth-server/src/main/resources/application.yml
server:
  port: 8080

spring:
  application:
    name: auth-server

  security:
    oauth2:
      client:
        registration:
          # GitHub login
          github:
            client-id: ${GITHUB_CLIENT_ID:your-github-client-id}
            client-secret: ${GITHUB_CLIENT_SECRET:your-github-client-secret}
            scope:
              - read:user
              - user:email
            redirect-uri: "http://localhost:8080/login/oauth2/code/github"
            client-name: GitHub

          # Google login
          google:
            client-id: ${GOOGLE_CLIENT_ID:your-google-client-id}
            client-secret: ${GOOGLE_CLIENT_SECRET:your-google-client-secret}
            scope:
              - openid
              - profile
              - email
            redirect-uri: "http://localhost:8080/login/oauth2/code/google"
            client-name: Google

          # WeChat login (requires enterprise certification)
          wechat:
            client-id: ${WECHAT_APP_ID:your-wechat-app-id}
            client-secret: ${WECHAT_APP_SECRET:your-wechat-app-secret}
            authorization-grant-type: authorization_code
            client-authentication-method: client_secret_post
            redirect-uri: "http://localhost:8080/login/oauth2/code/wechat"
            scope:
              - snsapi_login
            client-name: 微信登录
            provider: wechat
            authorization-uri: https://open.weixin.qq.com/connect/qrconnect
            token-uri: https://api.weixin.qq.com/sns/oauth2/access_token
            user-info-uri: https://api.weixin.qq.com/sns/userinfo
            user-name-attribute: openid
            jwk-set-uri: https://api.weixin.qq.com/sns/oauth2/access_token

logging:
  level:
    org.springframework.security: DEBUG
    org.springframework.security.oauth2: DEBUG

7. Security Configuration (supporting social login)

// auth-server/src/main/java/com/teaching/auth/config/SecurityConfig.java
package com.teaching.auth.config;

import com.teaching.auth.handler.OAuth2LoginSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler;

    public SecurityConfig(OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler) {
        this.oAuth2LoginSuccessHandler = oAuth2LoginSuccessHandler;
    }

    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/oauth2/**", "/login/**")
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .permitAll()
            )
            // ★ New: support OAuth2 login
            .oauth2Login(oauth2 -> oauth2
                .loginPage("/login")
                .successHandler(oAuth2LoginSuccessHandler)
            );
        return http.build();
    }

    @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/.well-known/jwks.json", "/oauth2/jwks", "/api/social/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .permitAll()
            )
            .oauth2Login(oauth2 -> oauth2
                .loginPage("/login")
                .successHandler(oAuth2LoginSuccessHandler)
            );
        return http.build();
    }
}

8. OAuth2 Login Success Handler

// auth-server/src/main/java/com/teaching/auth/handler/OAuth2LoginSuccessHandler.java
package com.teaching.auth.handler;

import com.teaching.auth.service.SocialUserService;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

@Component
public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler {

    private final SocialUserService socialUserService;

    public OAuth2LoginSuccessHandler(SocialUserService socialUserService) {
        this.socialUserService = socialUserService;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) throws IOException {
        OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
        String registrationId = token.getAuthorizedClientRegistrationId();
        OAuth2User oAuth2User = token.getPrincipal();
        Map<String, Object> attributes = oAuth2User.getAttributes();

        // Extract user info
        String openId = extractOpenId(registrationId, attributes);
        String nickname = extractNickname(registrationId, attributes);
        String avatar = extractAvatar(registrationId, attributes);
        String email = extractEmail(registrationId, attributes);

        // Create or fetch local user
        Long userId = socialUserService.findOrCreateUser(registrationId, openId, nickname, avatar, email);

        // Return JSON instead of redirect for front‑end convenience
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(String.format(
            "{\"code\":200,\"message\":\"登录成功\",\"userId\":%d,\"provider\":\"%s\",\"nickname\":\"%s\"}",
            userId, registrationId, nickname
        ));
    }

    private String extractOpenId(String provider, Map<String, Object> attributes) {
        return switch (provider) {
            case "github" -> attributes.get("id").toString();
            case "google" -> attributes.get("sub").toString();
            case "wechat" -> attributes.get("openid").toString();
            default -> attributes.get("sub").toString();
        };
    }

    private String extractNickname(String provider, Map<String, Object> attributes) {
        return switch (provider) {
            case "github" -> (String) attributes.get("login");
            case "google" -> (String) attributes.get("name");
            case "wechat" -> (String) attributes.get("nickname");
            default -> (String) attributes.get("name");
        };
    }

    private String extractAvatar(String provider, Map<String, Object> attributes) {
        return switch (provider) {
            case "github" -> (String) attributes.get("avatar_url");
            case "google" -> (String) attributes.get("picture");
            case "wechat" -> (String) attributes.get("headimgurl");
            default -> null;
        };
    }

    private String extractEmail(String provider, Map<String, Object> attributes) {
        return switch (provider) {
            case "github" -> (String) attributes.get("email");
            case "google" -> (String) attributes.get("email");
            default -> null;
        };
    }
}

9. Social User Service (in‑memory mock)

// auth-server/src/main/java/com/teaching/auth/service/SocialUserService.java
package com.teaching.auth.service;

import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

@Service
public class SocialUserService {

    // Simulated database (replace with real DB in production)
    private final Map<String, SocialUser> userStore = new ConcurrentHashMap<>();
    private final AtomicLong idGenerator = new AtomicLong(2000);

    /**
     * Find or create a social‑login user
     */
    public Long findOrCreateUser(String provider, String openId, String nickname, String avatar, String email) {
        String key = provider + "_" + openId;
        SocialUser existing = userStore.get(key);
        if (existing != null) {
            return existing.getId();
        }
        Long newId = idGenerator.incrementAndGet();
        SocialUser newUser = new SocialUser(newId, provider, openId, nickname, avatar, email);
        userStore.put(key, newUser);
        return newId;
    }

    public SocialUser getUser(Long userId) {
        return userStore.values().stream()
                .filter(u -> u.getId().equals(userId))
                .findFirst()
                .orElse(null);
    }

    static class SocialUser {
        private final Long id;
        private final String provider;
        private final String openId;
        private final String nickname;
        private final String avatar;
        private final String email;

        public SocialUser(Long id, String provider, String openId, String nickname, String avatar, String email) {
            this.id = id;
            this.provider = provider;
            this.openId = openId;
            this.nickname = nickname;
            this.avatar = avatar;
            this.email = email;
        }
        public Long getId() { return id; }
        public String getProvider() { return provider; }
        public String getOpenId() { return openId; }
        public String getNickname() { return nickname; }
        public String getAvatar() { return avatar; }
        public String getEmail() { return email; }
    }
}

10. Login Page (supports form login and social buttons)

<!-- auth-server/src/main/resources/templates/login.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login - Auth Server</title>
    <style>
        * { margin:0; padding:0; box-sizing:border-box; }
        body { background:linear-gradient(135deg,#667eea 0%,#764ba2 100%); min-height:100vh; display:flex; justify-content:center; align-items:center; font-family:'PingFang SC','Microsoft YaHei',sans-serif; }
        .login-container { background:white; border-radius:20px; padding:40px; width:400px; box-shadow:0 20px 40px rgba(0,0,0,0.2); }
        .login-container h2 { text-align:center; margin-bottom:30px; color:#333; }
        .form-group { margin-bottom:20px; }
        .form-group input { width:100%; padding:12px; border:1px solid #ddd; border-radius:8px; font-size:14px; }
        .form-group input:focus { outline:none; border-color:#667eea; }
        .btn { width:100%; padding:12px; background:#667eea; color:white; border:none; border-radius:8px; font-size:16px; cursor:pointer; }
        .btn:hover { background:#5a67d8; }
        .divider { text-align:center; margin:20px 0; color:#999; position:relative; }
        .divider::before, .divider::after { content:""; position:absolute; top:50%; width:45%; height:1px; background:#ddd; }
        .divider::before { left:0; }
        .divider::after { right:0; }
        .social-buttons { display:flex; gap:15px; justify-content:center; }
        .social-btn { width:50px; height:50px; border-radius:50%; display:flex; align-items:center; justify-content:center; text-decoration:none; color:white; font-weight:bold; transition:transform 0.2s; }
        .social-btn:hover { transform:scale(1.05); }
        .github { background:#24292e; }
        .google { background:#db4437; }
        .wechat { background:#07c160; }
        .error { background:#fee; color:#c33; padding:10px; border-radius:8px; margin-bottom:15px; text-align:center; }
    </style>
</head>
<body>
    <div class="login-container">
        <h2>🔐 Authentication Center Login</h2>
        <!-- Error message placeholder -->
        <div th:if="${param.error}" class="error">用户名或密码错误</div>
        <form method="post" action="/login">
            <div class="form-group"><input type="text" name="username" placeholder="用户名" required></div>
            <div class="form-group"><input type="password" name="password" placeholder="密码" required></div>
            <button type="submit" class="btn">登录</button>
        </form>
        <div class="divider">或</div>
        <div class="social-buttons">
            <a href="/oauth2/authorization/github" class="social-btn github">G</a>
            <a href="/oauth2/authorization/google" class="social-btn google">G+</a>
            <a href="/oauth2/authorization/wechat" class="social-btn wechat">微</a>
        </div>
        <div style="text-align:center; margin-top:20px; font-size:12px; color:#999">
            测试账号:user / 123456 | admin / admin123
        </div>
    </div>
</body>
</html>

11. Testing & Verification

11.1 Start Services

# Set environment variables (GitHub)
export GITHUB_CLIENT_ID=your_client_id
export GITHUB_CLIENT_SECRET=your_client_secret

# Start the auth server
cd auth-server
mvn spring-boot:run

11.2 Access Login Page

Open a browser and navigate to http://localhost:8080/login.

11.3 Test GitHub Login

Click the GitHub button → redirect to GitHub authorization page → confirm → return to the application and see a JSON response indicating successful login.

11.4 Test Form Login

用户名: user
密码: 123456

12. Common Issues and Pitfalls

Redirect‑URI Mismatch

Symptom: redirect_uri_mismatch Solution: Ensure the callback URL configured in the GitHub OAuth App exactly matches the redirect-uri value in application.yml.

WeChat Login Requires Enterprise Certification

WeChat Open Platform only allows web login for verified enterprise accounts. As an alternative, use WeChat public‑platform QR‑code login.

Google Login Configuration

Google requires the authorized JavaScript origins and redirect URIs to be whitelisted in the Google Cloud console.

13. Next Episode Preview

Spring Security + OAuth2 Authentication (Part 4): Resource Service and Permission Control

Method‑level security annotations

Dynamic permission enforcement

Multi‑tenant data isolation

Permission cache optimization

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaGoogleGitHubOAuth2wechatSpring Securityauthorization-codesocial-login
Coder Trainee
Written by

Coder Trainee

Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.