JAVA示例

Java 示例(Spring Boot 3.x)

1. 依赖(pom.xml)

xml

<dependencies>
    <!-- Spring Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- HTTP客户端 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webclient</artifactId>
    </dependency>
    <!-- 工具类 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.14.0</version>
    </dependency>
    <!-- JSON处理 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>

2. 核心代码

java运行

import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.servlet.view.RedirectView;

import jakarta.servlet.http.HttpSession;
import java.util.Map;

/**
 * 软柠通行证 OAuth 2.0 Java 接入示例
 * 基于 Spring Boot 3.x + WebClient
 */
@Controller
public class RutnoOAuthController {

    // 配置项(建议放到application.yml)
    @Value("${rutno.oauth.client-id}")
    private String clientId;

    @Value("${rutno.oauth.client-secret}")
    private String clientSecret;

    @Value("${rutno.oauth.redirect-uri}")
    private String redirectUri;

    private static final String AUTH_URL = "https://id.rutno.com/rn-oauth/authorize";
    private static final String TOKEN_URL = "https://id.rutno.com/rn-oauth/token";
    private static final String USERINFO_URL = "https://id.rutno.com/rn-oauth/userinfo";

    private final WebClient webClient;

    // 构造注入WebClient
    public RutnoOAuthController(WebClient.Builder webClientBuilder) {
        this.webClient = webClientBuilder.build();
    }

    // 步骤1:引导用户授权
    @GetMapping("/auth/rutno")
    public RedirectView auth(HttpSession session) {
        // 生成随机state防CSRF
        String state = RandomStringUtils.randomAlphanumeric(16);
        session.setAttribute("oauth_state", state);

        // 构建授权URL
        String authUrl = String.format("%s?client_id=%s&redirect_uri=%s&response_type=code&scope=username email avatar&state=%s",
                AUTH_URL, clientId, redirectUri, state);

        return new RedirectView(authUrl);
    }

    // 步骤2:回调处理
    @GetMapping("/auth/rutno/callback")
    public String callback(
            @RequestParam String code,
            @RequestParam String state,
            HttpSession session
    ) {
        // 1. 校验state
        String storedState = (String) session.getAttribute("oauth_state");
        if (storedState == null || !storedState.equals(state)) {
            throw new RuntimeException("State验证失败,可能是CSRF攻击");
        }

        // 2. 换取Token
        TokenResponse tokenResponse = getAccessToken(code);
        if (tokenResponse == null || tokenResponse.getAccessToken() == null) {
            throw new RuntimeException("获取Token失败");
        }

        // 3. 获取用户信息
        UserInfo userInfo = getUserInfo(tokenResponse.getAccessToken());
        if (userInfo == null) {
            throw new RuntimeException("获取用户信息失败");
        }

        // 4. 业务处理(示例:打印用户信息)
        System.out.println("用户ID:" + userInfo.getId());
        System.out.println("用户名:" + userInfo.getUsername());
        System.out.println("邮箱:" + userInfo.getEmail());

        // 跳转到业务页面
        return "redirect:/home";
    }

    // 换取Access Token
    private TokenResponse getAccessToken(String code) {
        try {
            return webClient.post()
                    .uri(TOKEN_URL)
                    .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                    .body(BodyInserters.fromFormData(Map.of(
                            "grant_type", "authorization_code",
                            "client_id", clientId,
                            "client_secret", clientSecret,
                            "code", code,
                            "redirect_uri", redirectUri
                    )))
                    .retrieve()
                    .bodyToMono(TokenResponse.class)
                    .block(); // 同步调用(异步场景可改用subscribe)
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    // 获取用户信息
    private UserInfo getUserInfo(String accessToken) {
        try {
            return webClient.get()
                    .uri(USERINFO_URL)
                    .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                    .retrieve()
                    .bodyToMono(UserInfo.class)
                    .block();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    // Token响应实体
    public static class TokenResponse {
        private String access_token;
        private String token_type;
        private int expires_in;
        private String refresh_token;
        private String scope;

        // Getter & Setter
        public String getAccessToken() { return access_token; }
        public void setAccessToken(String access_token) { this.access_token = access_token; }
        public String getTokenType() { return token_type; }
        public void setTokenType(String token_type) { this.token_type = token_type; }
        public int getExpiresIn() { return expires_in; }
        public void setExpiresIn(int expires_in) { this.expires_in = expires_in; }
        public String getRefreshToken() { return refresh_token; }
        public void setRefreshToken(String refresh_token) { this.refresh_token = refresh_token; }
        public String getScope() { return scope; }
        public void setScope(String scope) { this.scope = scope; }
    }

    // 用户信息实体
    public static class UserInfo {
        private String id;
        private String username;
        private String nickname;
        private String email;
        private String avatar;

        // Getter & Setter
        public String getId() { return id; }
        public void setId(String id) { this.id = id; }
        public String getUsername() { return username; }
        public void setUsername(String username) { this.username = username; }
        public String getNickname() { return nickname; }
        public void setNickname(String nickname) { this.nickname = nickname; }
        public String getEmail() { return email; }
        public void setEmail(String email) { this.email = email; }
        public String getAvatar() { return avatar; }
        public void setAvatar(String avatar) { this.avatar = avatar; }
    }
}

3. 配置文件(application.yml)

yaml

rutno:
  oauth:
    client-id: YOUR_CLIENT_ID
    client-secret: YOUR_CLIENT_SECRET
    redirect-uri: https://your-domain.com/auth/rutno/callback

Java 使用说明

  1. Spring Boot 2.x 用户需将 jakarta.servlet 替换为 javax.servlet
  2. 生产环境建议:使用异步调用(subscribe 替代 block)增加请求超时配置统一异常处理Token 缓存(Redis)