首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 集成新浪微博 OAuth2 認證與 API 調用實戰

     編輯:Java 相關知識

前言

新浪微博開放平台採用 OAuth 2.0 授權體系。本文詳細講解 Java 後端集成微博登錄授權、Access Token 管理、API 調用(發布微博、上傳圖片)的完整實現流程。

一、OAuth 2.0 授權碼模式流程


+--------+                               +---------------+
|        |--(A) Authorization Request -->|   Resource    |
|        |                               |     Owner     |
|        |<-(B) Authorization Grant -----|               |
|        |                               +---------------+
|        |
|        |                               +---------------+
|        |--(C) Authorization Grant -->| Authorization |
| Client |                               |     Server    |
|        |<-(D) Access Token -----------|               |
|        |                               +---------------+
|        |
|        |                               +---------------+
|        |--(E) Access Token ---------->|    Resource   |
|        |                               |     Server    |
|        |<-(F) Protected Resource -----|               |
+--------+                               +---------------+

微博特定流程

  1. 用戶點擊「微博登錄」→ 重定向到微博授權頁
  2. 用戶授權後,微博重定向回 `redirect_uri`,攜帶 `code`
  3. 後端用 `code` 換取 `access_token`
  4. 使用 `access_token` 調用業務 API

二、核心配置與常量


@ConfigurationProperties(prefix = "weibo")
@Data
public class WeiboProperties {
    private String clientId;        // App Key
    private String clientSecret;    // App Secret
    private String redirectUri;     // 回調地址,需在開放平台配置
    private String authUrl = "https://api.weibo.com/oauth2/authorize";
    private String tokenUrl = "https://api.weibo.com/oauth2/access_token";
    private String apiBase = "https://api.weibo.com/2/";
}

三、授權碼獲取與 Token 交換

1. 構建授權 URL


@Service
public class WeiboOAuthService {
    
    @Autowired
    private WeiboProperties props;
    
    public String buildAuthorizeUrl(String state) {
        return String.format("%s?client_id=%s&redirect_uri=%s&response_type=code&state=%s",
            props.getAuthUrl(), 
            props.getClientId(),
            URLEncoder.encode(props.getRedirectUri(), StandardCharsets.UTF_8),
            state); // 防 CSRF
    }
    
    // 回調處理
    public WeiboTokenResponse handleCallback(String code, String state) {
        // 1. 驗證 state(防 CSRF)
        if (!stateCache.verifyAndRemove(state)) {
            throw new WeiboOAuthException("無效的 state 參數");
        }
        
        // 2. 換取 Access Token
        return exchangeCodeForToken(code);
    }
    
    private WeiboTokenResponse exchangeCodeForToken(String code) {
        String url = String.format("%s?client_id=%s&client_secret=%s&grant_type=authorization_code&code=%s&redirect_uri=%s",
            props.getTokenUrl(),
            props.getClientId(),
            props.getClientSecret(),
            code,
            URLEncoder.encode(props.getRedirectUri(), StandardCharsets.UTF_8));
        
        String response = httpClient.post(url, null); // 表單提交
        return gson.fromJson(response, WeiboTokenResponse.class);
    }
}

2. Token 響應模型


@Data
public class WeiboTokenResponse {
    private String access_token;      // 接口調用憑證
    private Long expires_in;          // 有效期(秒)
    private String uid;               // 用戶 ID
    private String reminder;          // 提醒信息
    private Integer error;            // 錯誤碼
    private String error_description; // 錯誤描述
    
    public boolean isSuccess() { return error == null || error == 0; }
}

四、Access Token 管理

1. 緩存策略


@Service
public class WeiboTokenManager {
    
    private static final String TOKEN_KEY_PREFIX = "weibo:token:";
    private static final String REFRESH_LOCK_PREFIX = "weibo:refresh:lock:";
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private WeiboOAuthService oAuthService;
    @Autowired
    private WeiboProperties props;
    
    public String getValidAccessToken(Long uid) {
        String key = TOKEN_KEY_PREFIX + uid;
        
        // 1. 讀取緩存
        String token = redisTemplate.opsForValue().get(key);
        if (StringUtils.hasText(token)) {
            return token;
        }
        
        // 2. 緩存未命中,需用戶重新授權(或使用 Refresh Token)
        throw new WeiboTokenExpiredException("Token 已過期,請重新授權");
    }
    
    public void cacheToken(Long uid, WeiboTokenResponse tokenResp) {
        String key = TOKEN_KEY_PREFIX + uid;
        // 提前 10 分鐘過期,留出刷新時間
        long ttl = tokenResp.getExpires_in() - 600;
        redisTemplate.opsForValue().set(key, tokenResp.getAccess_token(), ttl, TimeUnit.SECONDS);
        
        // 可選:持久化 Refresh Token 到數據庫
        // weiboUserRepository.updateTokens(uid, tokenResp.getAccess_token(), refreshToken);
    }
    
    // 刷新 Token(微博支持 refresh_token)
    public WeiboTokenResponse refreshToken(String refreshToken) {
        String url = String.format("%s?client_id=%s&client_secret=%s&grant_type=refresh_token&refresh_token=%s",
            props.getTokenUrl(), props.getClientId(), props.getClientSecret(), refreshToken);
        String response = httpClient.post(url, null);
        return gson.fromJson(response, WeiboTokenResponse.class);
    }
}

五、核心 API 調用封裝

1. 統一 API 客戶端


@Service
public class WeiboApiClient {
    
    @Autowired
    private WeiboTokenManager tokenManager;
    @Autowired
    private WeiboProperties props;
    private final Gson gson = new GsonBuilder().create();
    private final HttpClient httpClient;
    
    // 發布純文本微博
    public WeiboStatusResponse postStatus(Long uid, String content) {
        String token = tokenManager.getValidAccessToken(uid);
        String url = props.getApiBase() + "statuses/update.json";
        
        Map<String, Object> params = new HashMap<>();
        params.put("access_token", token);
        params.put("status", content); // 必須 URL 編碼
        
        return postForm(url, params, WeiboStatusResponse.class);
    }
    
    // 發布帶圖片微博
    public WeiboStatusResponse uploadStatus(Long uid, String content, File image) {
        String token = tokenManager.getValidAccessToken(uid);
        String url = props.getApiBase() + "statuses/upload.json";
        
        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        builder.addTextBody("access_token", token, ContentType.TEXT_PLAIN);
        builder.addTextBody("status", content, ContentType.TEXT_PLAIN);
        builder.addBinaryBody("pic", image, ContentType.IMAGE_JPEG, image.getName());
        
        HttpPost post = new HttpPost(url);
        post.setEntity(builder.build());
        
        return executeRequest(post, WeiboStatusResponse.class);
    }
    
    // 獲取用戶信息
    public WeiboUserInfo getUserInfo(Long uid) {
        String token = tokenManager.getValidAccessToken(uid);
        String url = props.getApiBase() + "users/show.json?access_token=" + token + "&uid=" + uid;
        return executeGet(url, WeiboUserInfo.class);
    }
    
    private <T> T postForm(String url, Map<String, Object> params, Class<T> respType) {
        // 構建表單實體...
    }
    
    private <T> T executeRequest(HttpRequestBase request, Class<T> respType) {
        // 統一執行、異常處理、日誌...
    }
}

2. 響應模型


@Data
public class WeiboStatusResponse {
    private String id;              // 微博 ID
    private String idstr;           // 字符串 ID
    private String text;            // 微博內容
    private String source;          // 來源
    private Boolean favorited;
    private Boolean truncated;
    private String created_at;
    private WeiboUserInfo user;     // 發布者信息
    private Integer error;
    private String error_description;
}

@Data
public class WeiboUserInfo {
    private Long id;
    private String screen_name;
    private String profile_image_url;
    private String profile_url;
    private Integer followers_count;
    private Integer friends_count;
    private Integer statuses_count;
    private Boolean verified;
}

六、Spring Boot 整合示例

1. Controller 層


@Controller
@RequestMapping("/weibo")
public class WeiboController {
    
    @Autowired
    private WeiboOAuthService oAuthService;
    @Autowired
    private WeiboApiClient apiClient;
    @Autowired
    private WeiboTokenManager tokenManager;
    
    // 步驟1:跳轉授權頁
    @GetMapping("/login")
    public String login(HttpSession session) {
        String state = UUID.randomUUID().toString();
        session.setAttribute("weibo_state", state);
        return "redirect:" + oAuthService.buildAuthorizeUrl(state);
    }
    
    // 步驟2:回調處理
    @GetMapping("/callback")
    public String callback(@RequestParam("code") String code,
                           @RequestParam("state") String state,
                           HttpSession session,
                           RedirectAttributes attrs) {
        try {
            // 驗證 state
            String sessionState = (String) session.getAttribute("weibo_state");
            if (!state.equals(sessionState)) {
                throw new IllegalStateException("State 不匹配");
            }
            
            WeiboTokenResponse tokenResp = oAuthService.handleCallback(code, state);
            if (!tokenResp.isSuccess()) {
                throw new WeiboOAuthException(tokenResp.getError_description());
            }
            
            // 緩存 Token
            tokenManager.cacheToken(tokenResp.getUid(), tokenResp);
            
            // 獲取用戶信息
            WeiboUserInfo user = apiClient.getUserInfo(tokenResp.getUid());
            session.setAttribute("weibo_user", user);
            
            return "redirect:/weibo/home";
        } catch (Exception e) {
            attrs.addFlashAttribute("error", "授權失敗:" + e.getMessage());
            return "redirect:/login";
        }
    }
    
    // 發布微博
    @PostMapping("/post")
    @ResponseBody
    public Result<String> postStatus(@RequestParam String content,
                                     @RequestParam(required = false) MultipartFile image,
                                     HttpSession session) {
        WeiboUserInfo user = (WeiboUserInfo) session.getAttribute("weibo_user");
        if (user == null) return Result.error("未授權");
        
        try {
            WeiboStatusResponse resp;
            if (image != null && !image.isEmpty()) {
                File tempFile = File.createTempFile("weibo_", ".jpg");
                image.transferTo(tempFile);
                resp = apiClient.uploadStatus(user.getId(), content, tempFile);
                tempFile.deleteOnExit();
            } else {
                resp = apiClient.postStatus(user.getId(), content);
            }
            
            if (resp.getError() != null && resp.getError() != 0) {
                return Result.error("發布失敗:" + resp.getError_description());
            }
            return Result.success("發布成功,微博 ID:" + resp.getId());
        } catch (Exception e) {
            return Result.error("發布異常:" + e.getMessage());
        }
    }
}

七、常見問題與避坑

問題原因解決
redirect_uri_mismatch回調地址與開放平台配置不一致精確匹配域名、路徑、協議、端口
access_token 過期有效期 30 天,未刷新使用 refresh_token 或引導重新授權
圖片上傳失敗格式/大小/Content-Type支持 jpg/png/gif <5MB;multipart/form-data
發布內容被屏蔽敏感詞/重複/廣告內容預檢;錯誤碼 10007/10008 需提示用戶
簽名驗證失敗App Secret 泄露/參與簽名參數錯誤服務端保密;嚴格按文檔構建簽名基礎串

八、安全最佳實踐

  1. State 參數防 CSRF:每次授權生成隨機 state,Session 存儲,回調驗證
  2. Token 安全存儲:Redis 加密存儲、數據庫加密字段、不記錄日誌
  3. 最小權限原則:僅申請必要 scope(如 `statuses_to_me_read`)
  4. 異常熔斷:API 失敗率超過閾值自動降級、告警
  5. 審計日誌:記錄授權、發布、Token 刷新等關鍵操作

總結

微博 OAuth2 集成核心在於:授權碼流程正確性、Token 生命周期管理、API 參數規範構建。封裝統一客戶端、集成 Spring 生態、做好異常處理與安全防護,即可構建穩健的微博開放平台應用。注意微博 API 頻率限制(默認 10000/天/應用),高併發場景需申請提額或實現本地限流。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved