1. 概述编辑
1.1 问题描述
当用户想要将 FineReport 报表工程集成自己的系统时,可以在登录自己系统的同时登录报表平台。例如用户集成了 OA 系统和平台系统,想要在登录 OA 的同时就登录平台,此时需要使用到单点登录。单点登录的方法除了前端 Ajax 单点( Ajax 跨域异步单点登录 )以外,还可以使用后台登录的方式。做好后台登录后,在 OA 等其他系统访问报表平台或报表地址,将无需重复登录。
1.2 实现思路
通过在 web.xml 中增加拦截请求,Java 调用 FineReport 后台登录接口,来实现后台登录。
web.xml拦截 >> filter处理 >> FR后台认证通过 >> 访问地址
2. 操作方法编辑
2.1 web.xml
在 web.xml 中增加 FineReport 相关地址的拦截代码:
<?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
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
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 包名有关,根据实际修改即可。
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