httpclient的webservice客户端与session验证

   欢迎来到:http://observer.blog.51cto.com    

   webservcie是可以跨语言跨平台开发的一种技术,各种计算机语言都可以搭建服务器,同时各种计算机语言也可以开发客户端。只要有服务器,不管是用java还是C++抑或是php搭建的,其他语言都可以根据其开放的wsdl开发好客户端,然后调用其方法就像调用本地本项目的方法一样。
   本文使用httpclient开发客户端,服务器例子依赖于文章:http://observer.blog.51cto.com/4267416/1231205,如果有不明白的可以先看这些文章再看本文。本文在原来例子的基础上加上安全验证与session的使用与权限管理
   webservice的安全验证有很多方法,实现soapheader认证、集成windows身份验证、ip绑定验证等等。在此博主觉得,基础的,往往却是最有效的。本文使用过滤器验证登录身份,然后就像平时web登录后一样,在session中存放信息达到保持登录与权限的管理
第一:编写webservice客户端模板
   首先咱们使用httpclient写一个能够访问webservice的客户端,详情可下载源码查看。
   在此,因为wsdl、url、encoding还有就是返回的字段名在后面修改的可能性很大,所以使用配置文件存放他们的信息。

   webservicetest.properties:

webservice.wsdl=http://localhost:8080/webServiceCXF/services/Service?wsdl
    webservice.url=http://www.observer.com/service
    webservice.encoding=utf-8
    webservice.fieldname=result

   然后包装一个类读取他们:com.observer.webservcie.util.ServicePropertiesUtil详情可以下载代码查看,咱们说正点。
   在我的上一篇文章:http://observer.blog.51cto.com/4267416/1244274中,介绍了js的webservice客户端的相关内容,其中XMLHttpRequest所起到的作用其实就相当于浏览器访问一个web服务器的作用一样,只不过它访问的时候向服务器推送了一段xml,并且访问的是webservice的url而已。
   既然知道了这个原理,那么跟他一样在java中能够起到浏览器作用的httpclient能不能访问webservice的url进而访问webservice的方法呢?咱们做一下尝试,拭目以待。
   因为后面加上session验证之后,客户端封装就要有所变化,所以在此先将客户端模板写出来,其中发送的xml模板完全可以使用soupui自动生成,soupri的使用参看介绍js的那篇文章。
   TestWebServiceClient:

public class TestWebServiceClient {
        public static void main(String[] args) throws FormatException {
            ServicePropertiesUtil servicePropertiesUtil = new ServicePropertiesUtil("webservicetest");
            //获取读取配置文件的ServicePropertiesUtil类
            StringBuffer sb = new StringBuffer();
            sb.append("<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:ser='"+
                    servicePropertiesUtil.getWebServiceUrl()+"'>");
            sb.append("<soapenv:Header/>");
            sb.append("<soapenv:Body>");
            sb.append("<ser:getGradeName>");
            sb.append("<toid>"); sb.append(123); sb.append("</toid>");
            sb.append("</ser:getGradeName>");
            sb.append("</soapenv:Body>");
            sb.append("</soapenv:Envelope>");
            //访问webservcie时需要用到的xml
                                                         
            HttpClient httpClient = new HttpClient();//新建一个HttpClient类
            PostMethod postMethod=new PostMethod(servicePropertiesUtil.getWebServiceWsdl());
            postMethod.setRequestHeader("SOAPAction","http://dao.wfservice.ws.emolay.com");
            String html = null;
            try {
                String out = sb.toString();
                postMethod.setRequestEntity(new StringRequestEntity(out, "text/xml",
                        servicePropertiesUtil.getWebServiceEncoding()));
                //设置需要推送的数据
                httpClient.executeMethod(postMethod);//访问webservcie
                html=postMethod.getResponseBodyAsString();//获取访问webserivce反馈的xml
                postMethod.releaseConnection();
            } catch (HttpException e) {
                e.printStackTrace();
                throw new FormatException("连接异常1", "服务器正在维护中,请稍后再试,或者联系服务器维护人员进行处理");
            } catch (IOException e) {
                throw new FormatException("连接异常2", "服务器正在维护中,请稍后再试,或者联系服务器维护人员进行处理");
            }
                                                         
            Document doc=Jsoup.parse(html);//使用jsoup解析xml,得到webservice返回的数据
            Elements elements=doc.select(servicePropertiesUtil.getWebServiceFieldname());
            //根据webservcie的配置,解析xml中的数据
            String [] datas = new String[elements.size()];
            int i = 0;
            for(Element ele:elements){
                datas[i] = ele.text();
                i++;
            }
                                                         
            //输出数据  
            for(String d : datas){
                System.out.println(d);
            }
        }
    }

   技术上的东西在注释已经写的很清楚了,这里就不多说了,服务器使用依赖的服务器webServiceCXF,部署服务器到tomcat后,运行这里的main方法,的到结果:“is succeed”;证明客户端代码编写正确,同时也证明了,httpclient是可以访问webservice的
第二:改造服务器
   首先咱们将服务器改个名,改为:webServiceCXFSession
   在此,也许会有人想,如果在webservice中定义一个登录的方法,webserivce访问这个方法的时候就必须不能被拦截,但是过滤器中是不可能知道你的webservice是要访问什么方法的,只有到了webservice解析完传过来的xml才能知,这时候就矛盾了,怎么办呢?在此,博主起初是想在传过来的头文件中加上一个参数(如islogin),然后在过滤器中得到这个头文件信息,然后根据这个头文件信息进行判断是否放行。但是这时候又出现问题了,要是别人重写了客户端,把所有的方法传过来的这个islogin全部都改为true,其他都不变,那也可以不用验证就进入webservcie。最后,博主觉得,直接在过滤器中进行判断登录即可,而登录的用户名密码可以使用头文件的形式传过来。
   好了,登录的问题解决了,使用过滤器登录了之后,我们就可以在islogin为false时判断session中是否已经登录了,session中也的确放进了用户信息,但是要怎样才能在webservice的方法中的到session中的信息,进而判断其权限呢?在此就要用到博主的上一篇文章中提到的一个类了,该类为:java.lang.ThreadLocal,简单的解释就是:每个不同线程访问的都是不同的副本(原理上是否这样暂且不管,只要知道达到了这个效果就成),详情可看API或上一篇文章:http://observer.blog.51cto.com/4267416/1263577;我们在访问webservice的过程其实就像访问servlet,以多线程的方式访问,但是一旦进到了服务器,那么访问过滤器与访问webservice的是同一个线程。那么好,只要我们在webservcie实现类中定义一个ThreadLocal,其中放着session,然后在webservcie进入与出来时把session放进与移除,那么在不同用户访问webservcie都可以得到不同的自己的session,然后在方法里面判断其权限即可,如下所示
   GradeServiceImpl中:

private static final ThreadLocal<HttpSession> threadLocal =
        new ThreadLocal<HttpSession>();
    public static void setHttpSession(HttpSession session){
        threadLocal.set(session);
    }
    public static void removeSession(){
        threadLocal.remove();
    }
    /**
     * 验证权限,如果不是admin用户,返回状态码"-1"表示没有权限访问该方法
     */
    public String getGradeName(long toid) {
        HttpSession session = threadLocal.get();
        String user = (String)session.getAttribute("user");
        if("admin".equals(user)){
            System.out.println("业务逻辑");
        }else{
            return "-1";
        }
        return "is succeed";
    }

   然后在web.xml中添加过滤器:

<!-- webserivce过滤器 -->
<filter>
    <filter-name>webserviceFilter</filter-name>
    <filter-class>com.observer.cxf.login.filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>webserviceFilter</filter-name>
    <url-pattern>/services/*</url-pattern>
</filter-mapping>
<!-- webserivce过滤器 end -->

   新建过滤器的实现类:com.observer.cxf.login.filter.LoginFilter:

public class LoginFilter implements Filter{
    public void destroy() {
    }
    /**
     * 过滤器的过滤方法
     * 当传过来的html头文件中有islogin并且为true时表示用户需要登录,那么就验证登录
     * 当传过来的html头文件中有islogin并且为false时表示用户需要调用webservice,
     * 那么首先验证是否已经登录,如果已经登录就继续,如果没有登录就直接返回notlogin提示用户没有登录
     */
    public void doFilter(ServletRequest arg0, ServletResponse arg1,
            FilterChain arg2) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)arg0;
        HttpServletResponse response = (HttpServletResponse)arg1;
        HttpSession session = request.getSession();
        String islogin = request.getHeader("islogin");
        if("false".equals(islogin)){//判断是要登录还是要访问webservice
            String user = (String) session.getAttribute("user");
            if(user != null){//判断是否已经登录
                GradeServiceImpl.setHttpSession(session);
                //访问webservice之前将session放到webservice实现类中
                arg2.doFilter(request, response);
                GradeServiceImpl.removeSession();
                //webservice访问完成后移除里面的session
                return;
            }else{
                PrintWriter out = response.getWriter();
                out.println("notlogin");
                out.flush();
                out.close();
            }
        }else if("true".equals(islogin)){
            String username = request.getHeader("username");
            String password = request.getHeader("password");
            //的到头文件中传过来的username与password
            String user = LoginManger.login(username, password);
            PrintWriter out = response.getWriter();
            if(user!=null){
                session.setAttribute("user", user);
                out.println("islogin");
            }else{
                out.println("notlogin");
            }
            out.flush();
            out.close();
        }
    }
    public void init(FilterConfig arg0) throws ServletException {
    }
}

   以上代码中的LoginManger.login(username, password);是登录的业务逻辑管理类,一般要检查数据库,在此就不多做了,详情可下载源码看即可。
第三:封装客户端
   服务器已经写好了,究竟能不能用,写好客户端就知道了。
   在此,博主使用模板模式编写客户端,这样在我们的服务器修改了之后,只要大体上没改,我们就可以只修改实现类就可以了,当然,服务器方法添加了的话,抽象类还是要写的,一般情况下,服务器修改的就是一些配置文件上的东西,这时候,相应的xml就会有所改变,那么你就只要修改实现类就可以了。还有这里使用了可变长参数列表,这样如果服务器的webservice方法要是参数改变了的话,也只要修改实现类中的方法内容即可。
   上代码:
   抽象类com.observer.webservcie.httpclient.WebServiceClient:

public abstract class WebServiceClient {
    public static ServicePropertiesUtil servicePropertiesUtil = new ServicePropertiesUtil("webservice");
    private static String tmpcookies="";
    /**
     * 向webservcie发送请求
     * @param outXml 请求所需要推送的xml数据
     * @return 请求反馈的信息
     * @throws FormatException 当webservice服务器关闭或链接不可以等原因造成请求失败时,
     *  抛出com.observer.webservcie.error.FormatException
     */
    private String executeMethod(String outXml) throws FormatException{
        HttpClient httpClient = new HttpClient();
        PostMethod postMethod=new PostMethod(servicePropertiesUtil.getWebServiceWsdl());
        postMethod.setRequestHeader("Cookie", tmpcookies);
        postMethod.setRequestHeader("islogin","false");
        postMethod.setRequestHeader("SOAPAction","http://dao.wfservice.ws.emolay.com");
        String html = null;
        try {
            String out = outXml;
            postMethod.setRequestEntity(new StringRequestEntity(out, "text/xml",
                    servicePropertiesUtil.getWebServiceEncoding()));
            httpClient.executeMethod(postMethod);
            html=postMethod.getResponseBodyAsString();
        } catch (HttpException e) {
            e.printStackTrace();
            throw new FormatException("连接异常1", "服务器正在维护中,请稍后再试,或者联系服务器维护人员进行处理");
        } catch (IOException e) {
            throw new FormatException("连接异常2", "服务器正在维护中,请稍后再试,或者联系服务器维护人员进行处理");
        }finally{
            postMethod.releaseConnection();
        }
        return html;
    }
    /**
     * 向webservcie服务器发送登录请求
     * @param username 用户名
     * @param password 用户密码
     * @return 登录成功后返回服务器反馈的信息:islogin
     * @throws FormatException 当webservice服务器关闭或链接不可以等原因造成请求失败或登录失败时,
     *  抛出com.observer.webservcie.error.FormatException
     */
    public String login(String username, String password) throws FormatException{
        HttpClient httpClient = new HttpClient();
        PostMethod postMethod=new PostMethod(servicePropertiesUtil.getWebServiceWsdl());
        postMethod.setRequestHeader("islogin","true");
        postMethod.setRequestHeader("username",username);
        postMethod.setRequestHeader("password",password);
        String html = null;
        try {
            httpClient.executeMethod(postMethod);
            html=postMethod.getResponseBodyAsString();
            if(html!=null && "notlogin".equals(html.trim())){
                throw new FormatException("登录失败", "用户名密码错误");
            }
            Cookie[] cookies = httpClient.getState().getCookies();
            StringBuffer sb = new StringBuffer();
            for (Cookie c : cookies) {
                sb.append(c.toString() + ";");
            }
            synchronized(this){
                tmpcookies = sb.toString();
            }
        } catch (HttpException e) {
            e.printStackTrace();
            throw new FormatException("连接异常1", "服务器正在维护中,请稍后再试,或者联系服务器维护人员进行处理");
        } catch (IOException e) {
            throw new FormatException("连接异常2", "服务器正在维护中,请稍后再试,或者联系服务器维护人员进行处理");
        }finally{
            postMethod.releaseConnection();
        }
        return html;
    }
    /**
     * 将服务器反馈回来的xml解析出结果来
     * @param xml 服务器返回的xml
     * @return 解析出来的结果集
     */
    private String[] parseData(String xml){
        Document doc=Jsoup.parse(xml);
        Elements elements=doc.select(servicePropertiesUtil.getWebServiceFieldname());
        String [] datas = new String[elements.size()];
        int i = 0;
        for(Element ele:elements){
            datas[i] = ele.text();
            i++;
        }
        return datas;
    }
    /**
     * 想webservice方式请求,并且解析webservice反馈的xml得到结果
     * @param outXml 需要请求发送的xml
     * @return 返回webservice反馈的xml解析之后的结果
     * @throws FormatException 当用户没有登录或者
     * 服务器已经关闭时抛出com.observer.webservcie.error.FormatException
     */
    public String[] send(String outXml) throws FormatException{
        String xml = executeMethod(outXml);
        if("notlogin".equals(xml.trim())){
            throw new FormatException("登陆异常", "对不起您没有登录");
        }
        return parseData(xml);
    }
    /**
     * 获取webservice中getGradeName方法所需要的xml的抽象方法,该方法让子类重写,
     * 让其可以在服务器参数更改是灵活的更改
     * @param data 获取webservice中getGradeName所需要的参数
     * @return webservice中getGradeName方法所需要的xml
     * @throws FormatException 当参数不符合规则时
     * 抛出com.observer.webservcie.error.FormatException
     */
    public abstract String getGradeNameXml(String... data) throws FormatException;
    /**
     * 想webservice请求getGradeName方法的模板方法
     * @param data 获取webservice中getGradeName所需要的参数
     * @return 返回webservice的getGradeName方法返回的数据
     * @throws FormatException 当连接失败或者没有登录或者没有那个权限进行访问该方法时
     * 抛出com.observer.webservcie.error.FormatException
     */
    public String[] getGradeName(String... data) throws FormatException{
        String outXml = getGradeNameXml(data);
        String[] datas = send(outXml);
        if(datas.length>0 && "-1".equals(datas[0])){
            throw new FormatException("操作异常", "对不起你没有权限进行此操作");
        }
        return datas;
    }
}

   实现类:com.observer.webservcie.httpclient.impl.WebServiceClientImpl:

public class WebServiceClientImpl extends WebServiceClient {
        /**
         * 获取webservice中getGradeName方法所需要的xml的实现方法
         * @param data 获取webservice中getGradeName所需要的参数
         * @return webservice中getGradeName方法所需要的xml
         * @throws FormatException 当参数不符合规则时
         * 抛出com.observer.webservcie.error.FormatException
         */
        public String getGradeNameXml(String... toid) throws FormatException{
            int id;
            try{
                id = Integer.valueOf(toid[0]);
            }catch (Exception e) {
                throw new FormatException("参数错误","输入的参数必须为整数字符串");
            }
            StringBuffer sb = new StringBuffer();
            sb.append("<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:ser='"+
                    servicePropertiesUtil.getWebServiceUrl()+"'>");
            sb.append("<soapenv:Header/>");
            sb.append("<soapenv:Body>");
            sb.append("<ser:getGradeName>");
            sb.append("<toid>"); sb.append(id); sb.append("</toid>");
            sb.append("</ser:getGradeName>");
            sb.append("</soapenv:Body>");
            sb.append("</soapenv:Envelope>");
            return sb.toString();
        }
    }

   以上代码中,因为登录跟访问webservice是两个不同的与代码,所以写了两个连接webservice的方法,然后,因为不同的httpclient访问服务器都会产生不同的session,解决的办法就是将上一次访问的Cookie传到下一次访问的httpclient中,这样服务器就会认为你这一次访问的依然是同一个session;在此,用户登录过后,就会将cookie的有关信息存放在一个静态的变量(tmpcookies)中,然后如果下次要进行访问,就将这个变量的cookie放到httpclient的头文件中,让服务器知道你需要使用刚才的session进行访
第四:测试
   好了,服务器跟客户端都已经编写完毕,该测试一下了,先登录后访问,不登录就访问等等:

public class TestClient {
    public static void main(String[] args) {
        WebServiceClient webServiceClient = new WebServiceClientImpl();
        String[] datas1 = null;
        String[] datas2 = null;
        try {
            String login = webServiceClient.login("admin", "admin");
            System.out.println(login);
            datas1 = webServiceClient.getGradeName("123");
            datas2 = webServiceClient.getGradeName("2222");
        } catch (FormatException e) {
            e.printStackTrace();
            System.out.println(e.getDetails());
            return;
        }
        for(String data : datas1){
            System.out.println(data);
        }
        for(String data : datas2){
            System.out.println(data);
        }
    }
}

   在此,博主测试是通过的,如果哪位朋友修改客户端测出了bug,可以给在下个信息,咱们看看能否解决。
第五:打包客户端
   详细的打包客户端这里就不说了,因为在这一篇文章已经有了:http://observer.blog.51cto.com/4267416/1238972,不明白的可以参看它后面的打包测试。
   在这里要说明的是,因为这里的客户端用到了第三方依赖包,所以,你可以直接将依赖包一起打进自己的jar中,但是如果这样做的话,当别人导进你的客户端的同时也导进了依赖包的话就会出现冲突,所以,一般是写一个说明文档,说明该客户端依赖于什么什么第三方的包,想要使用此客户端必须在项目下导进第三方的包,然后将所需要的包列出来,在自己压缩出来的压缩包中也像其他如ssh的框架一样将依赖包也一起放进去。
   到了这里使用httpclient制作客户端与使用session进行用户的验证的方法就已经完成了,详细的可以下载附件代码查看。
   在此,博主是在maven的服务器的项目下进行改造的,因为使用jar包实在是太大,上传实在太慢了,所以有不会用maven的朋友,实在是抱歉了,不过相信能够做java到这一步的,看着本文都能够自己写出项目了,把项目中的代码跟配置移植到普通web项目应该更是没有问题的。

你可能感兴趣的:(httpclient,webservice,session)