SuperMap iServer 支持在登录时对用户身份进行多重验证。除已支持的“用户名+密码+图形验证码”方式外,也可根据业务需要扩展自定义验证方式,例如:手机短信验证码、UKey认证等,用户通过扩展开发可使 iServer 与已有的认证服务器对接。本文档将指导您如何对登录界面进行扩展定制,实现在一次登录中同时验证用户名、密码及短信验证码等其他自定义信息。

扩展和配置流程

我们通过实现一个扩展来说明登录认证方式的扩展流程,以扩展“短信验证码”方式为例。

1. 扩展“短信验证码”方式实现类

分别实现了如下4个类:

  • UserCaptchaServiceHandler 类

用于验证码服务基础环境的初始化,建立请求路由机制。代码如下所示:

package com.supermap.services.security.captcha.handlers;
import java.io.IOException;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.context.support.AbstractApplicationContext;
import com.supermap.server.host.webapp.handlers.AbstractHandler;
import com.supermap.server.host.webapp.handlers.WebAppRequestDispatcher;
import com.supermap.services.security.captcha.rest.UserCaptchaServiceApplication;
import com.supermap.services.security.captcha.rest.UserCaptchaServiceServlet;
public class UserCaptchaServiceHandler extends AbstractHandler {
    UserCaptchaServiceServlet captchaServiceServlet;
    AbstractApplicationContext applicationContext;
    @Override
    public void init(FilterConfig filterConfig){
        try{
            applicationContext = UserCaptchaServiceApplication.creatApplicationContext();
            captchaServiceServlet = new UserCaptchaServiceServlet(applicationContext.getBean("webResourceConfig",ResourceConfig.class));
            captchaServiceServlet.init(this.getServletConfig(filterConfig));
        } catch (ServletException e){
            logger.warn("init captcha service servlet exception", e);
        }
    }  
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        String path = getPathInfo(request);
        if(!path.contains("/security/userCaptcha")){
            return;
        }
        WebAppRequestDispatcher dispatcher = new WebAppRequestDispatcher("/", this.captchaServiceServlet);
        dispatcher.forward(request, response);
        this.setHandleFinished(request);
    }
    @Override
    public void destroy(){
        if(this.captchaServiceServlet != null){
            this.captchaServiceServlet.destroy();
        }
        if(applicationContext !=null) {
            applicationContext.close();
        }
    }
}

  • UserCaptchaServiceApplication 类

完成REST服务框架的初始化。代码如下所示:

package com.supermap.services.security.captcha.rest;
import java.util.Set;
import org.glassfish.jersey.internal.guava.Sets;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.supermap.services.rest.HttpExceptionMapper;
import com.supermap.services.rest.IllegalArgumentExceptionMapper;
import com.supermap.services.rest.IllegalStateExceptionMapper;
import com.supermap.services.rest.ScNotModifiedExceptionMapper;
import com.supermap.services.rest.decoders.JsonDecoder;
import com.supermap.services.rest.encoders.JsonEncoder;
import com.supermap.services.rest.encoders.JsonpEncoder;
import com.supermap.services.rest.encoders.RJsonEncoder;
import com.supermap.services.rest.encoders.XMLEncoder;
import com.supermap.services.rest.resources.impl.JaxrsStaticResource;
public class UserCaptchaServiceApplication extends ResourceConfig{

    //新增初始化配置文件CaptchaServiceJaxrsResourceConfig.xml   
    private static final String WEB_RESOURCE_CONFIG = "classpath:com/supermap/services/security/captcha/rest/CaptchaServiceJaxrsResourceConfig.xml";
    public UserCaptchaServiceApplication(){
        Set<Object> instances = Sets.newHashSet();
        instances.add(new JsonEncoder());
        instances.add(new RJsonEncoder());
        instances.add(new XMLEncoder());
        instances.add(new JsonpEncoder());
        instances.add(new IllegalArgumentExceptionMapper());
        instances.add(new IllegalStateExceptionMapper());
        instances.add(new JaxrsStaticResource());
        instances.add(new JsonDecoder());
        registerInstances(instances);
        register(ScNotModifiedExceptionMapper.class);
        register(HttpExceptionMapper.class);
        register(UserCaptchaResource.class);
    }
    public static AbstractApplicationContext creatApplicationContext() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
        context.setConfigLocation(WEB_RESOURCE_CONFIG);
        context.refresh();
        return context;
    }
}

  • UserCaptchaServiceServlet 类

实现验证码服务与iServer Web容器的初始化集成。代码如下所示:

package com.supermap.services.security.captcha.rest;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import com.supermap.services.rest.JaxrsServletForJersey;
public class UserCaptchaServiceServlet extends JaxrsServletForJersey {
    private final ServletContainer servletContainer;
    public UserCaptchaServiceServlet(ResourceConfig resourceConfig) {
        servletContainer = new ServletContainer(resourceConfig);
    }
    @Override
    protected ServletContainer getServletContainer() {
        return servletContainer;
    }
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        servletContainer.init(config);
    }
}

  • UserCaptchaResource 类

负责验证码校验业务逻辑的实现,接收验证码参数,执行具体的校验判断并返回验证结果。其中,@Path("/security/userCaptcha") 定义了该资源的URI,此示例中与验证码相关的请求都将以“/security/userCaptcha” 作基础路径,例如完整的请求路径为“http://<server_address>/iserver/security/userCaptcha”;@POST函数用于验证用户输入的验证码;@GET函数用于在登录界面(前端)通过GET请求触发验证码的生成与发送逻辑,主动获取验证信息。

代码如下所示:

package com.supermap.services.security.captcha.rest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
@Path("/security/userCaptcha")
public class UserCaptchaResource {
    @POST
    public String checkCaptcha(@Context HttpServletRequest request, @Context HttpServletResponse response, String captcha){
        //在该处实现校验逻辑captcha为请求体中传入的检验参数    
        System.out.println(captcha + "------------------------------------------------");
        return captcha + "sucess post";

    }

    @GET

    public Object getCaptcha(@Context HttpServletRequest request, @Context HttpServletResponse response) throws IOException {

 

          return "";
    }
}

编译后请将相应的 class 文件分别更新至 SuperMap iServer 核心库 iserver-all-{版本号}.jar 内的对应路径中,即 %SuperMap iServer_HOME%\webapps\iserver\WEB-INF\lib\iserver-all-{版本号}.jar 下。其中 UserCaptchaServiceHandler.class 置于 com/supermap/services/security/captcha/handlers/ 路径下,而 UserCaptchaServiceApplication.class、UserCaptchaServiceServlet.class 和 UserCaptchaResource.class 应置于 com/supermap/services/security/captcha/rest/ 路径下。

2. “短信验证码”方式的配置

a. 在产品包根目录 %SuperMap iServer_HOME%/webapps/iserver/WEB-INF/lib 的server-host-model-{版本号}.jar下,位于 com/supermap/server/host/webapp 下的 handlers.xml 文件的节点<util:list>中添加如下内容,并新增相应配置:

    ...
    <util:list id="baseHandlerList">
        ...
        <value>userCaptchaServiceHandler</value>    //新增    
    </util:list>
        ...
    <bean id="userCaptchaServiceHandler" class="com.supermap.server.host.webapp.handlers.HandlerInfo">
        <property name="type"
                  value="com.supermap.services.security.captcha.handlers.UserCaptchaServiceHandler" />
        <property name="priority" value="984" />
    </bean>
</beans>

:"priority"设值时须唯一,不能与已有配置重复。

b. 在产品包根目录 %SuperMap iServer_HOME%webapps/iserver/WEB-INF/lib 的iserver-all-{版本号}.jar下,位于com/supermap/services/security/captcha/rest 下新建一个 CaptchaServiceJaxrsResourceConfig.xml 文件,里面的内容如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
     http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"
    default-lazy-init="true">
    <!-- 开启注解配置 -->
    <context:annotation-config />
    <!-- 扫描用到spring注解的包,自动装配bean -->
    <context:component-scan base-package="com.supermap.services.security.captcha"/>
    <bean id="indexResource" class="com.supermap.services.security.captcha.rest.UserCaptchaResource"/>
    <bean id="webResourceConfig" class="org.glassfish.jersey.server.ResourceConfig" factory-method="forApplication">
        <constructor-arg value="#{new com.supermap.services.security.captcha.rest.UserCaptchaServiceApplication().register(indexResource)}"/>
    </bean>
</beans>

配置文件中<beans>为 Spring 框架的配置根元素,用于定义验证码服务组件的初始化配置,可包含多个<bean>标签。

3. 扩展登录安全界面功能与组件

可通过定制登录界面增加按钮,来实现发送验证码及验证机制。下面以基于“图形验证码”改造,扩展发送“短信验证码”方式为例。

a. 在产品包根目录 %SuperMap iServer_HOME%webapps/iserver/WEB-INF/lib 的iserver-all-{版本号}.jar下,位于 templates/ 下的 login_security*.ftl 文件的节点<div>标识符id="loginPage"下添加并注释/删除如下内容:

    ...
<div id="loginPage">
        ...
                <div align="center" width="100%" style="position: relative;">
                    <div id="captcha-tr"  style="display: none">
                        <div style="margin-right: auto;">
                            <input class="form-control logininput " id="captcha-input" type="text" name="captcha" maxlength="4"
                                   placeholder="请输入验证码">
                        </div>

                        <!-- 说明:请自行定义函数 sendCaptcha(),以实现获取短信验证码的业务逻辑。 -->                        
                        <button id="loginButton" class="btn" onclick="sendCaptcha();" type="button" disabled>获取短信验证码</button>    //新增。增加获取验证码按钮
                        // 注释掉原图形验证码“更新图片”模块                        
<!--                        <div style="margin-left: auto;">
                            <img id="captcha-img" alt="图片加载失败" title="看不清,点击换一张">
                        </div> -->
                    </div>
                </div>

b. 在上述文件的 ready() 方法下修改为如下内容:

    ...   

        $(document).ready(function () {
        var captchaConfig = {};
        captchaConfig.enable = true
        captchaConfig.length = 6 //配置预期要获取的验证码长度
        initVerificationCode(captchaConfig) 
        //processCaptcha();
    });

    ...

其中“captchaConfig.enable”处控制是否启用短信验证码登录,true表示启用,false不启用;“captchaConfig.length”处配置获取验证码预期字符长度。

c. 在上述文件中注释/删除原图形验证码机制相关内容

    ...   

    function initVerificationCode(imageCaptchaConfig) {

          ...
        // var image = document.getElementById('captcha-img');
        // image.src = getRootUrl().replace("/manager", "") + "security/captcha.json" + "?t=" + new Date().getTime();

        ...

        //image.onclick = function () {
        //    imageOnclick(imageCaptchaConfig, captchaInput, captchaItem, image);
        //};
        //image.onload = function () {
        //    lastRefreshTime = Date.now();
        //};        
    });

        loadLoginPage();
    }

          ...

    function verifyCaptcha(captchaInput, captchaItem) {

          ...

                // var image = document.getElementById('captcha-img');
                // image.src = getRootUrl().replace("/manager", "") + "security/captcha.json" + "?t=" + new Date().getTime();

            }
        });
    }

    ...

注释/删除后,需根据实际情况在函数声明“verifyCaptcha”下添加短信验证码机制相关内容,例如: var url = getRootUrl().replace("/manager", "")+ "services/security/userCaptcha.json";

4. 查看扩展结果

当完成以上步骤时,表示已经成功添加了“短信验证码“认证登录方式。重启 SuperMap iServer 并清除浏览器缓存后,访问 iServer 登录界面,查看登录界面表单是否已按预期显示为基于短信验证码方式的输入选项(包括短信验证码输入框、获取验证码按钮等)。使用正确的用户名/密码及短信验证码尝试登录验证是否成功。如任一项验证信息错误,即应登录不成功。

注意事项:

1. 修改配置文件和login_security*.ftl模板文件之前,建议进行备份;

2. 建议在测试环境中先行验证后,再部署至生产环境。