上图简要说明了用户认证的过程:
上图中实线部分即为本文档描述的接口,SDK与直播云之间的登录交互由SDK实现,应用无需关心。 认证接口由应用开发者实现,直播云向认证接口发出HTTP请求, 注意: 回调请求是HTTP GET, 实现的应用接口应该接收并处理HTTP GET请求。
客户可以登录到直播云控制台,自服务修改认证接口地址。如果客户不想自己实现认证接口,则可以使用直播云的用户管理系统,在这种情况下同样可以登录直播云控制台进行用户的管理。
认证有以下两种模式:
模式 | 认证模式字段值 | 说明 |
---|---|---|
明文认证 | 2 | 传输明文密码 |
MD5挑战模式 | 3 | 密码经一定运算后的HASH值 |
字段名 | 说明 | 备注 |
---|---|---|
username | 用户名 | |
password | 明文密码 | |
service_code | 服务码 | |
authen_mode | 认证模式 | 固定为2 |
云平台默认的认证方式是挑战认证方式,如果需要明文认证,则需要向我们申请,同时在客户端上调用相应的API,强制成明文认证。
Android SDK强制明文认证的的API为
Manager.forceAuthenMode(mode)
//mode = Manager.HANDSHAKE_CLEAR_PASSWORD 强制明文认证
//mode = Manager.HANDSHAKE_MD5_CHALLENGE 挑战认证模式
iOS SDK强制明文认证的API为
(void)setAuthMode:(AuthMode)authMode
CHALLENGE_PASSWORD-加密,CLEAR_PASSWORD-明文
字段名 | 说明 | 备注 |
---|---|---|
username | 用户名 | |
service_code | 服务码 | |
challenge | 挑战字串 | |
response | 挑战应答 | |
authen_mode | 认证模式 | 固定为3 |
对于明文认证很简单,应用接口服务器在收到通知后取出用户名和密码字段,核对即可。这里不多说了。
下面详细介绍一下挑战认证的算法,
挑战认证的目的是传输时不传输原始的密码串,其核心概念是请求认证方把密码
password
和种子码chanllenge
按一定算法进行hash运算,然后把hash的结果response
和种子码一起传给认证方,认证方用同样的算法把自己这里保存的密码做同样的运算,把运算结果和认证方传来的认证结果比较,如果一致则认证通过,否则认证失败。
BytesToHexString( Md5(Md5(password) + HexStringToBytes(challenge) ) )
下面是三个函数的定义: md5是做hash运算的函数 byte [] Md5(byte [] param)
注意传入的参数是一个byte数组,输出也是byte数组,不是字符串
StringToBytes
是指将以16进制表示的字串解码成byte数组,输入是String,输出是byte数组。
byte []HexStringToBytes(String param)
例如:输入字串 param = "1a2b3c4d5e6f7080"
则输出应该是 [0x1a, 0x2b, 0x3c, 0x4d, 0x5e, 0x6f, 0x70, 0x80]
的一个数组.
BytesToHexString与StringToBytes
相反,是将一个数组用十六进制字符串来表示。
String BytesToHexString(byte [] param)
例如输入para是m = [0x1a, 0x2b, 0x3c, 0x4d, 0x5e, 0x6f, 0x70, 0x80]
则返回是字符串: "1a2b3c4d5e6f7080"
以下表示两个byte数组合并: Md5(password) + HexStringToBytes(challenge)
例如:
Md5(password)
返回的 [0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7]
,
HexStringToBytes
返回的是[0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf]
则合并后为:[0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf]
下面是一个真实的挑战认证请求,其中query parameter如下:
username=glass1&service_code=DEVEL&challenge=4d0606d422bed2376f2c22ba268a1cf2&response=99c823c2973e6418175e7a8ced39b8c0&authen_mode=3
假设应用数据库中保存的密码是123456, 则验证过程如下:
md5(byte array of '123456') = [0xe1, 0x0a, 0xdc, 0x39, 0x49, 0xba, 0x59, 0xab, 0xbe, 0x56, 0xe0, 0x57, 0xf2, 0x0f, 0x88, 0x3e]
HexStringToBytes(challenge) = [0x4d, 0x06, 0x06, 0xd4, 0x22, 0xbe, 0xd2, 0x37, 0x6f, 0x2c, 0x22, 0xba, 0x26, 0x8a, 0x1c, 0xf2]
md5(byte array of '123456') + HexStringToBytes(challenge) = [0xe1, 0x0a, 0xdc, 0x39, 0x49, 0xba, 0x59, 0xab, 0xbe, 0x56, 0xe0, 0x57, 0xf2, 0x0f, 0x88, 0x3e, 0x4d, 0x06, 0x06, 0xd4, 0x22, 0xbe, 0xd2, 0x37, 0x6f, 0x2c, 0x22, 0xba, 0x26, 0x8a, 0x1c, 0xf2]
md5(md5(byte array of '123456') + HexStringToBytes(challenge)) = [0x99, 0xc8, 0x23, 0xc2, 0x97, 0x3e, 0x64, 0x18, 0x17, 0x5e, 0x7a, 0x8c, 0xed, 0x39, 0xb8, 0xc0]
BytesToHexString(Md5(Md5(password)+HexStringToBytes(challenge))) = "99c823c2973e6418175e7a8ced39b8c0"
将计算出来的结果与response字段的值比较,发现两者一致,则认为用户密码合法,认证通过。
有的时候应用出于安全的考虑数据库中保存的已经是经过md5运算密码的hash值,则md5(password)
这一步跳过,直接将保存的hash过的字串做计算:
上面的公式就改为了:
BytesToHexString( Md5(HexStringToBytes(hashed_password) + HexStringToBytes(challenge) ) )
function calculate_challenge_response(challenge, password) {
var md5_signer = crypto.createHash('md5');
md5_signer.update(password);
var hashed_pass = md5_signer.digest();
md5_signer = crypto.createHash('md5');
md5_signer.update(hashed_pass);
md5_signer.update(new Buffer(challenge, 'hex'));
return md5_signer.digest('hex');
}
返回值即为计算出的response字串。
如果a和b都是数组 md5_signer.update(a) md5_signer.update(b) 的结果和 md5_signer.update(a.concat(b)) 的结果是一样的。
/*
* 将两个ASCII字符合成一个字节;
* 如:"EF"--> 0xEF
* @param src0 byte
* @param src1 byte
* @return byte
*/
public static byte uniteBytes(byte src0, byte src1) {
byte _b0 = Byte.decode("0x" + new String(new byte[]{src0})).byteValue();
_b0 = (byte)(_b0 << 4);
byte _b1 = Byte.decode("0x" + new String(new byte[]{src1})).byteValue();
byte ret = (byte)(_b0 ^ _b1);
return ret;
}
/**
* 将指定字符串src,以每两个字符分割转换为16进制形式
* 如:"2B44EFD9" --> byte[]{0x2B, 0x44, 0xEF, 0xD9}
* @param src String
* @return byte[]
*/
public static byte[] HexString2Bytes(String src){
byte[] ret = new byte[src.length()/2];
byte[] tmp = src.getBytes();
for(int i=0; i<src.length()/2; i++){
ret[i] = uniteBytes(tmp[i*2], tmp[i*2+1]);
}
return ret;
}
byte[] newpwd = Md5Util.HexString2Bytes(password)
byte[] newchallenge = Md5Util.HexString2Bytes(challenge)
//将密码和挑战串转成16进制表示的字节数据
byte[] result =new byte[newpwd.length+newchallenge.length]
System.arraycopy(newpwd, 0, result, 0, newpwd.length);
System.arraycopy(newchallenge, 0, result, newpwd.length, newchallenge.length)
//合并数据
String response = Md5Util.byte2MD5(result)
//md5加密计算response
byte[] bytepwd = HexStringToBytes(md5(pwd));
byte[] bytechallenge = HexStringToBytes(challenge);
byte[] newbyte = copybyte(bytepwd, bytechallenge);
string response =MD5(newbyte);
//16进制字符串转换为字节数组
public static byte[] HexStringToBytes(string mHex)
{
{
mHex = mHex.Replace(" ", "");
if (mHex.Length % 2 != 0) { mHex = ""; };
byte[] vBytes = new byte[mHex.Length / 2];
string TempStr = "";
for (int i = 0; i < mHex.Length; i += 2)
{
TempStr = mHex.Substring(i, 2);
vBytes[i / 2] = Convert.ToByte(TempStr, 16);
}
return vBytes;
}
}
$cal_response = md5(hex2bin( md5($password) . $challenge))
其中$password为密码字串, $challenge是回调请求的challenge字串。
相关参考:
Java中bytes []
和 Hex String
互相转换的工具类参见: "Hex":http://commons.apache.org/proper/commons-codec/apidocs/org/apache/commons/codec/binary/Hex.html
其中上述工式中提到的:
BytesToHexString
相当于 String encodeHexString(byte[] data)
HexStringToBytes
相当于:static byte[] decodeHex(char[] data)
应用认证接口收到云平台的认证请求后,应该返回json字串来表示认证的结果
{"ret": 0}
返回参数释义:
output_formats 用户定制化的输出描述字串,这里可以返回给当前认证用户定制化的输出描述,通过此字段,则可以实现同一服务码下不同用户输出不同格式,或不同地址的业务功能。比如某个应用需要通过直播云向其他平台推出rtmp流,并且需要每个用户推到不同的地址,则可以通过此字段来定义每个用户的直播输出行为。
这里举个例子,某个应用需要将用户1的直播流推送到 rtmp://xyz.the3rd.com:1935/user1
这个rtmp地址,而用户2要推到 rtmp://the3rd.com:1935/user2
, 则用户user1认证成功后返回的output_formats输出格式串为
<output tag="rtmp_push">
<extension>rtmp</extension>
<format>flv</format>
<codec-v>h264</codec-v>
<codec-a>aac</codec-a>
<try-copy-video/>
<output-url>xyz.the3rd.com:1935/user1</output-url>
</output>
而用户2认证成功后返回的output_formats可以为
<output tag="rtmp_push">
<extension>rtmp</extension>
<format>flv</format>
<codec-v>h264</codec-v>
<codec-a>aac</codec-a>
<try-copy-video/>
<output-url>xyz.the3rd.com:1935/user2</output-url>
</output>
则当用户 user1 发起一个直播,直播云给他分配的task_id为 ae12f87a2时,其直播流实际被推到了这样一个地址 rtmp://xyz.the3rd.com:1935/user1/ae12f87a2
。通过此种方法,可以针对不同用户输出不同格式、不同路径、不同的向外推流地址等。