阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

Apache CXF拦截器Interceptor实现WebServices用户验证

482次阅读
没有评论

共计 5797 个字符,预计需要花费 15 分钟才能阅读完成。

Apache CXF 应该都知道,是一个开源的 Services 框架,可以构建基于 SOAP 或 RESTful 的 WebServices,并且可以和 spring 天然地进行无缝集成。
不过这里不是关于 CXF 的系统介绍,而是在开发 WebServices 时遇到一些问题,最终使用 CXF 拦截器完成需求。所以侧重记录使用 CXF 的拦截器。

问题

先说问题,其实也很简单,简言之就是在请求时需要对请求做一些预先处理。
项目框架做成了所谓的前后端分离,即前台 app 使用第三方工具生成,调用后台通过 WebServices。可以理解为前台就是静态的 Html 与 js, 后台就是 WebServices。现在问题是要求是每次请求时需要做下验证,保证当前登录设备的用户没有在其他设备登录,如果有就到跳到登录界面。
具体做法就是,在每次登录时会在系统记录一个唯一编码,然后在每次请求时先比较登录的账号和这个唯一编码和系统中已存的是否一致,如果不一致就到登录也,否则就可以继续请求。当然拦截会排除登录请求。因为前台 app 所有请求都是通过 WebServices,这样可以保证如果同一账号同时只能在一台设备上操作。

分析

CXF 的拦截器是 CXF 功能最主要的扩展点。通过自定义的 Interceptor,可以改变请求和响应的一些消息处理,其中最基本的原理还是一个动态代理。所以对于上面的需求应该可以是满足的.

拦截器接口

查看 cxf 的 API 了解到,cxf 是有拦截器接口定义的。

package org.apache.cxf.interceptor;

import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Message;

public interface Interceptor<T extends Message> {
    void handleMessage(T var1) throws Fault;

    void handleFault(T var1);
}

所以对于上面的问题就想到可以使用拦截器实现了。现在知道要做什么,也知道实现方向,就看下怎么做了。

拦截器实现方式

通过上面的借口定义可知,Interceptor 只有两个方法,一个处理消息 handleMessage,一个是处理错误 handleFault。但这里需要提醒注意的是,在实行具体的 Interceptor 的这两个方法中,千万别调用 Interceptor 内部的成员变量。这是由于 Interceptor 是面向消息来进行处理的,每个 Interceptor 都有可能运行在不同的线程中,如果调用了 Interceptor 中的内部成员变量,就有在 Interceptor 中造成临界资源的访问的情况,而这时的 Interceptor 也就不是线程安全的 Interceptor 了。我在实现过程中,由于没有注意这个问题,花了不少时间处理异常。
通常 cxf 不允许我们直接实现 Interceptor 接口,Interceptor 有一个子接口 PhaseInterceptor,顾名思义就是分阶段的拦截器。cxf 将消息的发送、接收的过程分为很多个阶段,如 send、receive、read、write 等,具体可以参考类 org.apache.cxf.phase.Phase。cxf 要求我们在定义一个拦截器的时候必须指定当前拦截器需要作用的阶段。
cxf 已经为我们提供了一个实现了 PhaseInterceptor 接口的抽象实现类 org.apache.cxf.phase.AbstractPhaseInterceptor,而且建议我们通过继承 AbstractPhaseInterceptor 来实现自定义拦截器。AbstractPhaseInterceptor 中没有定义默认的空构造方法,所以子类在实现的时候必须在构造方法中调用父类的某一个构造方法。以下是一个非常简单的继承自 AbstractPhaseInterceptor 的自定义拦截器的实现。

参数处理

到这里,技术基本上确定了,剩下的就是实现的一些细节问题。
首先,因为我需要比较请求参数和目前的后台保存的数据,所以首要解决怎么取得请求穿过来的参数。
通过上面的 Interceptor 接口定义可知,拦截器在执行时会将请求的参数放在 Message 中。所以只要知道如何解析 Message 应该就可解决问题了。

配置拦截器

因 cxf 和 spring 天然的关系,自然会联想到通过 spring 的方式配置定义拦截器。

解决

通过上面的分析思路基本清楚了, 下面就开始具体编码实现.

拦截器代码

先定义一个拦截器. 完成后代码如下:

package com.mungo.aop;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.XMLMessage;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 系统全局拦截器(排除登录服务调用)* 用于校验登录的账号是否已在别处登录
 * 已在别处登录这抛出异常
 * 
 */
public class AuthInterceptor extends AbstractPhaseInterceptor<Message> {
    private Logger logger = Logger.getLogger(this.getClass());

    public AuthInterceptor() {// 定义拦截器阶段 
        super(Phase.RECEIVE);
    }

    /**
     * @Description: 拦截器操作
     * @param message 被拦截到的消息
     * @throws Fault
     */
    @Override
    public void handleMessage(Message message)  {logger.info("===== 自定义拦截器 =======");

        String methodName= getMethod(message);
        // 排除拦截的方法 
        List<String> list = new ArrayList<String>();
        list.add("login");
        list.add("forgotPassword");
        if(!list.contains(methodName)) {Map<String, String> reqParamsMap = new HashMap<String, String>();
            // 根据请求方式区分取得 GET 和 POST 的参数 
            if (message.get(message.HTTP_REQUEST_METHOD).equals("GET")) {//get 参数,=& 格式 
                String reqParams = (String) message.get(message.QUERY_STRING);
                logger.info("请求的参数:" + reqParams);
                // 注:toMap 为自定义方法, 实现将 String 转成 map
                reqParamsMap = StringUtils.toMap(reqParams, "&");
            } else if (message.get(message.HTTP_REQUEST_METHOD).equals("POST")) {InputStream is = message.getContent(InputStream.class);
                BufferedReader in = new BufferedReader(new InputStreamReader(is));
                StringBuffer buffer = new StringBuffer();
                String line = "";
                try {while ((line = in.readLine()) != null) {//post 参数,json 格式 
                        buffer.append(line);
                    }
                    logger.info("请求的参数:" + buffer);
                    JSONObject jasonObject = JSONObject.parseObject(buffer.toString());
                    reqParamsMap = (Map) jasonObject;
                } catch (IOException e) {e.printStackTrace();
                }
                if (is != null) {// 这里一定要加,post 参数流读取结束如果不加这个操作, 会报 io 异常 
                    message.setContent(InputStream.class, new ByteArrayInputStream(buffer.toString().getBytes()));
                }
            }
            if (reqParamsMap != null) {String agentCode = String.valueOf(reqParamsMap.get("agentCode"));
                // 注: 这里是系统用户登陆唯一标志, 使用时自己定义 
                String cid = getCid();
                if (StringUtils.isNotEmpty(cid)&&
                      !StringUtils.equals(cid, String.valueOf(reqParamsMap.get("cid")))) {throw new Fault(new Exception("已在别处已登录"));
                 }
            }
        }
    }

    /**
     * @Description:handleMessage 异常后执行
     * @param message
     */
    @Override
    public void handleFault(Message message) {super.handleFault(message);

        logger.info("=================================:"+"handleFault");
    }

    /**
     * @Description: 取得请求服务的具体方法
     * @param message
     * @return
     */
    private String getMethod(Message message) {// 通过分析 webservice 的 uri 取得实际执行的方法, 该 webservice 使用 cxf 的 RESTFul 形式发布 
        String requestUri = (String) message.get(XMLMessage.REQUEST_URI);
        String[] methods = StringUtils.split(requestUri,"/");
        logger.debug("********method name:" + requestUri);
        return methods!=null && methods.length>0?methods[methods.length-1]:"";
    }
}

拦截器配置

配置拦截器很简单, 尤其是结合 spring.
这里只有简单的说明, 如果要配置成全局的拦截器, 只需要在 spring 的配置文件中增加节点.
需要注意的是, 在使用时候,一定要引入 命名空间 xmlns:cxf=http://cxf.apache.org/core,及其对应的模式 http://cxf.apache.org/schemas/core.xsd.

<!-- 全局 Bus(输入拦截器) -->
    <cxf:bus>
        <cxf:inInterceptors>
            <bean class="com.meyacom.crm.aop.AuthInterceptor"></bean>
        </cxf:inInterceptors>
    </cxf:bus>

如果拦截器只对某个 webservice 进行拦截, 可以使用 或节点配置.

<jaxrs:server id="fileServiceContainer" address="/fileService">
        <jaxrs:serviceBeans>
            <ref bean="fileService" />
        </jaxrs:serviceBeans>
        <jaxrs:extensionMappings>
            <entry key="octet-stream" value="application/json" />
        </jaxrs:extensionMappings>
        <jaxrs:providers>
            <ref bean="jsonProvider" />
        </jaxrs:providers>
        <jaxrs:inInterceptors>
            <bean class="com.meyacom.crm.aop.AuthInterceptor"></bean>
        </jaxrs:inInterceptors>
        <jaxws:outInterceptors>
              <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"/>
          </jaxws:outInterceptors>
    </jaxrs:server>

如上所示,可以同时为 in 和 out 两个拦截器链 配置 拦截器,其中 in 为 cxf 本身自带的日志拦截器;这里额外为 out 拦截器链配置打印日志显示。
当然配置除了使用配置文件, 也可是使用代码实现, 但这里只是为了解决实际问题不是为了系统介绍拦截器, 所以不在赘述.

后记

至此, 遇到的问题基本解决了. 总共花了大约一天半的时间,cxf 之前没有系统的了解过, 这次也第一次使用. 也是靠着经验一边摸索一边实现, 中间的确是很花时间. 以后得抽个时间把 cxf 好好学习一次.

本文永久更新链接地址 :http://www.linuxidc.com/Linux/2016-12/138291.htm

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-21发表,共计5797字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中

星哥玩云

星哥玩云
星哥玩云
分享互联网知识
用户数
4
文章数
19350
评论数
4
阅读量
7960870
文章搜索
热门文章
星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛 NAS-6:抖音视频同步工具,视频下载自动下载保存 前言 各位玩 NAS 的朋友好,我是星哥!...
星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛 NAS-3:安装飞牛 NAS 后的很有必要的操作 前言 如果你已经有了飞牛 NAS 系统,之前...
我把用了20年的360安全卫士卸载了

我把用了20年的360安全卫士卸载了

我把用了 20 年的 360 安全卫士卸载了 是的,正如标题你看到的。 原因 偷摸安装自家的软件 莫名其妙安装...
再见zabbix!轻量级自建服务器监控神器在Linux 的完整部署指南

再见zabbix!轻量级自建服务器监控神器在Linux 的完整部署指南

再见 zabbix!轻量级自建服务器监控神器在 Linux 的完整部署指南 在日常运维中,服务器监控是绕不开的...
飞牛NAS中安装Navidrome音乐文件中文标签乱码问题解决、安装FntermX终端

飞牛NAS中安装Navidrome音乐文件中文标签乱码问题解决、安装FntermX终端

飞牛 NAS 中安装 Navidrome 音乐文件中文标签乱码问题解决、安装 FntermX 终端 问题背景 ...
阿里云CDN
阿里云CDN-提高用户访问的响应速度和成功率
随机文章
支付宝、淘宝、闲鱼又双叕崩了,Cloudflare也瘫了连监控都挂,根因藏在哪?

支付宝、淘宝、闲鱼又双叕崩了,Cloudflare也瘫了连监控都挂,根因藏在哪?

支付宝、淘宝、闲鱼又双叕崩了,Cloudflare 也瘫了连监控都挂,根因藏在哪? 最近两天的互联网堪称“故障...
一句话生成拓扑图!AI+Draw.io 封神开源组合,工具让你的效率爆炸

一句话生成拓扑图!AI+Draw.io 封神开源组合,工具让你的效率爆炸

一句话生成拓扑图!AI+Draw.io 封神开源组合,工具让你的效率爆炸 前言 作为天天跟架构图、拓扑图死磕的...
安装并使用谷歌AI编程工具Antigravity(亲测有效)

安装并使用谷歌AI编程工具Antigravity(亲测有效)

  安装并使用谷歌 AI 编程工具 Antigravity(亲测有效) 引言 Antigravity...
安装Black群晖DSM7.2系统安装教程(在Vmware虚拟机中、实体机均可)!

安装Black群晖DSM7.2系统安装教程(在Vmware虚拟机中、实体机均可)!

安装 Black 群晖 DSM7.2 系统安装教程(在 Vmware 虚拟机中、实体机均可)! 前言 大家好,...
小白也能看懂:什么是云服务器?腾讯云 vs 阿里云对比

小白也能看懂:什么是云服务器?腾讯云 vs 阿里云对比

小白也能看懂:什么是云服务器?腾讯云 vs 阿里云对比 星哥玩云,带你从小白到上云高手。今天咱们就来聊聊——什...

免费图片视频管理工具让灵感库告别混乱

一言一句话
-「
手气不错
240 元左右!五盘位 NAS主机,7 代U硬解4K稳如狗,拓展性碾压同价位

240 元左右!五盘位 NAS主机,7 代U硬解4K稳如狗,拓展性碾压同价位

  240 元左右!五盘位 NAS 主机,7 代 U 硬解 4K 稳如狗,拓展性碾压同价位 在 NA...
三大开源投屏神器横评:QtScrcpy、scrcpy、escrcpy 谁才是跨平台控制 Android 的最优解?

三大开源投屏神器横评:QtScrcpy、scrcpy、escrcpy 谁才是跨平台控制 Android 的最优解?

  三大开源投屏神器横评:QtScrcpy、scrcpy、escrcpy 谁才是跨平台控制 Andr...
自己手撸一个AI智能体—跟创业大佬对话

自己手撸一个AI智能体—跟创业大佬对话

自己手撸一个 AI 智能体 — 跟创业大佬对话 前言 智能体(Agent)已经成为创业者和技术人绕...
4盘位、4K输出、J3455、遥控,NAS硬件入门性价比之王

4盘位、4K输出、J3455、遥控,NAS硬件入门性价比之王

  4 盘位、4K 输出、J3455、遥控,NAS 硬件入门性价比之王 开篇 在 NAS 市场中,威...
开源MoneyPrinterTurbo 利用AI大模型,一键生成高清短视频!

开源MoneyPrinterTurbo 利用AI大模型,一键生成高清短视频!

  开源 MoneyPrinterTurbo 利用 AI 大模型,一键生成高清短视频! 在短视频内容...