当前位置: 代码迷 >> JavaScript >> [转]Discuz!论坛通行证与JSP网站的调整
  详细解决方案

[转]Discuz!论坛通行证与JSP网站的调整

热度:362   发布时间:2012-10-11 10:16:10.0
[转]Discuz!论坛通行证与JSP网站的整合
1.需求
需求:本项目网站需要和Discuz论坛实现同步登录、注销与注册。
2.引言.
Discuz论坛提供的Passport(通行证)接口可以很好的实现上述需求。通行证可以在Discuz论坛的系统设置中开启,开启通行证之后的论坛将不再接受除管理员以外的用户的登录请求,而与应用网站进行统一登录管理。整合之后的用户注册、登录、注销流程,请阅读参考资料[1],强烈建议看本文档之前先看一下参考资料[1],熟悉一下整合的流程。本文档的大部分代码是参照上面所说的文章,在其基础上修改而来。下面介绍实现的整个流程,并给出完整代码。
3.流程
应用程序与论坛整合的大体流程是:1、开发加密、解密类,以及密钥类 2、修改应用程序的登录、注销、注册的页面,使其能够满足同步登录的需要 3、在Discuz论坛的系统设置中启用通行证,并设置应用网站的地址及私钥(当然,在调试阶段,通行证应该一直是处于启用状态的)。注:步骤2与3是没有先后顺序的,即一旦开始进行步骤2,无论是先做的登录、注销还是注册,都应该开启Discuz论坛的通行证,以方便测试,否则即使代码正确也不会实现同步。
4.涉及的页面(以下URL为举例)
Discuz! 的 URL 为http://127.0.0.1:8088/index.php
项目程序的 URL 为http://localhost:8081/
项目程序的注册页面为user/userregpre.do
项目程序的登录页面为index.do
项目程序的退出页面为user/userlogout.do
以上这些会在下面的代码中和discuz论坛的通行证的配置中用到。
5.实现过程
5.1 开发DiscuzPassport及Encryption两个类,实现与Discuz一致的数据加密。这两个类是由上文说的成熟案例得到。经测试这两个类方法完全符合要求并且无错误,所以直接套用即可。

-----DiscuzPassport.java-----

import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
import sun.misc.BASE64Decoder;



public class DiscuzPassport {

public static String encrypt(String src, String key) {
  Random random = new Random();
  random.setSeed(System.currentTimeMillis());
  String rand = "" + random.nextInt() % 32000;
  String encKey = Encryption.generateKey(rand, "MD5");
  int ctr = 0;
  String tmp = "";
  for (int i = 0; i < src.length(); i++) {
   ctr = (ctr == encKey.length() ? 0 : ctr);
   tmp += encKey.charAt(ctr);
   char c = (char) (src.charAt(i) ^ encKey.charAt(ctr));
   tmp += c;
   ctr++;
  }
  String passportKey = passportKey(tmp, key);
  return new sun.misc.BASE64Encoder().encode(passportKey.getBytes());
}

public static String decrypt(String src, String key) {
  byte[] bytes = null;
  try {
   bytes = new BASE64Decoder().decodeBuffer(src);
   src = new String(bytes);
  } catch (Exception e) {
   return null;
  }
  src = passportKey(src, key);
  String tmp = "";
  for (int i = 0; i < src.length(); ++i) {
   char c = (char) (src.charAt(i) ^ src.charAt(++i));
   tmp += c;
  }
  return tmp;
}

public static String passportKey(String src, String key) {
  String encKey = Encryption.generateKey(key, "MD5");
  int ctr = 0;
  String tmp = "";
  for (int i = 0; i < src.length(); ++i) {
   ctr = (ctr == encKey.length() ? 0 : ctr);
   char c = (char) (src.charAt(i) ^ encKey.charAt(ctr));
   tmp += c;
   ctr++;
  }
  return tmp;
}

public static String passportEncode(Map data) {
  Set keys = data.keySet();
  String key = "";
  String ret = "";
  Iterator iterator = keys.iterator();
  while (iterator.hasNext()) {
   key = (String) iterator.next();
   try
   {
    ret += key + "=" + (String) data.get(key) + "&";
   }
   catch (Exception e)
   {
    return "";
   }
  }
  if (ret.length() > 0)
   return ret.substring(0, ret.length() - 1);
  return "";
}
}

------------------------------


-----Encryption.java-----

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;



public class Encryption {

public static String generateKey(String src, String algorithm) {
  MessageDigest m = null;
  try
  {
   m = MessageDigest.getInstance(algorithm);
   m.update(src.getBytes("UTF8"));
  }
  catch (NoSuchAlgorithmException e)
  {
   e.printStackTrace();
  }
  catch (UnsupportedEncodingException e)
  {
   e.printStackTrace();
  }
  byte s[] = m.digest();
  String result = "";
  for (int i = 0; i < s.length; i++)
  {
   result += Integer.toHexString(
     (0x000000FF & s[i]) | 0xFFFFFF00).substring(6);
  }
  return result;
}
}

------------------------------

5.2 修改几个页面,实现网站之间的整合,涉及的页面包括(注:看本部分时请参考原项目代码,效果会好些):

    index.php(Discuz论坛首页,包含登录、注册与退出的链接)
    ACT_UserLogin.java(项目网站的登录业务处理页面,这里将进行修改)
    ACT_UserLogout.java(项目网站的注销业务处理页面,这里将进行修改)
    ACT_UserReg.java(项目网站的注销业务处理页面,这里将进行修改)
  
5.2.1登录
  
    从论坛登录,页面跳转过程为:
    index.php -> index.do ->userlogin.do ->index.php

从项目网站登录,页面跳转过程为:
   index.do ->userlogin.do ->index.php ->index.do

    用户在论坛首页点击登录后,index.php将首先指向index.do,并以GET方式向其传递一个forward变量,用来存储登录后跳转的目标。这里,forward一般都是论坛首页,即http://127.0.0.1:8088/index.php。index.do主要负责给出用户登录界面,并以POST方式向forward变量。
logined.jsp负责处理从项目网站和论坛两处提交过来的登录申请。因此,它需要首先判断上一个页面是否提交了forward变量,如果没有,那么申请就是从应用网站提交过来的,只需按正常的流程登录(设置自身的cookie或session);如果有,那么除了按正常流程登录外,还需要对用户的登录信息进行加密,并按规定的格式传递给index.php(这部分工作由上面两个类来完成)。
而从项目网站登录的时候,userlogin.do由于没有接收到从index.do传过来的forward,所以会自行生成forward,这个时候forward存的是项目程序的登录URL,即http://localhost:8081/index.do,这样在Action转向discuz以后,又会从新跳转回项目网站。
   userlogin.do中调用两个类的关键代码如下(用红色标注部分,从注明“开始”到“结束”之间):

-----userlogin.do(部分)-----
   


public class ACT_UserLogin extends BaseAction {

public ACT_UserLogin() {
     super();
}

public ActionForward doExcute(ActionMapping mapping, ActionForm form,
         HttpServletRequest request, HttpServletResponse response,
         ActionMessages errors) throws Exception {
     //获取数据
     String strLoginName =request.getParameter("loginname");
     String strLoginPass =request.getParameter("loginpass");
     String strReturnUrl =request.getParameter("returnurl");
     
     if(strLoginName ==null || strLoginPass ==null){
         throw new Exception("error.common.badrequest");
     }
    
     //验证数据
     this.doValidate(errors, INFO_User.validateLoginName(strLoginName));
     this.doValidate(errors, INFO_User.validateLoginPass(strLoginPass));
     if(!errors.isEmpty()){
         return null;
     }
     
     //以下是关于应用程序与论坛的同步登录
     HttpSession session =request.getSession();
     String forward=null;
      //下面是判断forward是否为空,为空则说明请求是有项目本身传递的,则自行为//froward赋值
     if(request.getSession().getAttribute("forward")!=null){
         forward=(String)session.getAttribute("forward");
     }else{
         forward="http://localhost:8081/index.do";
     }
    
      //为三个变量赋值
     String username=strLoginName;
     String password=strLoginPass;
     //传递到论坛的数据中,电子邮件地址是必须传递的部分,如果项目中没有的话则先写//个固定的即可
     String email="lyp_roc@163.com";
         
     Map mb = new LinkedHashMap();
     mb.put("time", "" + System.currentTimeMillis());
     mb.put("username", username);
     mb.put("password", password);
     mb.put("email", email);
     //注意这里的私钥一定要和discuz中的私钥相同,否则便会解密信息失败

     //下面的auth,verify,forward所存的具体值与具体意义,请参照文档[1].
     String key = "liyupeng01"; //私钥                                     
     String enc=DiscuzPassport.passportEncode(mb);
     String auth = DiscuzPassport.encrypt(enc, key);
     
     String verify = "login" + auth + forward + key;
     verify = Encryption.generateKey(verify, "MD5");  
     
//location就是要跳转的URL
String location = "http://127.0.0.1:8088/api/passport.php?action=login&auth=" + java.net.URLEncoder.encode(auth, "UTF-8") + "&forward=" +java.net.URLEncoder.encode(forward, "UTF-8") + "&verify=" + verify;
//结束
     //处理请求
     BO_User boUser =new BO_User();
     INFO_User infoUser =boUser.userLogin(strLoginName, strLoginPass);
     //设置到Session
     session.setAttribute("loginuser", infoUser);
     //设置资源下载列表
     ArrayList downFileList =new ArrayList();
     session.setAttribute("downfilelist", downFileList);    
    
     //进行跳转
     if(strReturnUrl !=null &&!strReturnUrl.equals("null")){
         //这里即为跳转部分
         return new ActionForward(location, true);
     }else{
         return new ActionForward("/index.do", true);
     }
    
}
}

------------------------------

5.2.2注销

    从论坛注销,页面跳转过程为:
index.php -> userlogout.do -> index.php
从项目网站注销,页面跳转过程为:
   userlogout.do -> index.php -> index.do


-----logout.jsp(部分)-----

public class ACT_UserLogout extends BaseAction {

public ACT_UserLogout() {
         super();
}

public ActionForward doExcute(ActionMapping mapping, ActionForm form,
                HttpServletRequest request, HttpServletResponse response,
                ActionMessages errors) throws Exception {
         //获取返回地址
        //String strReturnUrl =request.getParameter("returnurl");
         HttpSession session =request.getSession();
         session.removeAttribute("loginuser");
         session.removeAttribute("downfilelist");
        
         //以下是论坛与应用程序同步注销功能代码

     //判断注销命令是从discuz传递的还是从项目程序传递的
     //如果来自项目本身,则Action自行给forward赋值
         String forward=null;
         if(request.getParameter("forward") != null){
                forward=request.getParameter("forward");
                System.out.println(forward+"discuz!");
         }else{
                forward="http://localhost:8081/index.do";
         }
     //密钥
         String key = "liyupeng01";                   
         String verify = "logout" + forward + key;
         verify = Encryption.generateKey(verify, "MD5");  
         
         String location = "http://127.0.0.1:8088/api/passport.php?action=logout&" + "&forward=" + java.net.URLEncoder.encode(forward, "UTF-8") + "&verify=" + verify;
         System.out.println(location);
        
         //跳转
//              if(strReturnUrl !=null && !strReturnUrl.equals("null")){
         //完成跳转
                return new ActionForward(location, true);
      //结束
//              }else{
//                     return new ActionForward("/index.do", true);
//              }
}

}

------------------------------

5.2.3注册
  由于要求论坛在后台管理中关闭论坛的注册功能,所以只有项目程序有注册的功能,在这里将把forward写死。此部分的代码其实和登录部分的代码几乎一样,读者参照登录部分代码就可以比较出来。
    页面跳转过程为:
   ACT_UserReg.java -> index.php ->ACT_UserReg.java
-----userreg.do(部分)-----
public class ACT_UserReg extends BaseAction {

public ACT_UserReg() {
         super();
}

public ActionForward doExcute(ActionMapping mapping, ActionForm form,
                HttpServletRequest request, HttpServletResponse response,
                ActionMessages errors) throws Exception {
         //验证session中的activecard
         HttpSession session =request.getSession();
         INFO_Card infoCard =(INFO_Card) session.getAttribute("activecard");
         INFO_Cardtype infoCardtype =(INFO_Cardtype)session.getAttribute("activecardtype");
         INFO_User infoAgentUser =(INFO_User)session.getAttribute("agentuser");
         if(infoCard ==null){
                throw new Exception("error.action.user.act_userreg.error1");
         }
        
         //获取用户数据
         Integer iSex =null;
         Long lAgentUserId =new Long(0);
         String strLoginName =request.getParameter("loginname");
         String strLoginPass =request.getParameter("loginpass");
         String strRealName =request.getParameter("realname");
         String strSex =request.getParameter("sex");
        
         //验证数据
         this.doValidate(errors, INFO_User.validateLoginName(strLoginName));
         this.doValidate(errors, INFO_User.validateLoginPass(strLoginPass));
         this.doValidate(errors, INFO_User.validateRealName(strRealName));
         if(!errors.isEmpty()){
                return null;
         }
        
     //以下是关于应用程序与论坛的同步注册
     //直接写死forward
         String forward=forward="http://localhost:8081/index.do";

      //为三个变量赋值
         String username=strLoginName;
         String password=strLoginPass;
         //测试用
         String email="lyp_roc@163.com";
                
         Map mb = new LinkedHashMap();
         mb.put("time", "" + System.currentTimeMillis());
         mb.put("username", username);
         mb.put("password", password);
         mb.put("email", email);

         
         String key = "liyupeng01"; //私钥                                     
        String enc=DiscuzPassport.passportEncode(mb);
         String auth = DiscuzPassport.encrypt(enc, key);
         
         

         String verify = "login" + auth + forward + key;
         verify = Encryption.generateKey(verify, "MD5");  
          //最终跳转的URL
         String location = "http://127.0.0.1:8088/api/passport.php?action=login&auth=" + java.net.URLEncoder.encode(auth, "UTF-8") + "&forward=" +
         java.net.URLEncoder.encode(forward, "UTF-8") + "&verify=" + verify;
         //整合部分结束
         //转换数据
         iSex =new Integer(strSex);
         if(infoAgentUser !=null){
                lAgentUserId =infoAgentUser.getId();
         }
         //处理请求
         BO_User boUser =new BO_User();
         boUser.userReg(strLoginName, strLoginPass, strRealName, iSex, infoCardtype.getUserType(), infoCard.getId(), lAgentUserId);
         //从session中移除activecard和activecardtype
         session.removeAttribute("activecard");
         session.removeAttribute("activecardtype");
         session.removeAttribute("agentuser");
         //跳转
         return new ActionForward(location, true);
}

}

------------------------------


5.3 在Discuz论坛的系统设置中启用通行证,并设置应用网站的地址及私钥。(当然,在调试阶段,通行证应该一直是处于启用状态的)
用超级管理员进入论坛后台管理界面以后(用户名和密码都是admin),选择 “扩展设置” ->”通行证 API”,即可进入通行证设置。需要配置的选项有:
应用程序 URL 地址:http://localhost:8081/
通行证私有密匙: liyupeng01
应用程序注册地址: user/userregpre.do
应用程序登录地址: index.do
应用程序退出地址: user/userlogout.do
如果不是很明白,就参照参考文档[1]的要求配置,上面的说明很详细。(参考下图)

    至此,Discuz!论坛通行证与JSP网站的整合工作全部完成了。

6.注意事项
. 1、项目程序与论坛的密钥一定要完全相同。

2、在启动论坛通行证功能之前,千万要把论坛登录的URL,与超级管理员退出的URL备份,因为调试失败时需要关闭通行证功能从新来,如果没有备份这两个的话就不进入不了论坛了。想关闭的时候用论坛登录URL进入,而不是直接点击登录进入,因为这样会进入项目程序的登录界面。而用admin开启论坛通行证之后,如果想注销admin,则只能用admin的URL来注销了。URL例子:
discuz登录:http://127.0.0.1:8088/logging.php?action=login
admin退出:http://127.0.0.1:8088/logging.php?action=logout&formhash=828f5a79

3、如果没有做注册,就做了登录的整合,那么可以在项目程序和论坛中同时申请一个用户名相同的用户,这样如果代码正确的话就可以实现同步登录。

4、如果测试的时候,失败以后想注销论坛中已经登录的用户却注销不了(因为通行证已经开启,注销功能已经与项目程序同步,点击注销只会跳转到项目程序中的注销界面),那么可以到论坛的数据库中,直接删除该用户(admin用户千万别这么做),重启Apache即可。

5、注意在代码套用的时候一定要记得把所有东西全部改成自己的,不然找都不好找。

6注意,千万不能在应用程序中也申请一个和论坛的超级管理员用户名与密码都相同的用户(admin),这样会造成论坛的超级管理员进入不了后台管理界面,这个时候只能回复以前的数据库中的members表了。

7、以后修改数据库尤其是涉及到admin管理员用户之前,千万要备份数据库!!!
  相关解决方案