security(chat): secure chat stream endpoint with jwt validation and token extraction

This commit is contained in:
844143714@qq,com 2026-05-19 22:24:29 +08:00
parent 05392384b6
commit 6d12291941
4 changed files with 18 additions and 4 deletions

View File

@ -10,6 +10,7 @@ object AppConfig {
private const val KEY_REMEMBER_LOGIN = "remember_login" private const val KEY_REMEMBER_LOGIN = "remember_login"
private const val KEY_USERNAME = "saved_username" private const val KEY_USERNAME = "saved_username"
private const val KEY_PASSWORD = "saved_password" private const val KEY_PASSWORD = "saved_password"
private const val KEY_ACCESS_TOKEN = "access_token"
// 默认配置 // 默认配置
const val DEFAULT_SERVER_URL = "https://template.gyouzhe.com" const val DEFAULT_SERVER_URL = "https://template.gyouzhe.com"
@ -68,4 +69,12 @@ object AppConfig {
fun setSavedPassword(context: Context, password: String) { fun setSavedPassword(context: Context, password: String) {
getPrefs(context).edit().putString(KEY_PASSWORD, password).apply() getPrefs(context).edit().putString(KEY_PASSWORD, password).apply()
} }
fun getAccessToken(context: Context): String {
return getPrefs(context).getString(KEY_ACCESS_TOKEN, "") ?: ""
}
fun setAccessToken(context: Context, token: String) {
getPrefs(context).edit().putString(KEY_ACCESS_TOKEN, token).apply()
}
} }

View File

@ -155,6 +155,7 @@ class LoginActivity : AppCompatActivity() {
val data = json.optJSONObject("data") val data = json.optJSONObject("data")
val loginData = data?.toString() ?: "{}" val loginData = data?.toString() ?: "{}"
val accessToken = data?.optString("access_token", "") ?: "" val accessToken = data?.optString("access_token", "") ?: ""
AppConfig.setAccessToken(this@LoginActivity, accessToken)
Toast.makeText(applicationContext, "登录成功", Toast.LENGTH_SHORT).show() Toast.makeText(applicationContext, "登录成功", Toast.LENGTH_SHORT).show()

View File

@ -1,7 +1,9 @@
package com.stand.standapp.ui.chat package com.stand.standapp.ui.chat
import androidx.lifecycle.ViewModel import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.stand.standapp.AppConfig
import com.stand.standapp.ui.chat.model.ChatMessage import com.stand.standapp.ui.chat.model.ChatMessage
import com.stand.standapp.ui.chat.model.ChatSession import com.stand.standapp.ui.chat.model.ChatSession
import com.stand.standapp.ui.chat.repo.AiChatRepository import com.stand.standapp.ui.chat.repo.AiChatRepository
@ -13,7 +15,7 @@ import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.UUID import java.util.UUID
class ChatViewModel : ViewModel() { class ChatViewModel(application: Application) : AndroidViewModel(application) {
private val repository = AiChatRepository() private val repository = AiChatRepository()
private val _sessions = MutableStateFlow<List<ChatSession>>(emptyList()) private val _sessions = MutableStateFlow<List<ChatSession>>(emptyList())
@ -56,7 +58,8 @@ class ChatViewModel : ViewModel() {
// 4. 发起网络请求,流式追加内容 // 4. 发起网络请求,流式追加内容
viewModelScope.launch { viewModelScope.launch {
repository.streamChat(content) val token = AppConfig.getAccessToken(getApplication())
repository.streamChat(content, token)
.catch { e -> .catch { e ->
appendAiMessageChunk(aiMsgId, "\n[请求异常: ${e.message}]") appendAiMessageChunk(aiMsgId, "\n[请求异常: ${e.message}]")
finalizeAiMessage(aiMsgId) finalizeAiMessage(aiMsgId)

View File

@ -22,11 +22,12 @@ class AiChatRepository {
.readTimeout(0, TimeUnit.MILLISECONDS) // SSE 需要关闭读取超时 .readTimeout(0, TimeUnit.MILLISECONDS) // SSE 需要关闭读取超时
.build() .build()
fun streamChat(message: String): Flow<String> = callbackFlow { fun streamChat(message: String, token: String): Flow<String> = callbackFlow {
val json = """{"message": "$message"}""" val json = """{"message": "$message"}"""
val request = Request.Builder() val request = Request.Builder()
.url(backendUrl) .url(backendUrl)
.post(json.toRequestBody("application/json".toMediaType())) .post(json.toRequestBody("application/json".toMediaType()))
.header("Authorization", "Bearer $token")
.header("Accept", "text/event-stream") .header("Accept", "text/event-stream")
.build() .build()