历史版本8 :CAS单点登录插件 返回文档
编辑时间: 内容长度:图片数:目录数: 修改原因:

目录:

1. 概述编辑

1.1 问题描述

当用户想要将 FineReport 报表工程集成自己的系统时,可以在登录自己系统的同时登录报表平台。例如用户集成了 OA 系统和平台系统,想要在登录 OA 的同时就登录平台,此时需要使用到单点登录。单点登录的方法除了前端 Ajax 单点( Ajax 跨域异步单点登录 )以外,还可以使用后台登录的方式。做好后台登录后,在 OA 等其他系统访问报表平台或报表地址,将无需重复登录。

1.2 实现思路

通过在 web.xml 中增加拦截请求,Java 调用 FineReport 后台登录接口,来实现后台登录。

web.xml拦截 >> filter处理 >> FR后台认证通过 >> 访问地址

2. 操作方法编辑

2.1 web.xml

%TOMCAT_HOME%\webapps\webroot\WEB-INF目录下新建web.xml 文件,插入代码

注:可根据实际情况调整代码内容,比如只需要拦截PC平台地址,其余都不需要,删除相关filter-mapping即可

<?xml version="1.0" encoding="UTF-8"?>
<web-app
   xmlns="http://java.sun.com/xml/ns/j2ee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
   version="2.4">
  <display-name>Template WebApp</display-name>
   
    <mime-mapping>
  <extension>msi</extension>
  <mime-type>application/x-msi</mime-type>
  </mime-mapping>
     
 <filter>   
    <filter-name>Auther</filter-name>   
    <filter-class>com.fr.io.AuthFilter</filter-class>   
</filter>   
<filter-mapping>   
    <filter-name>Auther</filter-name>   
    <url-pattern>/decision/view/report</url-pattern>  //拦截report 
</filter-mapping> 
<filter-mapping>   
    <filter-name>Auther</filter-name>   
    <url-pattern>/decision/view/form</url-pattern>    //拦截form
</filter-mapping>
<filter-mapping>   
    <filter-name>Auther</filter-name>   
    <url-pattern>/decision</url-pattern>    //拦截pc平台
</filter-mapping>
<filter-mapping>   
    <filter-name>Auther</filter-name>   
    <url-pattern>/decision/url/mobile</url-pattern>  //拦截移动端平台
</filter-mapping> 
</web-app>

2.2 Java 代码

在 Java 中调用后台登录接口,有两种方式:cookie 和 session,根据实际情况选其一即可。

2.2.1 cookie

  • 用户名username:在代码中固定写死便于测试,实际应根据具体场景情况修改,动态获取。

package com.fr.io;

import com.fr.data.NetworkHelper;
import com.fr.decision.authority.data.User;
import com.fr.decision.mobile.terminal.TerminalHandler;
import com.fr.decision.webservice.exception.user.UserNotExistException;
import com.fr.decision.webservice.utils.DecisionServiceConstants;
import com.fr.decision.webservice.v10.login.LoginService;
import com.fr.decision.webservice.v10.login.TokenResource;
import com.fr.decision.webservice.v10.user.UserService;
import com.fr.general.ComparatorUtils;
import com.fr.log.FineLoggerFactory;
import com.fr.security.JwtUtils;
import com.fr.stable.StringUtils;
import com.fr.stable.web.Device;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;


public class AuthFilter implements Filter {


    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // username的获取,根据具体情况而定,测试情况下可写死用户名测试
        String username = "sysadmin";
        FineLoggerFactory.getLogger().info("username="+username);
        // 用户名参数不为空时,走登录逻辑
        if (StringUtils.isNotEmpty(username)) {
            // 获取该用户名的token
            String oldToken = TokenResource.COOKIE.getToken(request);  
            FineLoggerFactory.getLogger().info("oldToken=="+oldToken);
            // 无token/token失效,则走登录逻辑
            if (StringUtils.isEmpty(oldToken) || !checkTokenValid(request, oldToken, username)) {
                try {
                    // 系统中无该用户则抛出异常
                    User user = UserService.getInstance().getUserByUserName(username);
                    FineLoggerFactory.getLogger().info("user="+user);
                    if (user == null) {
                        throw new UserNotExistException();
                    }
                    // 将token写入response
                    String token = LoginService.getInstance().login(request, response, username);
                    // 将token写入request
                    request.setAttribute(DecisionServiceConstants.FINE_AUTH_TOKEN_NAME, token);
                    filterChain.doFilter(request,response);
                } catch (Exception e) {
                    FineLoggerFactory.getLogger().error(e.getMessage(), e);
                }
            }
            else {
                filterChain.doFilter(request,response);
            }
        } else {
            // 用户名参数为空,直接doFilter
            filterChain.doFilter(request,response);
        }
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }

    // token有效性检验
    private boolean checkTokenValid(HttpServletRequest req, String token, String currentUserName) {

        try {
            // 当前登录用户和token对应的用户名不同,需要重新生成token
            if (!ComparatorUtils.equals(currentUserName, JwtUtils.parseJWT(token).getSubject())) {
                FineLoggerFactory.getLogger().info("username changed:" + currentUserName);
                return false;
            }
            Device device = NetworkHelper.getDevice(req);
            // 判断该token是否还保存在状态服务器中,也就是判断该token是否还有用
            LoginService.getInstance().loginStatusValid(token, TerminalHandler.getTerminal(req, device));
            return true;
        } catch (Exception e) {
        }
        return false;
    }
}

注:如果遇到 IP 访问单点正常,域名访问立即提示登录信息失效,可能是因为 cookie 跨域了。映射的时候,域名跟 IP 不在同一个域,因为 cookie 是存在域下面的。这种场景,可以用 session 存放 Token。

2.2.2 session

  • 用户名username:在代码中固定写死便于测试,实际应根据具体场景情况修改,动态获取。

package com.fr.io;

import com.fr.data.NetworkHelper;
import com.fr.decision.authority.data.User;
import com.fr.decision.mobile.terminal.TerminalHandler;
import com.fr.decision.webservice.exception.user.UserNotExistException;
import com.fr.decision.webservice.utils.DecisionServiceConstants;
import com.fr.decision.webservice.v10.login.LoginService;
import com.fr.decision.webservice.v10.login.TokenResource;
import com.fr.decision.webservice.v10.user.UserService;
import com.fr.general.ComparatorUtils;
import com.fr.log.FineLoggerFactory;
import com.fr.security.JwtUtils;
import com.fr.stable.StringUtils;
import com.fr.stable.web.Device;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;


public class AuthFilter implements Filter {


    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // username的获取,根据具体情况而定,测试情况下可写死用户名测试
        String username = "sysadmin";
        FineLoggerFactory.getLogger().info("username="+username);
        // 用户名参数不为空时,走登录逻辑
        if (StringUtils.isNotEmpty(username)) {
            // 获取该用户名的token
         String oldToken = (String)request.getSession().getAttribute("fine_auth_token");
            FineLoggerFactory.getLogger().info("oldToken=="+oldToken);
            // 无token/token失效,则走登录逻辑
            if (StringUtils.isEmpty(oldToken) || !checkTokenValid(request, oldToken, username)) {
                try {
                    // 系统中无该用户则抛出异常
                    User user = UserService.getInstance().getUserByUserName(username);
                    FineLoggerFactory.getLogger().info("user="+user);
                    if (user == null) {
                        throw new UserNotExistException();
                    }
                    // 将token写入response
                    String token = LoginService.getInstance().login(request, response, username);
                    // 将token写入request
                    request.getSession().setAttribute("fine_auth_token", token);
                    filterChain.doFilter(request,response);
                } catch (Exception e) {
                    FineLoggerFactory.getLogger().error(e.getMessage(), e);
                }
            }
            else {
                filterChain.doFilter(request,response);
            }
            
        } else {
            // 用户名参数为空,直接doFilter
            filterChain.doFilter(request,response);
        }
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }

    // token有效性检验
    private boolean checkTokenValid(HttpServletRequest req, String token, String currentUserName) {

        try {
            // 当前登录用户和token对应的用户名不同,需要重新生成token
            if (!ComparatorUtils.equals(currentUserName, JwtUtils.parseJWT(token).getSubject())) {
                FineLoggerFactory.getLogger().info("username changed:" + currentUserName);
                return false;
            }
            Device device = NetworkHelper.getDevice(req);
            // 判断该token是否还保存在状态服务器中,也就是判断该token是否还有用
            LoginService.getInstance().loginStatusValid(token, TerminalHandler.getTerminal(req, device));
            return true;
        } catch (Exception e) {
        }
        return false;
    }
}

2.3 编译 class 文件

编译 Java 代码生成 class 文件,将文件放在%FR_HOME%\webapps\webroot\WEB-INF\classes\com\fr\io路径下,重启报表工程。

注:class 文件放置路径与 Java 包名有关,根据实际修改即可。

企业微信截图_15819206282641.png

2.4 访问格式

1)PC 端

  • 平台:http://ip:port/webroot/decision

  • 模板:http://ip:port/webroot/decision/view/report?viewlet=GettingStarted.cpt

2)移动端

  • 平台:http://ip:port/webroot/decision/url/mobile

  •  模板:http://ip:port/webroot/decision/view/report?viewlet=GettingStarted.cpt&op=h5

3.原理解释编辑

后台登录接口只需要用户名这一个参数,它的内部逻辑并不复杂:

1、首先会判断用户名是否为空

2、在cookie或session中获取token,判断是否是登录状态,并检查当前用户与登录状态用户是否为同一个

3、判断此用户是否在平台用户列表中

4、经过以上过程,就会根据该用户名生成一个token并且会先把该token写入response中然后再返回。


注1:username参数可以放在任何能在filter里取到的地方,可以放在url后面,可以放在querystring里,甚至可以放在cookie里,需要保证在filter里通过request获取到username。

注2:如果登录后浏览器依然跳转到平台登录页面,此时要看一下浏览器的cookie,看一下有没有fine_auth_token,如果没有那么很有可能是你的浏览器禁用了第三方cookie!