From 6aa8f9ae61128e8b7ceb55f0343310da66a77690 Mon Sep 17 00:00:00 2001 From: JEECG <445654970@qq.com> Date: Fri, 26 Sep 2025 12:18:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=87=E7=BA=A7springboot3=E3=80=81=E6=9D=83?= =?UTF-8?q?=E9=99=90=E9=87=87=E7=94=A8sa-token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +- jimureport-example/README.md | 8 + jimureport-example/pom.xml | 33 ++-- .../{modules => }/JimuReportApplication.java | 7 +- .../config/ApiSecurityConfigFilter.java | 35 ---- .../config/CustomCorsConfiguration.java | 18 -- .../config/CustomLoginSuccessHandler.java | 53 ------ .../config/JimuReportSysSwitchFilter.java | 61 ------- .../jmreport/config/SpringSecurityConfig.java | 111 ------------ .../jmreport/config/ViewPageCustomAccess.java | 43 ----- .../jmreport/controller/IndexController.java | 21 --- .../jmreport/controller/LoginController.java | 89 ++++++++++ .../extend/JimuDragExternalServiceImpl.java | 3 +- .../extend/JimuReportTokenServiceImpl.java | 87 +++++++--- .../jmreport/satoken/SaTokenConfigure.java | 85 +++++++++ .../jmreport/satoken/StpInterfaceImpl.java | 44 +++++ .../{ => satoken}/config/RedisConfig.java | 8 +- .../satoken/config/SecurityConfig.java | 40 +++++ .../jmreport/satoken/config/vo/User.java | 20 +++ .../jmreport/satoken/exception/AjaxJson.java | 162 ++++++++++++++++++ .../satoken/exception/GlobalException.java | 53 ++++++ .../exception/GlobalExceptionHandler.java | 38 ++++ .../satoken/util/AjaxRequestUtils.java | 79 +++++++++ .../src/main/resources/application-dev.yml | 12 +- .../src/main/resources/application-prod.yml | 12 +- .../src/main/resources/application.yml | 19 +- .../main/resources/static/login/login.html | 2 +- 27 files changed, 752 insertions(+), 401 deletions(-) rename jimureport-example/src/main/java/com/jeecg/{modules => }/JimuReportApplication.java (90%) delete mode 100644 jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/ApiSecurityConfigFilter.java delete mode 100644 jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/CustomCorsConfiguration.java delete mode 100644 jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/CustomLoginSuccessHandler.java delete mode 100644 jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/JimuReportSysSwitchFilter.java delete mode 100644 jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/SpringSecurityConfig.java delete mode 100644 jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/ViewPageCustomAccess.java delete mode 100644 jimureport-example/src/main/java/com/jeecg/modules/jmreport/controller/IndexController.java create mode 100644 jimureport-example/src/main/java/com/jeecg/modules/jmreport/controller/LoginController.java create mode 100644 jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/SaTokenConfigure.java create mode 100644 jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/StpInterfaceImpl.java rename jimureport-example/src/main/java/com/jeecg/modules/jmreport/{ => satoken}/config/RedisConfig.java (86%) create mode 100644 jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/config/SecurityConfig.java create mode 100644 jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/config/vo/User.java create mode 100644 jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/exception/AjaxJson.java create mode 100644 jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/exception/GlobalException.java create mode 100644 jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/exception/GlobalExceptionHandler.java create mode 100644 jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/util/AjaxRequestUtils.java diff --git a/README.md b/README.md index 5db1c38..7595039 100644 --- a/README.md +++ b/README.md @@ -40,17 +40,17 @@ v2.1.3 | 2025-09-05 快速集成积木报表 ----------------------------------- -> 快速集成到自己项目中,支持SpringBoot脚手架项目,springboot2依赖采用jdk8编译,支持jdk8、jdk17、jdk21+。 +> 快速集成到自己项目中,支持SpringBoot3脚手架项目(要求jdk17+),springboot2版本要求jdk17+ #### 第一步:引入积木报表依赖 -- springboot2 +- springboot3 ``` org.jeecgframework.jimureport - jimureport-spring-boot-starter + jimureport-spring-boot3-starter 2.1.3 @@ -67,12 +67,12 @@ v2.1.3 | 2025-09-05 ``` -- springboot3 +- springboot2 ``` org.jeecgframework.jimureport - jimureport-spring-boot3-starter-fastjson2 + jimureport-spring-boot-starter 2.1.3 diff --git a/jimureport-example/README.md b/jimureport-example/README.md index 38cdee2..3443e97 100644 --- a/jimureport-example/README.md +++ b/jimureport-example/README.md @@ -8,6 +8,14 @@ +环境要求 +----------------------------------- + +- 要求jdk17+(本项目springboot3架构) +- 要求mysql5.7+ 手工执行db/jimureport.mysql5.7.create.sql,会自动创建库jimureport +- 要求redis +- 项目配置:src/main/resources/application-dev.yml + 使用步骤 ----------------------------------- diff --git a/jimureport-example/pom.xml b/jimureport-example/pom.xml index 1c7d218..957e3a2 100644 --- a/jimureport-example/pom.xml +++ b/jimureport-example/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.18 + 3.5.5 @@ -45,12 +45,12 @@ - 1.8 + 17 8.0.3 8.0.27 - 4.6 + 4.9 @@ -64,7 +64,7 @@ org.jeecgframework.jimureport - jimureport-spring-boot-starter + jimureport-spring-boot3-starter 2.1.3 @@ -82,17 +82,16 @@ org.jeecgframework.jimureport - jimubi-spring-boot-starter - 2.1.3 + jimubi-spring-boot3-starter + 2.1.3.1 - + com.github.jsqlparser jsqlparser ${jsqlparser.version} - org.springframework.boot @@ -103,10 +102,22 @@ spring-boot-starter-freemarker - + - org.springframework.boot - spring-boot-starter-security + cn.dev33 + sa-token-spring-boot3-starter + 1.44.0 + + + + cn.dev33 + sa-token-redis-jackson + 1.44.0 + + + + org.apache.commons + commons-pool2 diff --git a/jimureport-example/src/main/java/com/jeecg/modules/JimuReportApplication.java b/jimureport-example/src/main/java/com/jeecg/JimuReportApplication.java similarity index 90% rename from jimureport-example/src/main/java/com/jeecg/modules/JimuReportApplication.java rename to jimureport-example/src/main/java/com/jeecg/JimuReportApplication.java index 4c5dd44..3b25e1b 100644 --- a/jimureport-example/src/main/java/com/jeecg/modules/JimuReportApplication.java +++ b/jimureport-example/src/main/java/com/jeecg/JimuReportApplication.java @@ -1,7 +1,6 @@ -package com.jeecg.modules; +package com.jeecg; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext; @@ -10,8 +9,7 @@ import org.springframework.core.env.Environment; /** * 积木报表独立服务启动类 */ -@SpringBootApplication(scanBasePackages = {"org.jeecg", "com.jeecg"}) -@EnableAutoConfiguration(exclude={MongoAutoConfiguration.class}) +@SpringBootApplication(scanBasePackages = {"org.jeecg", "com.jeecg"}, exclude = {MongoAutoConfiguration.class}) public class JimuReportApplication { public static void main(String[] args) { @@ -24,6 +22,7 @@ public class JimuReportApplication { // 默认编码不是UTF-8设置为UTF-8 System.setProperty("file.encoding", "UTF-8"); } + System.out.println("\n----------------------------------------------------------\n\t" + "JimuReport 积木报表平台 is running! Access URL:\n\t" + "报表工作台: \t\thttp://localhost:" + port + path + "/jmreport/list\n\t" + diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/ApiSecurityConfigFilter.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/ApiSecurityConfigFilter.java deleted file mode 100644 index e0351de..0000000 --- a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/ApiSecurityConfigFilter.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.jeecg.modules.jmreport.config; - -import com.alibaba.fastjson.JSONObject; -import org.jeecg.modules.jmreport.common.util.OkConvertUtils; -import org.springframework.security.core.context.SecurityContextImpl; - -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; -import java.io.IOException; - -/** -* @Description: api访问权限过滤器 -* -* @author: wangshuai -* @date: 2024/9/25 下午6:22 -*/ -public class ApiSecurityConfigFilter implements Filter { - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - HttpServletRequest req = (HttpServletRequest) request; - String loginFrom = req.getHeader("jm_login_from"); - if(null != loginFrom && !loginFrom.isEmpty()){ - String springSecurityContext = req.getHeader("jm_spring_security_context"); - if(null != springSecurityContext && !springSecurityContext.isEmpty()){ - SecurityContextImpl securityContext = JSONObject.parseObject(springSecurityContext, SecurityContextImpl.class); - HttpSession session = req.getSession(); - session.setAttribute("loginFrom", loginFrom); - session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext); - } - } - chain.doFilter(request, response); - } -} diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/CustomCorsConfiguration.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/CustomCorsConfiguration.java deleted file mode 100644 index 8a3e365..0000000 --- a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/CustomCorsConfiguration.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.jeecg.modules.jmreport.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class CustomCorsConfiguration implements WebMvcConfigurer { - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**") // 所有接口 - .allowCredentials(true) // 是否发送 Cookie - .allowedOriginPatterns("*") // 支持域 - .allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"}) // 支持方法 - .allowedHeaders("*") - .exposedHeaders("*"); - } -} \ No newline at end of file diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/CustomLoginSuccessHandler.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/CustomLoginSuccessHandler.java deleted file mode 100644 index 47c1092..0000000 --- a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/CustomLoginSuccessHandler.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.jeecg.modules.jmreport.config; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; -import org.springframework.security.web.savedrequest.SavedRequest; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; - -/** - * 自定义springsecurity登录成功处理 - * [TV360X-1884]jimureport-example集成简单的 spring security,设置登录账号密码 - * @author chenrui - * @date 2024/8/2 16:26 - */ -@Slf4j -public class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { - - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException, ServletException { - HttpSession session = request.getSession(); - session.setAttribute("loginFrom", "jimu_example"); - - SavedRequest savedRequest = (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST"); - String redirectUrl = savedRequest != null ? savedRequest.getRedirectUrl() : null; - - if (redirectUrl != null) { - try { - URI uri = new URI(redirectUrl); - String path = uri.getPath(); // 提取路径部分 - - if (path != null && path.startsWith("/jmreport")) { - // 路径符合要求,执行默认跳转 - super.onAuthenticationSuccess(request, response, authentication); - return; - } - } catch (URISyntaxException e) { - // URL解析失败,打印日志后继续执行默认跳转 - log.error(e.getMessage(), e); - } - } - - // 不符合 /jmreport 开头,或 redirectUrl 为 null 或异常 - response.sendRedirect("/"); - } -} \ No newline at end of file diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/JimuReportSysSwitchFilter.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/JimuReportSysSwitchFilter.java deleted file mode 100644 index 56d6da9..0000000 --- a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/JimuReportSysSwitchFilter.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.jeecg.modules.jmreport.config; - -import org.springframework.stereotype.Component; - -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; -import java.io.IOException; - -/** - * 积木报表子系统切换处理类 - * @author chenrui - * @date 2024/10/31 11:33 - */ -@Component("jimuReportSwitchSysHandler") -public class JimuReportSysSwitchFilter implements Filter { - - // 包含积木报表 - boolean includeJimuReport = false; - // 包含积木仪表盘 - boolean includeJimuDrag = false; - - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - Filter.super.init(filterConfig); - // 判断是否包含积木报表 - try { - Class.forName("org.jeecg.modules.jmreport.config.JmReportBaseConfig"); - includeJimuReport = true; - } catch (ClassNotFoundException ignored) { - } - // 判断是否包含积木仪表盘 - try { - Class.forName("org.jeecg.modules.drag.service.IOnlDragCompService"); - includeJimuDrag = true; - } catch (ClassNotFoundException ignored) { - } - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - if (request instanceof HttpServletRequest) { - HttpServletRequest httpRequest = (HttpServletRequest) request; - HttpSession session = httpRequest.getSession(); - if(includeJimuReport) { - session.setAttribute("switchJimuReport", "true"); - }else{ - session.setAttribute("switchJimuReport", "false"); - } - if(includeJimuDrag) { - session.setAttribute("switchJimuDrag", "true"); - }else { - session.setAttribute("switchJimuDrag", "false"); - } - } - chain.doFilter(request, response); - } - - -} diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/SpringSecurityConfig.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/SpringSecurityConfig.java deleted file mode 100644 index 9c450a4..0000000 --- a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/SpringSecurityConfig.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.jeecg.modules.jmreport.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; -import org.springframework.security.web.firewall.StrictHttpFirewall; - -/** - * spring security 配置 - * [TV360X-1884]jimureport-example集成简单的 spring security,设置登录账号密码 - * @Author chenrui - * @Date 2024-07-23 - */ -@Configuration -@EnableWebSecurity -public class SpringSecurityConfig { - - - @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - http.csrf().disable() - .authorizeRequests() - .antMatchers("/login/**").permitAll() - // 放过静态资源 - .antMatchers("/jmreport/**/cdn/**", - "/jmreport/desreport_/**/*.js", - "/jmreport/desreport_/**/*.css", - "/jmreport/desreport_/**/*.ico", - "/jmreport/desreport_/**/*.png").permitAll() - // 放过静态资源-仪表盘 - .antMatchers("/drag/lib/**/*.css", - "/drag/lib/**/*.js", - "/drag/lib/**/*.png", - "/drag/**/*.ico").permitAll() - // 不需要登录的接口 - .antMatchers("/jmreport/excelQueryByTemplate", - "/jmreport/query/report/folder/template", - "/jmreport/img/**", - "/jmreport/download/image", - "/jmreport/verificationToken", - "/jmreport/link/queryByIds", - "/jmreport/test/getUserMsg", - "/jmreport/test/getOrder", - "/jimureport/test/**", - "/jmreport/auto/export/download/**", - "/jmreport/auto/export", - "/jmreport/qurestSql", - "/jmreport/qurestApi").permitAll() - // 分享页面 - .antMatchers("/jmreport/shareView/**", - "/jmreport/exportPdfStream", - "/jmreport/exportAllExcelStream", - "/jmreport/checkParam/**", - "/jmreport/share/verification", - "/jmreport/getQueryInfo", - "/jmreport/show", - "/jmreport/form/submit", - "/jmreport/form/repeat/check/**", - "/jmreport/exportReport", - "/jmreport/dictCodeSearch", - "/jmreport/query/multiple/initValue", - "/jmreport/addViewCount/**").permitAll() - // 仪表盘分享页面 - .antMatchers("/jimubi/share/view/**", - "/drag/share/view/**", - "/drag/page/queryById", - "/drag/page/addVisitsNumber", - "/drag/page/queryTemplateList", - "/drag/onlDragDatasetHead/getAllChartData", - "/drag/onlDragDatasetHead/getTotalData", - "/drag/onlDragDatasetHead/getDictByCodes", - "/drag/mock/json/**", - "/drag/getImageBase64/**").permitAll() - // view页面 - .antMatchers("/jmreport/view/**").access("@viewPageCustomAccess.check(request,authentication)") - .anyRequest().authenticated() - .and() - .formLogin() - .loginPage("/login/login.html") - .loginProcessingUrl("/login") - .successHandler(new CustomLoginSuccessHandler()) - .permitAll().and() - .addFilterBefore(new ApiSecurityConfigFilter(), BasicAuthenticationFilter.class) - .logout() - .invalidateHttpSession(true) - .clearAuthentication(true).permitAll(); - // 开放iframe访问限制 - http.headers().frameOptions().disable(); - // 禁用默认的 no-cache - http.headers().cacheControl().disable(); - http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS); - http.rememberMe().useSecureCookie(true); - - return http.build(); - } - - @Bean - public WebSecurityCustomizer webSecurityCustomizer() { - StrictHttpFirewall firewall = new StrictHttpFirewall(); - // 允许反斜杠 - firewall.setAllowBackSlash(true); - // 允许双反斜杠 - firewall.setAllowUrlEncodedDoubleSlash(true); - return (web) -> web.httpFirewall(firewall); - } -} diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/ViewPageCustomAccess.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/ViewPageCustomAccess.java deleted file mode 100644 index 3ef7ac8..0000000 --- a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/ViewPageCustomAccess.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.jeecg.modules.jmreport.config; - -import org.jeecg.modules.jmreport.common.util.OkConvertUtils; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; - -/** - * 自定义view页面access处理 - * for: [TV360X-2206] 目前这个版本必须登录后才能看报表,如何设置不登录也能查看报表 #2919 - * @author chenrui - * @date 2024/8/23 14:28 - */ -@Component("viewPageCustomAccess") -public class ViewPageCustomAccess { - - @Value("${spring.security.open-view-page:false}") - boolean openViewPage = false; - - public boolean check(HttpServletRequest request, Authentication authentication) { - Object principal = authentication.getPrincipal(); - if (null == principal || principal.toString().isEmpty() || "anonymousUser".equalsIgnoreCase(principal.toString())) { - // 未登录 - if (openViewPage) { - // 配置文件设置了开放view页面 - return true; - } - HttpServletRequest httpRequest = (HttpServletRequest) request; - String previousPage = httpRequest.getParameter("previousPage"); - String jmLink = httpRequest.getParameter("jmLink"); - if (null != previousPage && !previousPage.isEmpty() - && null != jmLink && !jmLink.isEmpty()) { - // 参数中有previousPage和jmLink - return true; - } - return false; - } - return true; - } -} - diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/controller/IndexController.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/controller/IndexController.java deleted file mode 100644 index bf24234..0000000 --- a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/controller/IndexController.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.jeecg.modules.jmreport.controller; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; - -/** - * 积木报表-设置默认首页跳转 - */ -@Controller -public class IndexController { - private Logger logger = LoggerFactory.getLogger(IndexController.class); - - @GetMapping("/") - public String index(Model model) { - model.addAttribute("name", "jimureport"); - return "jmreport/list"; // 视图重定向 - 跳转 - } -} \ No newline at end of file diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/controller/LoginController.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/controller/LoginController.java new file mode 100644 index 0000000..38c9c43 --- /dev/null +++ b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/controller/LoginController.java @@ -0,0 +1,89 @@ +package com.jeecg.modules.jmreport.controller; + +import cn.dev33.satoken.stp.StpUtil; +import com.jeecg.modules.jmreport.satoken.config.SecurityConfig; +import com.jeecg.modules.jmreport.satoken.util.AjaxRequestUtils; +import jakarta.servlet.http.HttpSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.net.URLEncoder; + +/** + * 积木报表-设置默认首页跳转 + */ +@Controller +public class LoginController { + private Logger logger = LoggerFactory.getLogger(LoginController.class); + public final static String LOGIN_PAGE = "/login/login.html"; + + @Autowired + SecurityConfig securityConfig; + + /** + * 登录请求 + * + * @param username + * @param password + * @param req + * @return + */ + @GetMapping("/doLogin") + public String login(@RequestParam String username, @RequestParam String password, jakarta.servlet.http.HttpServletRequest req) { + logger.info("登录请求,用户名:" + username + ",密码:" + password); + // 此处通过yml配置文件获取登录账号和密码,实际项目中请从数据库读取进行验证 + if (securityConfig.getUser().getName().equals(username) && securityConfig.getUser().getPassword().equals(password)) { + StpUtil.login(securityConfig.getUser().getName()); + logger.info("登录成功,当前会话tokeName={}, tokenValue={}", StpUtil.getTokenName(), StpUtil.getTokenValue()); + + // 设置登录来源,方便退出登录时区分 + AjaxRequestUtils.setLoginSessionInfo(); + return "jmreport/list"; + }else{ + logger.error("登录失败,用户名或密码错误"); + // 返回密码错误提示,需进行URL编码 + return "redirect:" + LOGIN_PAGE + "?error=1"; + } + } + + + /** + * 首页跳转 + * + * @param model + * @return + */ + @GetMapping("/") + public String index(Model model) { + model.addAttribute("name", "jimureport"); + return "jmreport/list"; // 视图重定向 - 跳转 + } + + /** + * 查询登录状态 + * + * @return + */ + @RequestMapping("/isLogin") + public String isLogin() { + logger.info("查询登录状态:{}", StpUtil.getTokenInfo()); + return "当前会话是否登录:" + StpUtil.isLogin(); + } + + /** + * 退出登录 + * + * @return + */ + @RequestMapping("/logout") + public String logout() { + StpUtil.logout(); + return "redirect:" + LOGIN_PAGE; + } +} \ No newline at end of file diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/extend/JimuDragExternalServiceImpl.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/extend/JimuDragExternalServiceImpl.java index d9a5029..d623476 100644 --- a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/extend/JimuDragExternalServiceImpl.java +++ b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/extend/JimuDragExternalServiceImpl.java @@ -30,9 +30,8 @@ import java.util.Map; @Component public class JimuDragExternalServiceImpl implements IOnlDragExternalService { - - @Autowired @Lazy + @Autowired private IJimuReportDictService reportDictService; /** diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/extend/JimuReportTokenServiceImpl.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/extend/JimuReportTokenServiceImpl.java index 9f0420e..cf5258f 100644 --- a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/extend/JimuReportTokenServiceImpl.java +++ b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/extend/JimuReportTokenServiceImpl.java @@ -1,22 +1,32 @@ package com.jeecg.modules.jmreport.extend; +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.stp.StpUtil; +import com.jeecg.modules.jmreport.satoken.config.SecurityConfig; +import com.jeecg.modules.jmreport.satoken.util.AjaxRequestUtils; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.jeecg.modules.jmreport.api.JmReportTokenServiceI; import org.jeecg.modules.jmreport.common.constant.JmConst; +import org.jeecg.modules.jmreport.common.expetion.JimuReportException; import org.jeecg.modules.jmreport.common.util.JimuSpringContextUtils; import org.jeecg.modules.jmreport.common.util.OkConvertUtils; -import org.springframework.http.HttpHeaders; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import javax.servlet.http.HttpServletRequest; - /** * 自定义积木报表鉴权(如果不进行自定义,则所有请求不做权限控制) * 1.自定义获取登录token * 2.自定义获取登录用户 */ +@Slf4j @Component public class JimuReportTokenServiceImpl implements JmReportTokenServiceI { - + @Autowired + SecurityConfig securityConfig; + /** * 通过请求获取Token * @param request @@ -24,9 +34,15 @@ public class JimuReportTokenServiceImpl implements JmReportTokenServiceI { */ @Override public String getToken(HttpServletRequest request) { - //System.out.println("---------call---------getToken-----------------------"); - //return TokenUtils.getTokenByRequest(request); - return "123456"; + String token = StpUtil.getTokenValue(); + log.debug("------SA--TOKEN-----RequestPath={} ,GET Token = {}", SaHolder.getRequest().getRequestPath(), token); + if(StringUtils.isEmpty(token)){ + token = request.getParameter("token"); + // 将URL上的token设置到SaToken上下文,方便后续操作 + log.info("------SA--Init--TOKEN-----RequestPath={} ,从URL参数获取Token = {}", SaHolder.getRequest().getRequestPath(), token); + StpUtil.setTokenValue(token); + } + return token; } /** @@ -36,8 +52,9 @@ public class JimuReportTokenServiceImpl implements JmReportTokenServiceI { */ @Override public String getUsername(String token) { - // return JwtUtil.getUsername(token); - return "admin"; + String username = StpUtil.getLoginIdAsString(); + log.debug("------SA--TOKEN-----RequestPath={} ,Token={} , LoginId={}", SaHolder.getRequest().getRequestPath(), token, username); + return username; } /** @@ -75,25 +92,46 @@ public class JimuReportTokenServiceImpl implements JmReportTokenServiceI { */ @Override public Boolean verifyToken(String token) { - //System.out.println("---------verify-----Token---------------"); - //return TokenUtils.verifyToken(token, sysBaseAPI, redisUtil); + try { + if(securityConfig.getEnable()!=null && !securityConfig.getEnable()){ + // 如果security.enable=false,则不进行登录校验 + return true; + } + StpUtil.checkLogin(); + log.debug("--SaToken verifyToken-成功!RequestPath={},Token = {}", SaHolder.getRequest().getRequestPath(), token); + } catch (Exception e) { + log.warn("Token校验失败: token = {},error:{}", token, e.getMessage()); + + if(e instanceof NotLoginException){ + // 跳转登录页面 + try { + if(!AjaxRequestUtils.isAjaxRequest(JimuSpringContextUtils.getHttpServletRequest())){ + JimuSpringContextUtils.getHttpServletResponse().sendRedirect("/login/login.html"); + } + } catch (Exception ex) { + } + return false; + }else{ + throw new JimuReportException(e); + } + } return true; } - /** - * 自定义请求头 - * @return - */ - @Override - public HttpHeaders customApiHeader() { - HttpHeaders header = new HttpHeaders(); - header.add("custom-header1", "Please set a custom value 1"); - header.add("token", "token value 2"); - return header; - } +// /** +// * 自定义请求头 +// * @return +// */ +// @Override +// public HttpHeaders customApiHeader() { +// HttpHeaders header = new HttpHeaders(); +// header.add("custom-header1", "Please set a custom value 1"); +// header.add("token", "token value 2"); +// return header; +// } /** - * 自定义获取租户 + * 自定义租户 * * @return */ @@ -110,7 +148,6 @@ public class JimuReportTokenServiceImpl implements JmReportTokenServiceI { headerTenantId = request.getParameter(JmConst.TENANT_ID); } } - //return headerTenantId; - return null; + return headerTenantId; } } \ No newline at end of file diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/SaTokenConfigure.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/SaTokenConfigure.java new file mode 100644 index 0000000..8aa6210 --- /dev/null +++ b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/SaTokenConfigure.java @@ -0,0 +1,85 @@ +package com.jeecg.modules.jmreport.satoken; + +import cn.dev33.satoken.context.model.SaRequest; +import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.stp.StpUtil; +import com.jeecg.modules.jmreport.controller.LoginController; +import com.jeecg.modules.jmreport.satoken.util.AjaxRequestUtils; +import jakarta.servlet.http.HttpSession; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.filter.SaServletFilter; +import cn.dev33.satoken.interceptor.SaInterceptor; +import cn.dev33.satoken.util.SaResult; + + +/** + * [Sa-Token 权限认证] 配置类 + * + * @author click33 + */ +@Configuration +public class SaTokenConfigure implements WebMvcConfigurer { + + + /** + * 注册 Sa-Token 拦截器打开注解鉴权功能 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 注册 Sa-Token 拦截器打开注解鉴权功能 + registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**"); + } + + /** + * 注册 [Sa-Token 全局过滤器] + */ + @Bean + public SaServletFilter getSaServletFilter() { + return new SaServletFilter() + + // 指定 [拦截路由] 与 [放行路由] + .addInclude("/**") + .addExclude("/favicon.ico") + .addExclude("/login/**") + .addExclude("/doLogin") + + + // 认证函数: 每次请求执行 + .setAuth(obj -> { + // 设置登录来源,方便退出登录时区分 + AjaxRequestUtils.setLoginSessionInfo(); + +// System.out.println("---------- sa全局认证,path = " + SaHolder.getRequest().getRequestPath()); +// System.out.println("---------- sa全局认证,token = " + StpUtil.getTokenValue()); + }) + + // 异常处理函数:每次认证函数发生异常时执行此函数 + .setError(e -> { + System.out.println("---------- sa全局异常,path = " + SaHolder.getRequest().getRequestPath()); + System.out.println("---------- sa全局认证,token = " + StpUtil.getTokenValue()); + e.printStackTrace(); + return SaResult.error(e.getMessage()); + }) + + // 前置函数:在每次认证函数之前执行(BeforeAuth 不受 includeList 与 excludeList 的限制,所有请求都会进入) + .setBeforeAuth(r -> { + // ---------- 设置一些安全响应头 ---------- + SaHolder.getResponse() + // 服务器名称 + .setServer("sa-server") + // 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以 + .setHeader("X-Frame-Options", "SAMEORIGIN") + // 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面 + .setHeader("X-XSS-Protection", "1; mode=block") + // 禁用浏览器内容嗅探 + .setHeader("X-Content-Type-Options", "nosniff") + ; + }); + } + +} \ No newline at end of file diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/StpInterfaceImpl.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/StpInterfaceImpl.java new file mode 100644 index 0000000..aebdc4f --- /dev/null +++ b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/StpInterfaceImpl.java @@ -0,0 +1,44 @@ +package com.jeecg.modules.jmreport.satoken; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.stereotype.Component; + +import cn.dev33.satoken.stp.StpInterface; + +/** + * 自定义权限验证接口扩展 + */ +@Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展 +public class StpInterfaceImpl implements StpInterface { + + /** + * 返回一个账号所拥有的权限码集合 + */ + @Override + public List getPermissionList(Object loginId, String loginType) { + // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限 + List list = new ArrayList(); + list.add("101"); + list.add("user-add"); + list.add("user-delete"); + list.add("user-update"); + list.add("user-get"); + list.add("article-get"); + return list; + } + + /** + * 返回一个账号所拥有的角色标识集合 + */ + @Override + public List getRoleList(Object loginId, String loginType) { + // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色 + List list = new ArrayList(); + list.add("admin"); + list.add("super-admin"); + return list; + } + +} \ No newline at end of file diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/RedisConfig.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/config/RedisConfig.java similarity index 86% rename from jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/RedisConfig.java rename to jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/config/RedisConfig.java index 46860a6..17362f2 100644 --- a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/config/RedisConfig.java +++ b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/config/RedisConfig.java @@ -1,4 +1,4 @@ -package com.jeecg.modules.jmreport.config; +package com.jeecg.modules.jmreport.satoken.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; @@ -42,12 +42,10 @@ public class RedisConfig { } private Jackson2JsonRedisSerializer jacksonSerializer() { - Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); - // 使用新的替代方法,避免已废弃的enableDefaultTyping objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); - jackson2JsonRedisSerializer.setObjectMapper(objectMapper); - return jackson2JsonRedisSerializer; + // 直接在构造器中传入 objectMapper + return new Jackson2JsonRedisSerializer<>(objectMapper, Object.class); } } diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/config/SecurityConfig.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/config/SecurityConfig.java new file mode 100644 index 0000000..b1e5313 --- /dev/null +++ b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/config/SecurityConfig.java @@ -0,0 +1,40 @@ +package com.jeecg.modules.jmreport.satoken.config; + +import com.jeecg.modules.jmreport.satoken.config.vo.User; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Role; +import org.springframework.stereotype.Component; + + +/** + * 加载项目配置 + * + * @author: jeecg-boot + */ +@Component("securityConfig") +@ConfigurationProperties(prefix = "spring.security") +@Role(BeanDefinition.ROLE_INFRASTRUCTURE) +public class SecurityConfig { + private Boolean enable = true; + /** + * 登录账号和密码 + */ + private User user; + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Boolean getEnable() { + return enable; + } + + public void setEnable(Boolean enable) { + this.enable = enable; + } +} diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/config/vo/User.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/config/vo/User.java new file mode 100644 index 0000000..b9a19a9 --- /dev/null +++ b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/config/vo/User.java @@ -0,0 +1,20 @@ +package com.jeecg.modules.jmreport.satoken.config.vo; + +public class User { + private String name; + private String password; + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getPassword() { + return password; + } + public void setPassword(String password) { + this.password = password; + } +} + diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/exception/AjaxJson.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/exception/AjaxJson.java new file mode 100644 index 0000000..c7cff37 --- /dev/null +++ b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/exception/AjaxJson.java @@ -0,0 +1,162 @@ +//package com.jeecg.modules.jmreport.satoken.exception; +// +//import java.io.Serializable; +//import java.util.List; +// +// +///** +// * ajax请求返回Json格式数据的封装 +// */ +//public class AjaxJson implements Serializable{ +// +// private static final long serialVersionUID = 1L; // 序列化版本号 +// +// public static final int CODE_SUCCESS = 200; // 成功状态码 +// public static final int CODE_ERROR = 500; // 错误状态码 +// public static final int CODE_WARNING = 501; // 警告状态码 +// public static final int CODE_NOT_JUR = 403; // 无权限状态码 +// public static final int CODE_NOT_LOGIN = 401; // 未登录状态码 +// public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码 +// +// public int code; // 状态码 +// public String msg; // 描述信息 +// public Object data; // 携带对象 +// public Long dataCount; // 数据总数,用于分页 +// +// /** +// * 返回code +// * @return +// */ +// public int getCode() { +// return this.code; +// } +// +// /** +// * 给msg赋值,连缀风格 +// */ +// public AjaxJson setMsg(String msg) { +// this.msg = msg; +// return this; +// } +// public String getMsg() { +// return this.msg; +// } +// +// /** +// * 给data赋值,连缀风格 +// */ +// public AjaxJson setData(Object data) { +// this.data = data; +// return this; +// } +// +// /** +// * 将data还原为指定类型并返回 +// */ +// @SuppressWarnings("unchecked") +// public T getData(Class cs) { +// return (T) data; +// } +// +// // ============================ 构建 ================================== +// +// public AjaxJson(int code, String msg, Object data, Long dataCount) { +// this.code = code; +// this.msg = msg; +// this.data = data; +// this.dataCount = dataCount; +// } +// +// // 返回成功 +// public static AjaxJson getSuccess() { +// return new AjaxJson(CODE_SUCCESS, "ok", null, null); +// } +// public static AjaxJson getSuccess(String msg) { +// return new AjaxJson(CODE_SUCCESS, msg, null, null); +// } +// public static AjaxJson getSuccess(String msg, Object data) { +// return new AjaxJson(CODE_SUCCESS, msg, data, null); +// } +// public static AjaxJson getSuccessData(Object data) { +// return new AjaxJson(CODE_SUCCESS, "ok", data, null); +// } +// public static AjaxJson getSuccessArray(Object... data) { +// return new AjaxJson(CODE_SUCCESS, "ok", data, null); +// } +// +// // 返回失败 +// public static AjaxJson getError() { +// return new AjaxJson(CODE_ERROR, "error", null, null); +// } +// public static AjaxJson getError(String msg) { +// return new AjaxJson(CODE_ERROR, msg, null, null); +// } +// +// // 返回警告 +// public static AjaxJson getWarning() { +// return new AjaxJson(CODE_ERROR, "warning", null, null); +// } +// public static AjaxJson getWarning(String msg) { +// return new AjaxJson(CODE_WARNING, msg, null, null); +// } +// +// // 返回未登录 +// public static AjaxJson getNotLogin() { +// return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null); +// } +// +// // 返回没有权限的 +// public static AjaxJson getNotJur(String msg) { +// return new AjaxJson(CODE_NOT_JUR, msg, null, null); +// } +// +// // 返回一个自定义状态码的 +// public static AjaxJson get(int code, String msg){ +// return new AjaxJson(code, msg, null, null); +// } +// +// // 返回分页和数据的 +// public static AjaxJson getPageData(Long dataCount, Object data){ +// return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount); +// } +// +// // 返回,根据受影响行数的(大于0=ok,小于0=error) +// public static AjaxJson getByLine(int line){ +// if(line > 0){ +// return getSuccess("ok", line); +// } +// return getError("error").setData(line); +// } +// +// // 返回,根据布尔值来确定最终结果的 (true=ok,false=error) +// public static AjaxJson getByBoolean(boolean b){ +// return b ? getSuccess("ok") : getError("error"); +// } +// +// /* (non-Javadoc) +// * @see java.lang.Object#toString() +// */ +// @SuppressWarnings("rawtypes") +// @Override +// public String toString() { +// String data_string = null; +// if(data == null){ +// +// } else if(data instanceof List){ +// data_string = "List(length=" + ((List)data).size() + ")"; +// } else { +// data_string = data.toString(); +// } +// return "{" +// + "\"code\": " + this.getCode() +// + ", \"msg\": \"" + this.getMsg() + "\"" +// + ", \"data\": " + data_string +// + ", \"dataCount\": " + dataCount +// + "}"; +// } +// +// +// +// +// +//} \ No newline at end of file diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/exception/GlobalException.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/exception/GlobalException.java new file mode 100644 index 0000000..3e3b7f9 --- /dev/null +++ b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/exception/GlobalException.java @@ -0,0 +1,53 @@ +//package com.jeecg.modules.jmreport.satoken.exception; +// +//import org.springframework.web.bind.annotation.ExceptionHandler; +//import org.springframework.web.bind.annotation.RestControllerAdvice; +//import cn.dev33.satoken.exception.DisableServiceException; +//import cn.dev33.satoken.exception.NotLoginException; +//import cn.dev33.satoken.exception.NotPermissionException; +//import cn.dev33.satoken.exception.NotRoleException; +//import jakarta.servlet.http.HttpServletRequest; +//import jakarta.servlet.http.HttpServletResponse; +// +///** +// * 全局异常处理 +// */ +//@RestControllerAdvice +//public class GlobalException { +// +// // 全局异常拦截(拦截项目中的所有异常) +// @ExceptionHandler +// public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response) +// throws Exception { +// +// // 打印堆栈,以供调试 +// System.out.println("全局异常---------------"); +// //e.printStackTrace(); +// +// // 不同异常返回不同状态码 +// AjaxJson aj = null; +// if (e instanceof NotLoginException) { // 如果是未登录异常 +// NotLoginException ee = (NotLoginException) e; +// aj = AjaxJson.getNotLogin().setMsg(ee.getMessage()); +// } +// else if(e instanceof NotRoleException) { // 如果是角色异常 +// NotRoleException ee = (NotRoleException) e; +// aj = AjaxJson.getNotJur("无此角色:" + ee.getRole()); +// } +// else if(e instanceof NotPermissionException) { // 如果是权限异常 +// NotPermissionException ee = (NotPermissionException) e; +// aj = AjaxJson.getNotJur("无此权限:" + ee.getPermission()); +// } +// else if(e instanceof DisableServiceException) { // 如果是被封禁异常 +// DisableServiceException ee = (DisableServiceException) e; +// aj = AjaxJson.getNotJur("当前账号 " + ee.getService() + " 服务已被封禁 (level=" + ee.getLevel() + "):" + ee.getDisableTime() + "秒后解封"); +// } +// else { // 普通异常, 输出:500 + 异常信息 +// aj = AjaxJson.getError(e.getMessage()); +// } +// +// // 返回给前端 +// return aj; +// } +// +//} \ No newline at end of file diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/exception/GlobalExceptionHandler.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..a86e749 --- /dev/null +++ b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/exception/GlobalExceptionHandler.java @@ -0,0 +1,38 @@ +//package com.jeecg.modules.jmreport.satoken.exception; +// +//import cn.dev33.satoken.exception.NotLoginException; +//import cn.dev33.satoken.util.SaResult; +//import com.jeecg.modules.jmreport.satoken.util.AjaxRequestUtils; +//import jakarta.servlet.http.HttpServletRequest; +//import jakarta.servlet.http.HttpServletResponse; +//import org.springframework.web.bind.annotation.ExceptionHandler; +//import org.springframework.web.bind.annotation.RestControllerAdvice; +// +//import java.io.IOException; +//import java.net.URLEncoder; +// +//@RestControllerAdvice +//public class GlobalExceptionHandler { +// +// // 捕获未登录异常 +// @ExceptionHandler(NotLoginException.class) +// public SaResult handlerNotLoginException(NotLoginException e, +// HttpServletRequest request, +// HttpServletResponse response) { +// +// // AJAX 请求返回 JSON +// if (AjaxRequestUtils.isAjaxRequest(request)) { +// return SaResult.error("未登录,请先登录").setCode(401); +// } +// +// // 普通请求重定向到登录页 +// try { +// response.sendRedirect("/login/login.html?redirect=" + URLEncoder.encode(request.getRequestURI(), "UTF-8")); +// } catch (IOException ex) { +// ex.printStackTrace(); +// } +// +// return SaResult.error("未登录"); +// } +// +//} \ No newline at end of file diff --git a/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/util/AjaxRequestUtils.java b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/util/AjaxRequestUtils.java new file mode 100644 index 0000000..d27bebb --- /dev/null +++ b/jimureport-example/src/main/java/com/jeecg/modules/jmreport/satoken/util/AjaxRequestUtils.java @@ -0,0 +1,79 @@ +package com.jeecg.modules.jmreport.satoken.util; + +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.session.SaSession; +import com.alibaba.fastjson.JSON; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; + +@Slf4j +public class AjaxRequestUtils { + + /** + * 判断是否为 AJAX 请求 + */ + public static boolean isAjaxRequest(HttpServletRequest request) { + // 1. 检查 X-Requested-With 头部 + String xRequestedWith = request.getHeader("X-Requested-With"); + if ("XMLHttpRequest".equalsIgnoreCase(xRequestedWith)) { + return true; + } + + // 2. 检查 Accept 头部 + String accept = request.getHeader("Accept"); + if (accept != null && accept.contains("application/json")) { + return true; + } + + // 3. 检查 Content-Type 头部(对于 POST 请求) + String contentType = request.getHeader("Content-Type"); + if (contentType != null && contentType.contains("application/json")) { + return true; + } + + // 4. 检查请求参数(某些框架会添加特定参数) + String ajaxParam = request.getParameter("_ajax"); + if ("true".equals(ajaxParam)) { + return true; + } + + return false; + } + + /** + * 根据请求类型返回相应响应 + */ + public static void writeResponse(HttpServletRequest request, + HttpServletResponse response, + Object data) throws IOException { + + if (isAjaxRequest(request)) { + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write(JSON.toJSONString(data)); + } else { + // 普通请求处理 + request.setAttribute("data", data); + // 这里可以转发到相应的 JSP 页面 + } + } + + /** + * 设置 Sa-Token 会话的登录来源和拖拽开关 + */ + // 登录来源-积木报表示例 + public final static String LOGIN_FROM_MODEL_NEED_LOGOUT = "jimu_example"; + + public static void setLoginSessionInfo() { + HttpSession originalSession = ((HttpServletRequest) SaHolder.getRequest().getSource()).getSession(); + if (originalSession!=null && originalSession.getAttribute("loginFrom") == null) { + log.info("设置登录来源,BI与报表切换开关,注入个性化session信息。"); + originalSession.setAttribute("loginFrom", LOGIN_FROM_MODEL_NEED_LOGOUT); + originalSession.setAttribute("switchJimuDrag", true); + } + } + +} \ No newline at end of file diff --git a/jimureport-example/src/main/resources/application-dev.yml b/jimureport-example/src/main/resources/application-dev.yml index b671ac1..714e82c 100644 --- a/jimureport-example/src/main/resources/application-dev.yml +++ b/jimureport-example/src/main/resources/application-dev.yml @@ -7,9 +7,17 @@ spring: username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver + #Redis配置 + data: + redis: + database: 0 + host: 127.0.0.1 + port: 6379 + password: + # 登录认证配置 security: - #是否放开预览页面不需要登录 - open-view-page: true + #是否开启登录认证 + enable: true #登录账号和密码 user: name: "admin" diff --git a/jimureport-example/src/main/resources/application-prod.yml b/jimureport-example/src/main/resources/application-prod.yml index 0a4f0c0..5528a26 100644 --- a/jimureport-example/src/main/resources/application-prod.yml +++ b/jimureport-example/src/main/resources/application-prod.yml @@ -7,10 +7,16 @@ spring: username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver + #Redis配置 + data: + redis: + database: 0 + host: 127.0.0.1 + port: 6379 + password: + # 登录认证配置 security: - #是否放开预览页面不需要登录 - open-view-page: true - #登录账号和密码 + #是否开启登录认证 user: name: "admin" password: "123456" diff --git a/jimureport-example/src/main/resources/application.yml b/jimureport-example/src/main/resources/application.yml index caf4dfc..e8eb80a 100644 --- a/jimureport-example/src/main/resources/application.yml +++ b/jimureport-example/src/main/resources/application.yml @@ -1,3 +1,20 @@ spring: profiles: - active: dev \ No newline at end of file + active: dev + +############## Sa-Token 配置 (文档: https://sa-token.cc) ############## +sa-token: + # token 名称(同时也是 cookie 名称) + token-name: X-Access-Token + # token 有效期(单位:秒) 默认30天,-1 代表永久有效 + timeout: 2592000 + # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 + active-timeout: -1 + # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录) + is-concurrent: true + # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token) + is-share: false + # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik) + token-style: uuid + # 是否输出操作日志 + is-log: false \ No newline at end of file diff --git a/jimureport-example/src/main/resources/static/login/login.html b/jimureport-example/src/main/resources/static/login/login.html index c1b6e63..b3bb143 100644 --- a/jimureport-example/src/main/resources/static/login/login.html +++ b/jimureport-example/src/main/resources/static/login/login.html @@ -280,7 +280,7 @@
-