单点登录系统技术(HttpClient)梳理(二)

系统中两种单点登录实现方式:

1、  直接登录单点登录认证系统。

2、  登录业务系统,如用户此时为首次登录,则跳转得到单点登录认证界面。如用户已通过认证,则直接登录到业务系统。

 

【补充说明】

单点登录认证服务器地址:http://192.168.61.124:8080/SSOAuth_v1.0.0/

业务系统服务器地址:http://192.168.61.134:8080/sepmis_v1.3.0/

 

直接登录单点登录认证系统流程:

1、  打开单点登录认证服务器登录页面,输入用户名密码。

2、  如果通过认证,此时页面跳转到系统主界面main.jsp。这个时候在加载main.jsp页面同时,页面后端通过ajax中jsonp方式,向单点登录认证服务器后台Action发送hello请求。需要传递参数,当前用户代码。

main.jsp页面核心代码如下:

<%
    UserSession userSession = (UserSession)session.getAttribute(AppConst.USER_SESSION_ID);
    String userCode = "";
    String userName = "";
    
    if(null != userSession){
       userCode = userSession.getUserCode();
       userName = userSession.getUserType();
    }
%>  
 
<script type="text/javascript">
    $.ajax({  
       type: "get",  
       contentType: "application/json",
       url: "http://192.168.61.124:8080/SSOAuth_v1.0.0/hello.do?usercode=<%=userCode%>&callback=?",  
       crossDomain: true,
       success: function(data) {  
            $.each(data.msg, function () {
                $.getJSON(this); 
            });
       }
    });  
    </script>

3、  后台接受hello请求,通过传递的参数用户代码,在SysRealuser表中判断用户所能够访问到的业务系统对象。拿到业务系统对象中url参数,组织各个业务系统setCookie Url。然后通过json异步传回前台。

Url格式例如:(红色字体处为变量)

http://192.168.61.134:8080/sepmis_v1.3.0/setCookie.do?userCode=xxx&&callback=?

代码如下:

/**
     * 当用户成功通过单点登录系统认证后,用户进入系统操作界面,同时页面异步发送此action请求
     * 获取创建json对象,对象内容为页面使用ajax异步访问各个业务系统的 setcookie action方法
     * @return result
     */
    public String hello(){
       try {
           List<SysInfo> tmp = getUserSysRealSys();
           UserSession userSession = AuthorityUtil.getSysUserSession();
           JSONObject jsonObject = new JSONObject();
           JSONArray jsonArray = new JSONArray();
           if(tmp!=null && tmp.size()>0){
              for(int i=0; i<tmp.size(); i++){
                  jsonArray.put(tmp.get(i).getSysUrl() + "/setCookie.do?userCode="+userSession.getUserCode()+"&callback=?");
              }
           }
            jsonObject.put("msg", jsonArray);
            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");  
            response.setHeader("Cache-Control", "no-cache");  
           response.getWriter().write(jsonObject.toString());
           response.getWriter().flush();
           //释放资源
           tmp = null;
           userSession = null;
           //打印日志
           String info  = LoggerUtil.getInfoMsg("AJAX异步访问,用户权限范围内各业务系统的 setCookie方法");
           log.info(info);
           return null;
       } catch (IOException ie) {
           this.exceptionMessage.setError(ie.toString());
           this.exceptionMessage.setClassName(this.getClass().getName());
           this.exceptionMessage.setMessage("AJAX异步访问出现IO异常!");
           log.error(ie.toString());
           return ERROR;
       } catch (Exception ex) {
           this.exceptionMessage.setError(ex.toString());
           this.exceptionMessage.setClassName(this.getClass().getName());
           this.exceptionMessage.setMessage("AJAX异步访问出现异常!");
           log.error(ex.toString());
           return ERROR;
       }
    }

4、  前台main.jsp页面这时拿到传回的URL参数,此时执行Success代码段中的函数

success: function(data) {  
            $.each(data.msg, function () {
                $.getJSON(this); 
            });

此时function中参数data就是传回的URL参数,然后遍历执行Jquery中getJSON方法。此时通过AJAX技术异步又向各个业务系统发送setCookie请求,此操作可以视为一种透明的登录操作,用户是不能够感知到。

5、  业务系统接受setCookie action请求。

代码如下:

public String setCookie(){
       try{
           // - 查找用户岗位集合
           StringBuilder hql = new StringBuilder(300);
           userCode = request.getParameter("userCode");
           hql.append("FROM SysUser WHERE userCode ='").append(userCode).append("'");
           List<SysUser> sysUserList = sysUserService.findByHql(hql.toString());
           if(sysUserList.size() > 0){
              Cookie cookie = new Cookie("sso", userCode);
              cookie.setPath("/");
              //设置cookie的有效期,单位是秒;
              //如果不使用这个方法或者参数为负数的话,当浏览器关闭的时候,这个cookie就失效了
              //cookie一年内有效60*60*24*365
              cookie.setMaxAge(60*60); 
              response.addCookie(cookie);
           }
       }catch(Exception ex){
           //返回异常信息
           this.exceptionMessage.setError(ex.toString());
           this.exceptionMessage.setClassName(this.getClass().getName());
           this.exceptionMessage.setMessage("用户登录出现异常!");
           log.info(ex.toString());
           return ERROR;
       }
       return null;
    }

业务系统通过setCookie方法,将此用户在业务系统中Cookie存储到用户本地。

6、  输入业务系统连接。

业务系统同后台通过filter过滤所有请求,获取用户在当前服务器域下Cookie值,进行校验,通过校验进入系统,未通过校验返回单点登录认证页面。

SSOFilter.java核心代码如下

/**
     * servlet filter 过滤方法
     * @param ServletRequest
     * @param ServletResponse
     * @param FilterChain
     * @throws IOException,ServletException
     */
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
       // 用户本地cookie值
       String cookieValue = "";
       HttpServletRequest request = (HttpServletRequest) req;
       HttpServletResponse response = (HttpServletResponse) res;
       String url = request.getRequestURL().toString();
       // 返回结果
       String result = null;
       
       // 根据上下文根截取URL
       String[] urlArray = url.split(request.getContextPath());
       //如果是业务service地址,跳转到业务系统单点登录action
       if (urlArray.length == 2 && ("/").equals(urlArray[1])) {
           response.sendRedirect(url+"loginSSO.do");
           return;
       }else {
           for(int i=0; i<urlArray.length; i++){
              if("/setCookie.do".equals(urlArray[i])){
                  result = "setCookie";
                  break;
              }
           }
       }
       
       // 检查HTTP请求的head是否有需要的cookie
       Cookie[] diskCookies = request.getCookies();
       if (diskCookies != null) {
           log.info("【权限系统-用户认证过滤器】找到COOKIES");
           for (int i = 0; i < diskCookies.length; i++) {
              cookieValue = diskCookies[i].getValue();
              log.info("【权限系统-用户认证过滤器】COOKIES中找到用户标识:" + diskCookies[i].getName() + "=" + cookieValue);
              if (SSO_AUTH.equals(diskCookies[i].getName())) {
                  result = SSOService(cookieValue);
                  if ("success".equals(result)) {
                     log.info("【通过单点登录服务器认证】");
                  } else {
                     log.info("【未通过单点登录服务器认证】COOKIES认证失败");
                     response.sendRedirect(ssoLoginPage);
                  }
              } else if ("sso".equals(diskCookies[i].getName())) {
                  HttpSession session =  request.getSession(); 
                  session.setAttribute("sso", cookieValue);
                  result = "success";
              }
           }
       } else {
           log.info("【权限系统-用户认证过滤器】未找到COOKIES");
       }
       
       // COOKIES效验,转到单点登录认证页面
       if (result.equals("failed")) {
           log.info("【权限系统-用户认证过滤器】COOKIES认证失败");
           response.sendRedirect(ssoLoginPage);
       // 效验成功并退出服务
       } else if (result.equals("logout")) {
           log.info("【权限系统-用户认证过滤器】退出登录");
           //logoutService(cookieValue);
           response.sendRedirect(ssoLoginPage);
       // 效验成功
       } else {
           log.info("【权限系统-用户认证过滤器】认证成功");
           Throwable problem = null;
           try {
              chain.doFilter(req, res);
           } catch (Throwable t) {
              problem = t;
              t.printStackTrace();
           }
           if (problem != null) {
              if (problem instanceof ServletException) {
                  throw ((ServletException) problem);
              }
              if (problem instanceof IOException) {
                  throw ((IOException) problem);
              }
              sendProcessingError(problem, res);
           }
       }
    }
    
    /**
     * 使用httpclient调用身份认证服务(SSOAuth)校验cookie
     * 
     * @param cookievalue
     * @return String 
     * @throws IOException
     */
    private String SSOService(String cookievalue) throws IOException {
       String result = "failed";
       String authAction = "/sso/authcookie.do?cookieName=";
       String tmp = this.ssoServiceURL + authAction + cookievalue;
       // 通过http协议来调用身份认证服务(SSOAuth)使用httpclient实现
       HttpClient httpclient = new HttpClient();
       GetMethod httpget = new GetMethod(tmp);
       httpget.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler());
       try {
           // 执行getMethod
           int statusCode = httpclient.executeMethod(httpget);
           if (statusCode != HttpStatus.SC_OK) {
              log.error("HttpClient Method failed: " + httpget.getStatusLine());
           }
           // 获取返回内容
           result = httpget.getResponseHeader("result").getValue();
       } catch (HttpException e) {
           // 发生致命的异常,可能是协议不对或者返回的内容有问题
           log.error("Please check your HttpClient address!");
           log.error(e.toString());
       } catch (IOException e) {
           // 发生网络异常
           log.error(e.toString());
       } finally {
           httpget.releaseConnection();
       }
       log.info("【权限系统-用户认证过滤器】校验返回result=" + result);
       return result;
    }

执行通过业务系统fileter将,根据不同的请求进行不同的操作。获取业务系统Cookie后同构使用HttpClient类方法调用单点认证中的authcookie方法,验证用户Cookie的合法性。

RealSysLoginAction.java类authcookie方法代码:

/**
     * 验证用户本地cookie值,如果通过验证则登录到业务系统总,如果没有通过验证,
     * 则跳转到单点登录系统认证页面。
     * @return result
     */
    public String authcookie(){
       try{
           String info = null;
           SysUser tmpUser = sysUserService.getSysUserById(getCookieName());
           if( tmpUser == null){
              response.setHeader("result", "failed");
              info  = LoggerUtil.getInfoMsg("未通过验证用户本地cookie值。");
           }else{
              response.setHeader("result", "success");
              info  = LoggerUtil.getInfoMsg("通过验证用户本地cookie值。");
           }
           //释放资源
           tmpUser = null;
           //打印日志
           log.info(info);
           return null;
       }catch(Exception ex){
           this.exceptionMessage.setError(ex.toString());
           this.exceptionMessage.setClassName(this.getClass().getName());
           this.exceptionMessage.setMessage("登录业务系统出现异常!");
           log.error(ex.toString());
           return ERROR;
       }
    }

此种登录方法要修改原有业务系统中的登录action方法。

 

业务系统LoginSSOAction.java类,loginSSO登录方法

public String loginSSO(){
       try {
           //获取认证系统传过来的用户名密码
           String sysUserCode = ActionContext.getContext().getSession().get("sso").toString();
           log.info("【权限系统】获取用户登录信息:[sysUserCode="+sysUserCode+"]");
           
           SysUser sysUser_ = this.loginService.findByPk(sysUserCode);
           //判断输入用户名是否正确
           if (sysUser_ != null) {
              //判断输入密码是否正确
                  // - 查找用户岗位集合
                  StringBuilder hql = new StringBuilder(300);
                  hql.append("SELECT sg.sysRole FROM SysUserGroup su, SysGroupRole sg WHERE su.id.userCode = '").append(sysUserCode)
                     .append("' AND su.id.groupCode = sg.id.groupCode AND sg.sysRole.roleType = '").append(AppConst.ROLE_TYPE_01).append("'");
                  List<SysRole> rolesList = this.sysRoleService.findSysRoleList(hql.toString());
                  // - 将功能性的岗位添加至集合,并排除不同岗位中的相同功能
                  // - 查找用户所有功能集合
                  Map<String, SysRole> funcPowersMap = new HashMap<String, SysRole>();
                  for (SysRole role : rolesList) {
                     funcPowersMap.put(role.getRoleCode(), role);
                  }
                  // - 组织UserSession
                  UserSession userSession = new UserSession();
                  userSession.setUserCode(sysUserCode);
                  userSession.setUserName(sysUser_.getUserName());
                  userSession.setComCode(sysUser_.getSysCompany().getComCode());
                  userSession.setComCname(sysUser_.getSysCompany().getComCname());
                  userSession.setAreaCode(sysUser_.getSysCompany().getSysArea().getAreaCode());
                  userSession.setComType(sysUser_.getSysCompany().getComType());
                  userSession.setComLevel(sysUser_.getSysCompany().getComLevel());
                  userSession.setFuncPowers(funcPowersMap);
                  //用户信息放入session
                  ActionContext.getContext().getSession().put(AppConst.USER_SESSION_ID, userSession);
                  //释放map引用
                  funcPowersMap = null;
                  //打印日志
                  StringBuffer infoBuffer = new StringBuffer();
                  infoBuffer.append("【用户登录】 机构代码: ");
                  infoBuffer.append(userSession.getComCode());
                  infoBuffer.append(" 用户代码: ");
                  infoBuffer.append(userSession.getUserCode());
                  infoBuffer.append(" (");
                  infoBuffer.append(userSession.getUserName());
                  infoBuffer.append(") 登录时间: ");
                  infoBuffer.append(new DateTime(DateTime.current(), DateTime.YEAR_TO_SECOND));
                  infoBuffer.append(" 登录成功!");
                  log.info(infoBuffer.toString());
                  return SUCCESS;
              } else {
                  this.exceptionMessage.setClassName(this.getClass().getName());
                  this.exceptionMessage.setMessage("密码不正确!");
                  log.info("密码不正确!");
                  return ERROR;
              }
       } catch (Exception e) {
           //返回异常信息
           this.exceptionMessage.setError(e.toString());
           this.exceptionMessage.setClassName(this.getClass().getName());
           this.exceptionMessage.setMessage("用户登录出现异常!");
           log.info(e.toString());
           return ERROR;
       }
    }

业务系统总filter 在web.xml文件中配置方法:

 <!-- 实现单点登录Fileter start-->
     <filter>
        <filter-name>SSOFilter</filter-name>
        <filter-class>SSO.SSOFilter</filter-class>
        <init-param>
            <param-name>cookieName</param-name>
            <param-value>hzpCookies</param-value>
        </init-param>
        <init-param>
            <param-name>ssoServiceURL</param-name>
            <param-value>http://192.168.61.124:8080/SSOAuth_v1.0.0/</param-value>
        </init-param>
        <init-param>
            <param-name>ssoLoginPage</param-name>
            <param-value>http://192.168.61.124:8080/SSOAuth_v1.0.0/login.jsp</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>SSOFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- 实现单点登录Fileter end-->

未通过认证,登录业务系统,跳转到单点登录认证。

1、  输入业务系统服务地址

2、  系统自动跳转到单点登录服务器,下边过程同上“直接登录单点登录认证系统流程”

其原理是通过filter实现类中dofilter方法进行过滤,判断用没有Cookie,则进行跳转到单点登录认证界面。

过滤代码如下:

// 检查HTTP请求的head是否有需要的cookie
       Cookie[] diskCookies = request.getCookies();
       if (diskCookies != null) {
           log.info("【权限系统-用户认证过滤器】找到COOKIES");
           for (int i = 0; i < diskCookies.length; i++) {
              cookieValue = diskCookies[i].getValue();
              log.info("【权限系统-用户认证过滤器】COOKIES中找到用户标识:" + diskCookies[i].getName() + "=" + cookieValue);
              if (SSO_AUTH.equals(diskCookies[i].getName())) {
                  result = SSOService(cookieValue);
                  if ("success".equals(result)) {
                     log.info("【通过单点登录服务器认证】");
                  } else {
                     log.info("【未通过单点登录服务器认证】COOKIES认证失败");
                     response.sendRedirect(ssoLoginPage);
                  }
              } else if ("sso".equals(diskCookies[i].getName())) {
                  HttpSession session =  request.getSession(); 
                  session.setAttribute("sso", cookieValue);
                  result = "success";
              }
           }
       } else {
           log.info("【权限系统-用户认证过滤器】未找到COOKIES");
       }
       
       // COOKIES效验,转到单点登录认证页面
       if (result.equals("failed")) {
           log.info("【权限系统-用户认证过滤器】COOKIES认证失败");
           response.sendRedirect(ssoLoginPage);
       // 效验成功并退出服务
       } else if (result.equals("logout")) {
           log.info("【权限系统-用户认证过滤器】退出登录");
           //logoutService(cookieValue);
           response.sendRedirect(ssoLoginPage);
       // 效验成功
       } else {
           log.info("【权限系统-用户认证过滤器】认证成功");
           Throwable problem = null;
           try {
              chain.doFilter(req, res);
           } catch (Throwable t) {
              problem = t;
              t.printStackTrace();
           }
           if (problem != null) {
              if (problem instanceof ServletException) {
                  throw ((ServletException) problem);
              }
              if (problem instanceof IOException) {
                  throw ((IOException) problem);
              }
              sendProcessingError(problem, res);
           }
       }

发表评论