【安卓】Android Socket与HTTPS校验

Android中使用HTTPS的场景比较频繁,所以对于HTTPS的证书应该如何校验呢?关于HTTPS的校验原理可以参考我之前写的一篇文章:《 HTTPS协议实现原理 》,相信看完后应该对HTTPS有一个比较大致的了解。而且对HTTP(s)请求的工具进行了封装,需要体会这种封装工具类的思路,也就是编码中常见的Listener机制。然后是Android中TCP、UDP通信的例子,主要是把Android设备作为Client端,如果对Java的Socket编程比较熟悉的话,这些都是特别简单的示例程序,非常容易看懂。

<!-- more -->

TCP/UDP 简单示例

下面的例子演示了Client向Server发送了一串小写英文,Server返回大写字符串的功能:

UDPServer.java:

public class UDPServer {

private static final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");

public static void main(String[] args) throws Exception {

DatagramSocket datagramSocket;

datagramSocket = new DatagramSocket(8090);

byte[] buf;

DatagramPacket packet;

while (true){

buf = new byte[1024];

packet = new DatagramPacket(buf, buf.length);

datagramSocket.receive(packet);

String content = new String(packet.getData());

InetAddress address = packet.getAddress();

System.out.println(format.format(new Date()) + "-" + address + "-" + content);

int port = packet.getPort();

String replyContent = content.toUpperCase();

byte[] sendData = replyContent.getBytes();

DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, address, port);

datagramSocket.send(sendPacket);

}

}

}

UDPClient.java:

public class UDPClient {

private static final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");

public static void main(String[] args) throws Exception {

System.out.println("请输入一句英文,服务器会返回其大写形式[exit退出]");

Scanner scanner = new Scanner(System.in);

InetAddress address = InetAddress.getLocalHost();

DatagramPacket packet;

DatagramSocket socket = new DatagramSocket();

while(true){

String line = scanner.nextLine();

if("exit".equals(line)) break;

byte[] bytes = line.getBytes();

packet = new DatagramPacket(bytes, bytes.length, address, 8090);

socket.send(packet);

byte[] recvBuf = new byte[1024];

DatagramPacket recvPacket = new DatagramPacket(recvBuf, recvBuf.length);

socket.receive(recvPacket);

System.out.println(format.format(new Date()) + "-" + address + "-" + new String(recvBuf));

}

socket.close();

}

}

TCPServer.java:

public class TCPServer {

static SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");

public static void main(String[] args) throws IOException {

ServerSocket serverSocket = new ServerSocket(9090);

while (true){

Socket socket = serverSocket.accept();

InetAddress address = socket.getInetAddress();

InputStream is = socket.getInputStream();

byte[] readBuf = new byte[1024];

try{

int len = is.read(readBuf);

String recv = new String(readBuf, 0, len);

System.out.println(format.format(new Date()) + "-" + address + "-" + recv);

OutputStream os = socket.getOutputStream();

os.write(recv.toUpperCase().getBytes());

} catch (SocketException e){

System.err.println("客户端未发送信息");

} finally {

socket.close();

}

}

}

}

TCPClient.java:

public class TCPClient {

private static final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");

public static void main(String[] args) throws Exception {

System.out.println("请输入一句英文,服务器会返回其大写形式[exit退出]");

Scanner scanner = new Scanner(System.in);

while(true){

Socket socket = new Socket("127.0.0.1", 9090);

String line = scanner.nextLine();

if("exit".equals(line)) break;

OutputStream os = socket.getOutputStream();

os.write(line.getBytes());

InputStream is = socket.getInputStream();

byte[] readBuf = new byte[1024];

String recv = new String(readBuf, 0, is.read(readBuf));

InetAddress address = socket.getInetAddress();

System.out.println(format.format(new Date()) + "-" + address + "-" + recv);

socket.close();

}

}

}

Client移植到Android

将两个Client移植到Android:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:app="http://schemas.android.com/apk/res-auto"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical"

android:padding="10dp"

tools:context=".MainActivity">

<EditText

android:hint="输入发送内容"

android:id="@+id/et_content"

android:layout_width="match_parent"

android:layout_height="wrap_content"/>

<LinearLayout

android:orientation="horizontal"

android:layout_width="match_parent"

android:layout_height="wrap_content">

<EditText

android:text="192.168.1.113:8090"

android:id="@+id/et_udp_server"

android:layout_width="0dp"

android:layout_weight="1"

android:layout_height="wrap_content">

</EditText>

<Button

android:text="UDP发送"

android:onClick="sendUdpMessage"

android:layout_weight="1"

android:layout_width="0dp"

android:layout_height="wrap_content"/>

</LinearLayout>

<LinearLayout

android:orientation="horizontal"

android:layout_width="match_parent"

android:layout_height="wrap_content">

<EditText

android:text="192.168.1.113:9090"

android:id="@+id/et_tcp_server"

android:layout_width="0dp"

android:layout_weight="1"

android:layout_height="wrap_content">

</EditText>

<Button

android:text="TCP发送"

android:onClick="sendTcpMessage"

android:layout_weight="1"

android:layout_width="0dp"

android:layout_height="wrap_content"/>

</LinearLayout>

<TextView

android:id="@+id/tv_show"

android:text="收到回复:"

android:layout_width="match_parent"

android:layout_height="wrap_content"/>

</LinearLayout>

MainActivity.java:

public class MainActivity extends AppCompatActivity {

private static final String TAG = "MainActivity";

private static final SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss", Locale.CHINA);

private EditText etInput;

private TextView textView;

private EditText udpServerET;

private EditText tcpServerET;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

etInput = findViewById(R.id.et_content);

textView = findViewById(R.id.tv_show);

udpServerET = findViewById(R.id.et_udp_server);

tcpServerET = findViewById(R.id.et_tcp_server);

}

public void sendTcpMessage(View view) {

String[] tcpInfo = tcpServerET.getText().toString().split(":");

String inputContent = etInput.getText().toString();

new Thread(()->{

try (Socket socket = new Socket(tcpInfo[0], Integer.parseInt(tcpInfo[1]))){

OutputStream os = socket.getOutputStream();

os.write(inputContent.getBytes());

InputStream is = socket.getInputStream();

byte[] readBuf = new byte[1024];

String recv = new String(readBuf, 0, is.read(readBuf));

InetAddress address = socket.getInetAddress();

String ret = String.format("%s-%s-%s", df.format(new Date()), address, recv);

runOnUiThread(()-> textView.setText(ret));

}catch (IOException e){

Log.e(TAG, "sendTcpMessage: Error!");

}

}).start();

}

public void sendUdpMessage(View view) {

String[] udpInfo = udpServerET.getText().toString().split(":");

String inputContent = etInput.getText().toString();

new Thread(()->{

try {

DatagramSocket socket = new DatagramSocket();

byte[] bytes = inputContent.getBytes();

InetAddress address = InetAddress.getByName(udpInfo[0]);

int serverPort = Integer.parseInt(udpInfo[1]);

DatagramPacket packet = new DatagramPacket(bytes, bytes.length, address, serverPort);

socket.send(packet);

byte[] recvBuf = new byte[1024];

DatagramPacket recvPacket = new DatagramPacket(recvBuf, recvBuf.length);

socket.receive(recvPacket);

String ret = String.format("%s-%s-%s", df.format(new Date()), address, new String(recvBuf));

runOnUiThread(()-> textView.setText(ret));

}catch (IOException e){

Log.e(TAG, "sendUdpMessage: Error!");

}

}).start();

}

}

AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET"/>

【安卓】Android Socket与HTTPS校验

注意点:1、网络访问权限 2、子线程代码中使用runOnUiThread()方法可更新UI

Android访问HTTPS

对于一个普通的HTTP请求,我们可以使用如下方式来发起请求,下面是一个简易的Http请求工具类:

public class HttpUtils {

private static Handler mUIHandler = new Handler(Looper.getMainLooper());

interface HttpListener {

void onSuccess(String content);

void onFail(Exception e);

}

public static void doGet(String urlStr, HttpListener listener) {

new Thread(() -> {

Looper.prepare();

try {

URL url = new URL(urlStr);

HttpURLConnection conn = (HttpURLConnection) url.openConnection();

conn.setRequestMethod("GET");

conn.setConnectTimeout(5000);

conn.setReadTimeout(5000);

conn.connect();

try (InputStream is = conn.getInputStream();

InputStreamReader reader = new InputStreamReader(is)

) {

char[] buf = new char[4096];

int len;

StringBuilder sb = new StringBuilder();

while ((len = reader.read(buf)) != -1) {

sb.append(new String(buf, 0, len));

}

mUIHandler.post(() -> listener.onSuccess(sb.toString()));

} catch (IOException e) {

e.printStackTrace();

listener.onFail(e);

}

}catch (IOException e){

e.printStackTrace();

listener.onFail(e);

}

}).start();

}

}

1、不校验证书(不推荐)

MyX509TrustManager.java,MyX509TrustManager实现不做任何事情:

...

import java.security.cert.CertificateException;

import java.security.cert.X509Certificate;

import javax.net.ssl.X509TrustManager;

public class MyX509TrustManager implements X509TrustManager {

@Override

public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

// TODO...

}

@Override

public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

// TODO...

}

@Override

public X509Certificate[] getAcceptedIssuers() {

return new X509Certificate[0];

}

}

HttpsUtils.java

...

public class HttpsUtils {

private static Handler mUIHandler = new Handler(Looper.getMainLooper());

interface HttpListener {

void onSuccess(String content);

void onFail(Exception e);

}

public static void doGet(Context context, String urlStr, HttpListener listener) {

new Thread(() -> {

Looper.prepare();

try {

URL url = new URL(urlStr);

HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();

SSLContext sslContext = SSLContext.getInstance("TLS");

// 放入自定义的MyX509TrustManager对象即可

TrustManager[] trustManagers = {new MyX509TrustManager()};

sslContext.init(null, trustManagers, new SecureRandom());

conn.setSSLSocketFactory(sslContext.getSocketFactory());

conn.setRequestMethod("GET");

conn.setConnectTimeout(5000);

conn.setReadTimeout(5000);

conn.connect();

try (InputStream is = conn.getInputStream();

InputStreamReader reader = new InputStreamReader(is)

) {

char[] buf = new char[4096];

int len;

StringBuilder sb = new StringBuilder();

while ((len = reader.read(buf)) != -1) {

sb.append(new String(buf, 0, len));

}

mUIHandler.post(() -> listener.onSuccess(sb.toString()));

} catch (IOException e) {

e.printStackTrace();

listener.onFail(e);

}

}catch (Exception e){

e.printStackTrace();

listener.onFail(e);

}

}).start();

}

}

2、校验证书(推荐)

拿我自己的博客站点来说,想要获得证书只需要在浏览器下载对应的证书即可(选择DER编码二进制和Base64编码均可),保存了一个名为srca.cer的文件到桌面:

【安卓】Android Socket与HTTPS校验

将这份证书文件复制到项目的src/main/assets/目录下,没有assets就新建,所以完整路径为src/main/assets/srca.cer。

接下来需要实现MyX509TrustManager.java中的方法:

public class MyX509TrustManager implements X509TrustManager {

private static final String TAG = "MyX509TrustManager";

// 证书对象

private X509Certificate serverCert;

public MyX509TrustManager(X509Certificate serverCert) {

this.serverCert = serverCert;

}

@Override

public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

}

@Override

public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

// 遍历证书

for (X509Certificate certificate: chain){

// 校验合法性与是否过期

certificate.checkValidity();

try {

// 校验公钥

PublicKey publicKey = serverCert.getPublicKey();

certificate.verify(publicKey);

} catch (Exception e) {

throw new CertificateException(e);

}

}

}

@Override

public X509Certificate[] getAcceptedIssuers() {

return new X509Certificate[0];

}

}

同时,将使用keyStore这个API来获取TrustManager数组,HttpsUtils.java如下:

public class Https2Utils {

private static Handler mUIHandler = new Handler(Looper.getMainLooper());

interface HttpListener {

void onSuccess(String content);

void onFail(Exception e);

}

public static void doGet(Context context, String urlStr, HttpListener listener) {

new Thread(() -> {

Looper.prepare();

try {

URL url = new URL(urlStr);

HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();

SSLContext sslContext = SSLContext.getInstance("TLS");

X509Certificate serverCert = getCert(context);

String defaultType = KeyStore.getDefaultType();

KeyStore keyStore = KeyStore.getInstance(defaultType);

keyStore.load(null);

// 别名、证书

keyStore.setCertificateEntry("srca", serverCert);

String algorithm = TrustManagerFactory.getDefaultAlgorithm();

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(algorithm);

trustManagerFactory.init(keyStore);

TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

sslContext.init(null, trustManagers, new SecureRandom());

conn.setSSLSocketFactory(sslContext.getSocketFactory());

// 校验域名是否合法

conn.setHostnameVerifier((hostname, session) -> {

HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier();

return verifier.verify("zouchanglin.cn", session);

});

conn.setRequestMethod("GET");

conn.setConnectTimeout(5000);

conn.setReadTimeout(5000);

conn.connect();

try (InputStream is = conn.getInputStream();

InputStreamReader reader = new InputStreamReader(is)

) {

char[] buf = new char[4096];

int len;

StringBuilder sb = new StringBuilder();

while ((len = reader.read(buf)) != -1) {

sb.append(new String(buf, 0, len));

}

mUIHandler.post(() -> listener.onSuccess(sb.toString()));

} catch (IOException e) {

e.printStackTrace();

listener.onFail(e);

}

}catch (Exception e){

e.printStackTrace();

listener.onFail(e);

}

}).start();

}

private static X509Certificate getCert(Context context) {

try {

// src/main/assets/srca.cer

InputStream inputStream = context.getAssets().open("srca.cer");

CertificateFactory factory = CertificateFactory.getInstance("X.509");

return (X509Certificate) factory.generateCertificate(inputStream);

} catch (IOException | CertificateException e) {

e.printStackTrace();

}

return null;

}

}

在MainActivity中使用也很简单:

public class MainActivity extends AppCompatActivity {

private EditText etUrl;

private TextView tvShow;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

etUrl = findViewById(R.id.et_url);

tvShow = findViewById(R.id.tv_show);

}

public void loadContent(View view) {

String url = etUrl.getText().toString();

Https2Utils.doGet(this, url, new Https2Utils.HttpListener() {

@Override

public void onSuccess(String content) {

tvShow.setText(content);

}

@Override

public void onFail(Exception e) {

Toast.makeText(MainActivity.this, "Failed!", Toast.LENGTH_SHORT).show();

}

});

}

}

【安卓】Android Socket与HTTPS校验

以上是 【安卓】Android Socket与HTTPS校验 的全部内容, 来源链接: utcz.com/a/101147.html

回到顶部