反饋已提交

網絡繁忙

當前為10.0版本文檔,更多實例內容將在最新幫助文檔中展現,點選跳轉至 最新版幫助文檔

后台登录-PC移动端通用

1. 概述

1.1 问题描述

当用户想要将 FineReport 报表工程集成自己的系统时,可以在登录自己系统的同时登录报表平台。例如用户集成了 OA 系统和平台系统,想要在登录 OA 的同时就登录平台,此时需要使用到单点登录。

单点登录的方法除了前端 Ajax 单点( Ajax 跨域异步单点登录 )以外,还可以使用后台登录的方式。做好后台登录后,在 OA 等其他系统访问报表平台或报表地址,将无需重复登录。

1.2 实现思路

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

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

注:本文提供的后台登录方法只验证用户名。

2. 原理解释

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

  • 首先判断用户名是否为空。

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

  • 判断此用户是否在平台用户列表中。

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

3. 操作方法

3.1 环境准备

部署到 Tomcat 上的工程,Tomcat 服务器部署请参见:独立部署 

3.2 新建 web.xml 文件

1)新建 web.xml 文件,代码如下所示:

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

注2:web.xml 中<url-pattern>/decision/*</url-pattern>之间的 decision 需要改成实际工程名。

<?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)将 web.xml 文件放到%Tomcat_HOME%\webapps\webroot\WEB-INF下,如下图所示:

1600331499993719.png

3.3 新建 Java 文件

在 Java 中调用后台登录接口,有两种方式:cookie 和 session,根据实际情况选其一即可,将新建的 Java 文件命名为 AuthFilter.java

3.3.1 方式一: cookie

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 {

    // 携带用户名的参数名称,不能使用username,会存在冲突
    private static final String userNameParameter = "fr_username";

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

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // 这个地方username的获取,根据具体你请求的时候把username放在哪了来确定
        String username = request.getParameter(userNameParameter);
        //String username = "sysadmin";
        // 用户名参数不为空时,走登录逻辑
        if (StringUtils.isNotEmpty(username)) {
            // 获取该用户名的token
            String oldToken = TokenResource.COOKIE.getToken(request);
            // 无token/token失效,则走登录逻辑
            if (StringUtils.isEmpty(oldToken) || !checkTokenValid(request, oldToken, username)) {
                try {
                    // 系统中无该用户则抛出异常
                    User user = UserService.getInstance().getUserByUserName(username);
                    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);
                } catch (Exception e) {
                    FineLoggerFactory.getLogger().error(e.getMessage(), e);
                }
            }
            // 检查用户名参数是否位于URL,是的话去掉并重定向,以隐藏URL中的用户名
            if (StringUtils.equals(request.getMethod(), "GET") && request.getQueryString()!=null && request.getQueryString().contains(userNameParameter)) {
                response.sendRedirect(generateURIWithoutUsername(request));
            } else {
                // 不是位于URL的话直接doFilter
                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;
    }

    // 去除URL参数中的用户名参数
    private String generateURIWithoutUsername(HttpServletRequest request) {

        List<String> queryParams = Arrays.asList(request.getQueryString().split("&"));
        if (!queryParams.isEmpty()) {
            StringBuilder queryStringWithoutUsername = new StringBuilder("?");
            for (String param : queryParams) {
                if (!StringUtils.contains(param, userNameParameter)) {
                    queryStringWithoutUsername.append(param);
                    queryStringWithoutUsername.append("&");
                }
            }
            String newUrl = queryStringWithoutUsername.toString();
            return request.getRequestURI() + newUrl.substring(0, newUrl.length()-1);
        } else {
            return request.getRequestURI();
        }
    }
}

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

3.3.2 方式二:session

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.login.LogInOutResultInfo;
import com.fr.decision.webservice.v10.login.LoginService;
import com.fr.decision.webservice.v10.login.event.LogInOutEvent;
import com.fr.decision.webservice.v10.user.UserService;
import com.fr.event.Event;
import com.fr.event.EventDispatcher;
import com.fr.event.Listener;
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 javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/** * desc * * @author Anner * created on 2020-12-07 */
public class AuthFilter implements Filter {    // 携带用户名的参数名称,不能使用username,会存在冲突
   private static final String userNameParameter = "fr_username";
       private static final String TOKEN = "fine_auth_token";
           @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";        
                   String username = request.getParameter(userNameParameter);        
                   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);                
                          } catch (Exception e) {                    
                               FineLoggerFactory.getLogger().error(e.getMessage(), e);                
                               }            
                    }            
                    // 检查用户名参数是否位于URL,是的话去掉并重定向,以隐藏URL中的用户名            
                    if (StringUtils.equals(request.getMethod(), "GET") && request.getQueryString() != null && request.getQueryString().contains(userNameParameter)) {
                          response.sendRedirect(generateURIWithoutUsername(request));
                    } else {                
                    // 不是位于URL的话直接doFilter                
                    filterChain.doFilter(request, response);            
                    }        
                    } else {            
                    // 用户名参数为空,直接doFilter            
                    filterChain.doFilter(request, response);        
                    }    
                    }    
                    private String generateURIWithoutUsername(HttpServletRequest request) {        
                    List<String> queryParams = Arrays.asList(request.getQueryString().split("&"));        
                    if (!queryParams.isEmpty()) {            
                        StringBuilder queryStringWithoutUsername = new StringBuilder("?");            
                        for (String param : queryParams) {                
                        if (!StringUtils.contains(param, userNameParameter)) {                    
                            queryStringWithoutUsername.append(param);                    
                            queryStringWithoutUsername.append("&");                
                            }            
                        }            
                        String newUrl = queryStringWithoutUsername.toString();            
                        return request.getRequestURI() + newUrl.substring(0, newUrl.length()-1);        
                        } else {            
                            return request.getRequestURI();        
                        }    
                        }    
                        @Override    
                        public void init(FilterConfig filterConfig) throws ServletException {        
                            registerLogoutEvent();    
                        }    
                        @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;    
                                }    
                                private void registerLogoutEvent() {        
                                    EventDispatcher.listen(LogInOutEvent.LOGOUT, new Listener<LogInOutResultInfo>() {            
                                        @Override            
                                        public void on(Event event, LogInOutResultInfo resultInfo) {                
                                            HttpSession session = resultInfo.getRequest().getSession(false);          
                                                if (session != null && StringUtils.isNotEmpty((String) session.getAttribute(TOKEN))) {
                                                    session.removeAttribute(TOKEN);                
                                                }          
                                        }        
                                    });    
                                }}

3.3.3 代码说明

cookie 和 session 的代码中,下图红框中的代码为定义参数名,「fr_username」是用户名的参数名,并不是实际用户名称。所以在本文 3.5 节效果查看中,访问路径格式为:http://ip:端口/工程名/decision?用户参数名=用户名称,例如:http://192.168.43.69:8080/webroot/decision?fr_username=1

注:用户参数名不能为 username ,会存在冲突。

37.png

3.4 编译 class 文件

3.4.1 导入 FineReport 包

具体导入的包请参见:编译Java程序

3.4.2 编译 Java 文件

1)编译 Java 代码生成 class 文件,如下图所示:

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

34.png

2)将编译后的 class 文件放在%Tomcat_HOME%\webapps\webroot\WEB-INF\classes\com\fr\io路径下,重启工程。

1600332254725479.png

3.5 效果查看

3.5.1 访问格式

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

当单点配置完成,从用户系统访问以上相关地址(web.xml配置拦截)可实现免登录

注:模板认证默认关闭,所以访问模板地址默认不需要登录,如果验证效果,需开启模板认证。

3.5.2 效果示例

PC 端:

访问http://192.168.43.69:8080/webroot/decision?fr_username=1,无需输入账号和密码,直接进入平台。如下图所示:

0.gif

用户可在另一个工程中挂载该链接:http://192.168.43.69:8080/webroot/decision?fr_username=1,访问该链接时无需登录。

移动端:

效果如下图所示:

2.gif

4. 注意事项

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

33.png

2)做了单点登录后,若发现插件管理无法正常安装插件,原因可能是插件管理的请求被重定向到登录页面,需要在单点登录过滤器配置处把/webroot/decision/v10/plugin/disk/installation的请求放行。即在 3.2 节 web.xml 文件中添加decision/v10/plugin/disk/installation路径的拦截。

附件列表


主題: 原简体文档
已經是第一篇
已經是最後一篇
  • 有幫助
  • 沒幫助
  • 只是瀏覽
  • 圖片不清晰
  • 用語看不懂
  • 功能說明看不懂
  • 操作說明太簡單
  • 內容有錯誤

文 檔回 饋

滑鼠選中內容,快速回饋問題

滑鼠選中存在疑惑的內容,即可快速回饋問題,我們將會跟進處理。

不再提示

10s後關閉