java同步discuz账号
程序员文章站
2022-07-14 21:23:06
...
网站整合discuz账号,实现登录最起码有两个方向。1.相互不干涉,登录的时候,各自登录成功;2.就是大家共用一个登录标记,例如共用cookie实现。
至于第一种有很多实现方式。例如登录主网站W(web)的时候,隐藏登录D(discuz)论坛;还有就是异步登录,利用js登录。例如
http://code.google.com/p/discuz-ucenter-api-for-java/(java版UC API官网)就是利用uc的api生成一段登录的js脚本,调用登录脚本实现异步登录实现,这里的亮点就是利用api实现,因为现在流行组装性的,互不干涉的实现方式。如果想实现的比较漂亮,可采用这种方式。只是uc的实现略微麻烦,总的就是先实现通信,就是设置密钥;其次,才能调用api,但是这个api是针对uc的,不是直接针对D的,uc的api负责和D再同步,W--->U---->D,每次都是这样来回通信,可实现登陆,注册,修改信息等多个功能。具体的可参考官网。
针对我们的公司网站情况,还是不要采用这种过于麻烦的做法。我们只是想简单的实现同步登陆,同步注册而已,另外我们也可直接访问数据库,又不是第三方用户的,没必要搞api,因此,想着实现cookie共享就可了。为啥实现cookie共享的,这就由D的账号登录机制决定的。
D的账号登录机制,基本原理就是验证cookie的auth,利用加密算法把{password}/t{uid}加密进去,每次请求处理的时候,根据对应的解密算法解析auth,判断uid是否存在,就可判断是否登录。通过分析可知,所有的关键就是要掌握算法,只有搞定算法一切就ok。
接下来就要着重分析算法的实现啦。参考java版UC API官网可知,基本的算法,uc_authcode方法是java版实现/source/function/function_core.php里的authcode方法,MD5也类似一一对应的,只是java版里多了一步urlencode的GBK的编码转换而已。到这里似乎好像一切都很明朗,很简单化了。不过实际调用的时候又发现,所有的关键问题是key,key的生成,查看/discuz/upload/source/class/discuz/discuz_application.php的_init_input方法可知,authkey=md5(配置文件authkey和cookie的saltkey合并),为何是在这个方法,注意$discuz->init()这个方法,所有的基础操作都是由discuz_application类实现的,类似于java里的servlet,基础控制类。因此需要配置获取D论坛的config/config_global.php里的$_config[ 'security' ][' authkey' ]值,还有随机生成8个字母的盐值。到这里算法的key已生成。
public static void setBbsUserCookie(HttpServletResponse response, BbsUser user) { String saltkey = WebImpUtils.getRandomString(8); String cookieInfo = MessageFormat.format("{0}\t{1}", user.getPassword(), String.valueOf(user.getUid())); String auth = BBsUtils.urlencode(BBsUtils.uc_authcode(cookieInfo, "ENCODE", BBsUtils.md5(BBS_AUTH_KEY + saltkey))); WebImpUtils.setUserCookie(response, COOKIE_PRE + "auth", auth, COOKIE_DOMAIN); WebImpUtils.setUserCookie(response, COOKIE_PRE + "saltkey", saltkey, COOKIE_DOMAIN); }
其次,就是cookie的问题啦,cookie有限制,二级域名可直接在一级域名写cookie,当然也可读,但是反过来,一级域名就不可在二级域名下写cookie,也就是要实现cookie共享,只有在一级域名下,配置一下论坛的$_config[ 'cookie'][ 'cookiedomain' ] = '.xxx.com' ;,还有cookie的名字是否有前缀,$_config[ 'cookie'][ 'cookiepre' ]。具体请参考/config/config_global.php。
附件是java版实现cookie共享登录实现。直接调用setBbsUserCookie方法就可了,至于注册用户,直接截获论坛的注册的时候插入数据库的sql,插入数据生成的。
package com.ccc.cn.work.app; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.MessageFormat; import javax.servlet.http.HttpServletResponse; import com.ccc.cn.work.core.utils.WebImpUtils; import com.ccc.cn.work.service.bbs.Base64; import com.ccc.cn.work.service.bbs.BbsUser; /** * ************************************************ * @file: BBsUtils.java * @Copyright: 2012 Yiwu China Commodity City Information Technology Co., Ltd. * All right reserved. ************************************************ * @package: com.ccc.cn.work.service.bbs * @class: BBsUtils * @description: * * @author: bukebuhao * @since: 2012-7-27-上午09:25:27 * @history: * */ public class BBsUtils { /** * config/config_global.php */ private static String BBS_AUTH_KEY = ""; //$_config[ 'security' ][' authkey' ] private static String COOKIE_PRE = ""; //$_config[ 'cookie'][ 'cookiepre' ] private static String COOKIE_DOMAIN = ".xxx.com";//一级域名 private static String COOKIE_PATH = "/"; static { COOKIE_PRE = COOKIE_PRE + BBsUtils.md5(COOKIE_PATH + "|" + COOKIE_DOMAIN).substring(0, 4) + "_"; } public static String urlencode(String value) { try { return URLEncoder.encode(value, "GBK"); } catch (UnsupportedEncodingException e) { return e.getMessage(); } } public static String md5(String input) { MessageDigest md; try { md = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); return null; } return byte2hex(md.digest(input.getBytes())); } public static String md5(long input) { return md5(String.valueOf(input)); } public static String base64_decode(String input) { try { return new String(Base64.decode(input.toCharArray()), "iso-8859-1"); } catch (Exception e) { return e.getMessage(); } } public static String base64_encode(String input) { try { return new String(Base64.encode(input.getBytes("iso-8859-1"))); } catch (Exception e) { return e.getMessage(); } } public static String byte2hex(byte[] b) { StringBuffer hs = new StringBuffer(); String stmp = ""; for (int n = 0; n < b.length; n++) { stmp = (java.lang.Integer.toHexString(b[n] & 0XFF)); if (stmp.length() == 1) hs.append("0").append(stmp); else hs.append(stmp); } return hs.toString(); } public static String substr(String input, int begin, int length) { return input.substring(begin, begin + length); } public static String substr(String input, int begin) { if (begin > 0) { return input.substring(begin); } else { return input.substring(input.length() + begin); } } public static long microtime() { return System.currentTimeMillis(); } public static long time() { return System.currentTimeMillis() / 1000; } public static String sprintf(String format, long input) { String temp = "0000000000" + input; return temp.substring(temp.length() - 10); } /** * 字符串加密以及解密函数 * * @param string * string 原文或者密文 * @param string * operation 操作(ENCODE | DECODE), 默认为 DECODE * @param string * key 密钥 * @param int expiry 密文有效期, 加密时候有效, 单位 秒,0 为永久有效 * @return string 处理后的 原文或者 经过 base64_encode 处理后的密文 * * @example * * a = authcode('abc', 'ENCODE', 'key'); b = authcode(a, 'DECODE', * 'key'); // b(abc) * * a = authcode('abc', 'ENCODE', 'key', 3600); b = authcode('abc', * 'DECODE', 'key'); // 在一个小时内,b(abc),否则 b 为空 */ public static String uc_authcode(String string, String operation) { return uc_authcode(string, operation, null); } public static String uc_authcode(String string, String operation, String key) { return uc_authcode(string, operation, key, 0); } public static String uc_authcode(String string, String operation, String key, int expiry) { int ckey_length = 4; // note 随机密钥长度 取值 0-32; // note 加入随机密钥,可以令密文无任何规律,即便是原文和密钥完全相同,加密结果也会每次不同,增大破解难度。 // note 取值越大,密文变动规律越大,密文变化 = 16 的 ckey_length 次方 // note 当此值为 0 时,则不产生随机密钥 // key = md5( key!=null ? key : UC_KEY); key = md5(key); String keya = md5(substr(key, 0, 16)); String keyb = md5(substr(key, 16, 16)); String keyc = ckey_length > 0 ? (operation.equals("DECODE") ? substr( string, 0, ckey_length) : substr(md5(microtime()), -ckey_length)) : ""; String cryptkey = keya + md5(keya + keyc); int key_length = cryptkey.length(); string = operation.equals("DECODE") ? base64_decode(substr(string, ckey_length)) : sprintf("%010d", expiry > 0 ? expiry + time() : 0) + substr(md5(string + keyb), 0, 16) + string; int string_length = string.length(); StringBuffer result1 = new StringBuffer(); int[] box = new int[256]; for (int i = 0; i < 256; i++) { box[i] = i; } int[] rndkey = new int[256]; for (int i = 0; i <= 255; i++) { rndkey[i] = (int) cryptkey.charAt(i % key_length); } int j = 0; for (int i = 0; i < 256; i++) { j = (j + box[i] + rndkey[i]) % 256; int tmp = box[i]; box[i] = box[j]; box[j] = tmp; } j = 0; int a = 0; for (int i = 0; i < string_length; i++) { a = (a + 1) % 256; j = (j + box[a]) % 256; int tmp = box[a]; box[a] = box[j]; box[j] = tmp; result1 .append((char) (((int) string.charAt(i)) ^ (box[(box[a] + box[j]) % 256]))); } if (operation.equals("DECODE")) { String result = result1.substring(0, result1.length()); if ((Integer.parseInt(substr(result.toString(), 0, 10)) == 0 || Long .parseLong(substr(result.toString(), 0, 10)) - time() > 0) && substr(result.toString(), 10, 16).equals( substr(md5(substr(result.toString(), 26) + keyb), 0, 16))) { return substr(result.toString(), 26); } else { return ""; } } else { return keyc + base64_encode(result1.toString()).replaceAll("=", ""); } } /** * * @description setBbsUserCookie(这里用一句话描述这个方法的作用) * @conditions (这里描述这个方法适用条件 – 可选) * @param response * @param user * void * @exception * @since 1.0.0 */ public static void setBbsUserCookie(HttpServletResponse response, BbsUser user) { String saltkey = WebImpUtils.getRandomString(8); String cookieInfo = MessageFormat.format("{0}\t{1}", user.getPassword(), String.valueOf(user.getUid())); String auth = BBsUtils.urlencode(BBsUtils.uc_authcode(cookieInfo, "ENCODE", BBsUtils.md5(BBS_AUTH_KEY + saltkey))); WebImpUtils.setUserCookie(response, COOKIE_PRE + "auth", auth, COOKIE_DOMAIN); WebImpUtils.setUserCookie(response, COOKIE_PRE + "saltkey", saltkey, COOKIE_DOMAIN); } /** * * @description clearBbsUserCookie(这里用一句话描述这个方法的作用) * @conditions (这里描述这个方法适用条件 – 可选) * @param response * void * @exception * @since 1.0.0 */ public static void clearBbsUserCookie(HttpServletResponse response) { WebImpUtils.removeCookie(response, COOKIE_PRE + "auth", COOKIE_DOMAIN); WebImpUtils.removeCookie(response, COOKIE_PRE + "saltkey", COOKIE_DOMAIN); } }至于其中用到的WebImpUtils自己参考实现就可了,很简单的,例如继承org.springframework.web.util.WebUtils.