feat: 增加密码连接
This commit is contained in:
@@ -32,6 +32,7 @@ import com.ttstd.controlled.base.BaseMvvmActivity;
|
||||
import com.ttstd.controlled.databinding.ActivityMainBinding;
|
||||
import com.ttstd.controlled.service.ScreenCaptureService;
|
||||
import com.ttstd.controlled.accessibility.KeyboardAccessibilityService;
|
||||
import com.ttstd.controlled.utils.AuthSettings;
|
||||
import com.ttstd.controlled.utils.DeviceUtils;
|
||||
import com.ttstd.controlled.utils.SignatureUtils;
|
||||
import com.ttstd.controlled.webrtc.WebRtcClient;
|
||||
@@ -88,6 +89,31 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
|
||||
binding.btnStart.setOnClickListener(v -> startScreenSharing());
|
||||
binding.btnStop.setOnClickListener(v -> stopScreenSharing());
|
||||
|
||||
// 安全验证:显示动态验证码,提供重新生成与固定密码设置
|
||||
binding.tvDynamicCode.setText(AuthSettings.getDynamicCode(this));
|
||||
binding.btnRegenerateCode.setOnClickListener(v -> {
|
||||
String newCode = AuthSettings.generateDynamicCode();
|
||||
AuthSettings.setDynamicCode(this, newCode);
|
||||
binding.tvDynamicCode.setText(newCode);
|
||||
binding.tvAuthHint.setText("动态验证码已重新生成");
|
||||
});
|
||||
binding.btnSavePassword.setOnClickListener(v -> {
|
||||
String p1 = binding.etFixedPassword.getText().toString();
|
||||
String p2 = binding.etConfirmPassword.getText().toString();
|
||||
if (p1.isEmpty()) {
|
||||
binding.tvAuthHint.setText("固定密码不能为空");
|
||||
return;
|
||||
}
|
||||
if (!p1.equals(p2)) {
|
||||
binding.tvAuthHint.setText("两次输入的密码不一致");
|
||||
return;
|
||||
}
|
||||
AuthSettings.setFixedPassword(this, p1);
|
||||
binding.etFixedPassword.setText("");
|
||||
binding.etConfirmPassword.setText("");
|
||||
binding.tvAuthHint.setText("固定密码已保存");
|
||||
});
|
||||
|
||||
updateUI(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import com.ttstd.controlled.input.ShellInputUtils;
|
||||
import com.ttstd.controlled.input.SystemInputUtils;
|
||||
import com.ttstd.controlled.utils.SignatureUtils;
|
||||
import com.ttstd.controlled.signaling.SignalMessage;
|
||||
import com.ttstd.controlled.utils.AuthSettings;
|
||||
import com.ttstd.controlled.signaling.WebSocketClient;
|
||||
import com.ttstd.controlled.webrtc.WebRtcClient;
|
||||
|
||||
@@ -457,18 +458,78 @@ public class ScreenCaptureService extends Service {
|
||||
}
|
||||
|
||||
String controllerId = message.getFromDeviceId();
|
||||
// 暂存请求,弹出确认对话框,交由用户决定是否接受。
|
||||
// 暂存请求信息
|
||||
pendingControllerId = controllerId;
|
||||
pendingControllerName = (controllerName != null && !controllerName.isEmpty())
|
||||
? controllerName : controllerId;
|
||||
pendingOfferSdp = offerSdp;
|
||||
|
||||
// 鉴权:动态验证码(CODE) / 固定密码(PASSWORD)。
|
||||
// 服务器仅做转发,真实校验在此完成(密钥不离开被控端)。
|
||||
String authType = message.getAuthType();
|
||||
String authValue = message.getAuthValue();
|
||||
if (authType != null && !authType.trim().isEmpty()) {
|
||||
String failReason = verifyAuth(authType.trim(), authValue);
|
||||
if (failReason == null) {
|
||||
// 校验通过:自动接受,直接建立连接(无需手动确认)
|
||||
Log.i(TAG, "Auth passed (" + authType + "), auto-accepting " + controllerId);
|
||||
mainHandler.post(this::acceptConnection);
|
||||
} else {
|
||||
Log.w(TAG, "Auth failed (" + authType + "): " + failReason + " from " + controllerId);
|
||||
sendAuthRejected(failReason);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 未携带鉴权信息:沿用原有手动确认弹窗
|
||||
Intent dialogIntent = new Intent(this, ConnectionRequestActivity.class);
|
||||
dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
dialogIntent.putExtra(ConnectionRequestActivity.EXTRA_CONTROLLER_ID, controllerId);
|
||||
startActivity(dialogIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验控制端发来的鉴权信息。返回 null 表示通过,否则返回失败原因。
|
||||
* CODE: 与被控端当前动态验证码比对;
|
||||
* PASSWORD: 与被控端固定密码比对。
|
||||
*/
|
||||
private String verifyAuth(String authType, String authValue) {
|
||||
if (authValue == null) authValue = "";
|
||||
if ("CODE".equalsIgnoreCase(authType)) {
|
||||
String expected = AuthSettings.getDynamicCode(this);
|
||||
if (expected == null || expected.isEmpty()) {
|
||||
return "被控端未生成动态验证码";
|
||||
}
|
||||
return expected.equals(authValue) ? null : "动态验证码错误";
|
||||
} else if ("PASSWORD".equalsIgnoreCase(authType)) {
|
||||
String expected = AuthSettings.getFixedPassword(this);
|
||||
if (expected == null || expected.isEmpty()) {
|
||||
return "被控端未设置固定密码";
|
||||
}
|
||||
return expected.equals(authValue) ? null : "固定密码错误";
|
||||
}
|
||||
return "未知的鉴权类型: " + authType;
|
||||
}
|
||||
|
||||
/** 鉴权失败时,向控制端回送带原因的拒绝消息并清理待处理状态。 */
|
||||
private void sendAuthRejected(String reason) {
|
||||
if (pendingControllerId == null || wsClient == null) {
|
||||
return;
|
||||
}
|
||||
String controllerId = pendingControllerId;
|
||||
clearPendingRequest();
|
||||
SignalMessage msg = new SignalMessage();
|
||||
msg.setType("CONNECTION_REJECTED");
|
||||
msg.setFromDeviceId(deviceId);
|
||||
msg.setToDeviceId(controllerId);
|
||||
msg.setDeviceType("CONTROLLED");
|
||||
JsonObject payload = new JsonObject();
|
||||
payload.addProperty("reason", reason);
|
||||
msg.setPayload(payload.toString());
|
||||
wsClient.sendMessage(msg);
|
||||
Log.i(TAG, "Sent CONNECTION_REJECTED(" + reason + ") to " + controllerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户在弹窗中点击“接受”:创建 Answer 完成连接。
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,8 @@ public class SignalMessage {
|
||||
private String toDeviceId;
|
||||
private String deviceType;
|
||||
private String payload;
|
||||
private String authType;
|
||||
private String authValue;
|
||||
|
||||
public SignalMessage() {}
|
||||
|
||||
@@ -31,4 +33,10 @@ public class SignalMessage {
|
||||
|
||||
public String getPayload() { return payload; }
|
||||
public void setPayload(String payload) { this.payload = payload; }
|
||||
|
||||
public String getAuthType() { return authType; }
|
||||
public void setAuthType(String authType) { this.authType = authType; }
|
||||
|
||||
public String getAuthValue() { return authValue; }
|
||||
public void setAuthValue(String authValue) { this.authValue = authValue; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.ttstd.controlled.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* 被控端安全验证配置:动态验证码与固定密码。
|
||||
* 密钥仅保存在被控端本机,服务器只做转发,不接触任何密钥。
|
||||
*/
|
||||
public class AuthSettings {
|
||||
|
||||
private static final String PREF_NAME = "controlled_auth_prefs";
|
||||
private static final String KEY_DYNAMIC_CODE = "dynamic_code";
|
||||
private static final String KEY_FIXED_PASSWORD = "fixed_password";
|
||||
|
||||
private static final String CODE_CHARS =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
private static final int CODE_LENGTH = 6;
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
/** 生成一个 6 位、由大小写字母与数字组成的随机验证码。 */
|
||||
public static String generateDynamicCode() {
|
||||
StringBuilder sb = new StringBuilder(CODE_LENGTH);
|
||||
for (int i = 0; i < CODE_LENGTH; i++) {
|
||||
sb.append(CODE_CHARS.charAt(RANDOM.nextInt(CODE_CHARS.length())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前动态验证码;若不存在则随机生成并持久化。
|
||||
*/
|
||||
public static String getDynamicCode(Context context) {
|
||||
SharedPreferences sp = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
|
||||
String code = sp.getString(KEY_DYNAMIC_CODE, null);
|
||||
if (code == null || code.isEmpty()) {
|
||||
code = generateDynamicCode();
|
||||
sp.edit().putString(KEY_DYNAMIC_CODE, code).apply();
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
public static void setDynamicCode(Context context, String code) {
|
||||
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit().putString(KEY_DYNAMIC_CODE, code == null ? "" : code).apply();
|
||||
}
|
||||
|
||||
/** 获取固定密码(未设置时为空字符串)。 */
|
||||
public static String getFixedPassword(Context context) {
|
||||
return context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.getString(KEY_FIXED_PASSWORD, "");
|
||||
}
|
||||
|
||||
public static void setFixedPassword(Context context, String password) {
|
||||
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit().putString(KEY_FIXED_PASSWORD, password == null ? "" : password).apply();
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,85 @@
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- 安全验证:动态验证码 + 固定密码 -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:text="安全验证"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="动态验证码(连接时由控制端输入):"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_dynamic_code"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="------"
|
||||
android:textSize="22sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="monospace"
|
||||
android:letterSpacing="0.1" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_regenerate_code"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="重新生成" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="固定密码(手动设置并确认):"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_fixed_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:hint="输入固定密码"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_confirm_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:hint="再次输入固定密码"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_save_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="保存固定密码" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_auth_hint"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textSize="13sp"
|
||||
android:textColor="#888888" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_start"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
Reference in New Issue
Block a user