转自http://coder520.com/
1、登录遇到的问题:
-
session一致性问题,如图有两个springboot应用,当用户请求第一次之后session存到左边的应用,当第二次请求之后轮询到右边,这时候右边没有session,这就导致session不一致。
解决方法:引入Nosql,redis,把session存到redis里。
-
移动端没有session问题,session是基于cookie,session是有状态的,而移动端往往是无状态的,没有创建session。
解决方法:session的根本是能够标识唯一用户,唯一会话的就是session。不要局限于tomcat。仿造session,在用户登录的时候自己生成个token,只要每个token都是唯一的就行。 -
最后把token存入redis,利用redis的key过期事件设置超时。
-
用户每次请求都带着token,服务端进行校验。
-
安全性问题,当token验证通过就可以开放api接口,那么这个token怎么保密
解决方法,当用户登录的时候,服务端创建token传给用户的时候,token传输要加密。
例如:摩拜用户传验证码和手机号到服务端,要把这些加密,如果没有加密被人截获,他就可以使用这些信息去获取后台的api。
选择的加密手段: -
之前的md5是数字签名,是不可逆的,当你传输过来加密过后的验证码和手机号,服务端并不能解密,因为我根本不知道你手机号码和验证码是什么(之前的登录验证,服务端先把密码加密之后存入到数据库,当你前端传输加密后的密码时,可以与早就加密后的密码比较),但是手机号码是用户输入的,我们不会把用户手机加密存到数据库的是存明文的,密码就存密文所以可以使用md5,也不会在服务端加密验证码。所以md5在这里不可用。
-
加密的种类:
-
对称加密,解密和加密的时候key一样,不管什么人拿到钥匙都能开同一个门。
客户端:明文+key(密钥) = 密文
服务端:key(密钥)+ 密文 = 明文
流程:用户是key加密后,把密文和key传输给服务端,然后我再使用传输过来的key进行解密。
缺点:key的传输不安全,别人拿到密文和key同样可以知道传输的信息。
优点:效率高 -
非对称加密,解密和加密的时候key不一样
客户端:明文+公玥 = 密文
服务端:密文 + 私玥 = 明文
优点:公玥可以随便公开,但是私玥不能公开
缺点:效率低
最优解:
结合对称加密和非对称加密,使用对称加密来加密我们的数据,然后使用非对称加密来加密key进行传输,服务端就可以使用非对称手段来解密这个key,接着使用对称的手段来解密传输的数据。因为数据量大使用对称,key量小使用非对称,也解决了key的安全传输。 -
上面说白了都是字符串
对称加密算法:AES,DES等等。
非对称加密算法:RSA,基本根据不能破解,破解成本太大。基于大因数分解。
我们选用aes和rsa。 -
我们使用http来传输,它是不安全的,各种传输的过程完全暴露的,任何一个路由器转发的节点都可以拿到数据,所以才需要这些加密来进行保密
当然也可以使用https,但是服务器需要证书,需要几百块。
2、实操
- 创建对称算法类AESUtil,其中两个方法,一个就是加密一个解密,加密传入明文和key,解密传入密文和key。这里需要注意的是:返回的数据要使用base64util去编码,而安卓同样使用base64util去解码,不要使用java里的baseencode,因为安卓没有对应的basedecode。避免编码不同加密解密失败,都是使用同一套编码。
测试
public static void main(String[] args) throws Exception {
String key = "123456789abcdefg";
String result = "神游建";
//加密
String enResult = encrypt(result, key);
System.out.println(enResult);
//解密
String deResult = decrypt(enResult, key);
System.out.println(deResult);
}

base64util的作用,虽然在本机能够加密显示,但是什么==*等这些字符在网络传输的时候会出问题,所以需要base64util编码一下,改成可以传输的字符。
- 接着创建非对称RSAUtil类,公钥放在移动端,私钥放在服务端。自己调研java类去生成公钥和私钥,
public static void main(String[] args) throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
keyPairGen.initialize(1024);
KeyPair keyPair = keyPairGen.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
System.out.println(Base64Util.encode(privateKey.getEncoded()));
System.out.println(Base64Util.encode(publicKey.getEncoded()));
}
然后把私钥保存在文件里,安全其他开发人员就看不到,公钥就在类里定义好,私钥就通过读取文件流获取。


读取私钥方法
/**
* 读取密钥字符串
* @throws Exception
*/
public static void convert() throws Exception {
byte[] data = null;
try {
InputStream is = RSAUtil.class.getResourceAsStream("/enc_pri");
int length = is.available();
data = new byte[length];
is.read(data);
} catch (Exception e) {
}
String dataStr = new String(data);
try {
PRIVATE_KEY = dataStr;
} catch (Exception e) {
}
if (PRIVATE_KEY == null) {
throw new Exception("Fail to retrieve key");
}
}
接着私钥解密和公钥加密的方法,然后在main函数里测试
byte[] enR = encryptByPublicKey("ljs".getBytes("utf-8"),PUBLIC_KEY);
System.out.println(enR.toString());
byte[] deR = decryptByPrivateKey(enR);
System.out.println(new String(deR, "utf-8"));
这里报空指针异常Exception in thread "main" java.lang.NullPointerException,原因是我们新加的私钥配置文件没加载,在pom里设置。注意加密算法不同,私钥也就不同,如果修改了需要重新生成。
<includes>
<include>*.yml</include>
<include>*.properties</include>
<include>*.xml</include>
<include>enc_pri</include>
</includes>
最后需要指定编码不然解密后还是乱码。

最后整个加密解密流程测试
public static void main(String[] args) throws Exception {
/**AES加密数据,客户端操作开始**/
String key = "123456789abcdefg"; //约定好的key
String result = "神游建"; //传输的数据
String enResult = encrypt(result, key); //加密
System.out.println(enResult);
/**RSA加密AES的密钥,客户端操作结束**/
byte[] enKey = RSAUtil.encryptByPublicKey(key.getBytes(), "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCEPB4Y7bd4ttV3phsm7VpR lmG0j19QUQWRG+MVCgw7f7ahvgwiXpwrqWP4hyZFxlFRUT4PlS11cKNut1Qm xjco1pYIxZUG6TfQj+a9rnUOGogdkyS76IpKi5/xal6MTmPqlfpE9SkBLvDc qLFX8FBo0+/ReoPrIPg3H4Saj99tOwIDAQAB");
System.out.println(new String(enKey, "utf-8"));
//需要再转码不然在http传输会出问题,因为上面输出乱码
String baseKey = Base64Util.encode(enKey);
/**服务端RSA解密AES的key**/
byte[] de = Base64Util.decode(baseKey);
byte[] deResultKey = RSAUtil.decryptByPrivateKey(de);
System.out.println(new String(deResultKey, "utf-8"));
String deResult = decrypt(enResult, key); //服务端解密数据
System.out.println(deResult);
}

同样可以看到如果没base64转码会出现乱码,在http传输是会出问题的。
总结:
安卓端传输数据,先在安卓端使用AES对称加密进行数据加密,加密的key是存在安卓端上的,但是key不能明文传输,这样别人通过网络获取key和加密的数据同样可以解密,但是又不能对称加密传输,这样又得再用一个key,无限循环。。所以使用非对称加密key,在安卓端非对称加密key之后和对称加密的数据一起传输。服务端就可以使用非对称的私钥解密key,接着使用key解密数据。
这样还是不够安全,因为rsa的公钥是相同的,也就是说不同客户端的公钥都是相同的,如果黑客反编译的安卓app,这样就可以获取公钥,这样就不安全,我们可以服务端随机生成rsa公钥,然后与安卓端的标识绑定起来。防止反编译可以在安卓端使用代码混淆。这样就解决了相同公钥与反编译的问题,但是没有一个完美的系统。。同样上面也可以使用https。
网友评论