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