From 252d9e200e889e29d6ee960c77af3ee73d9a312b Mon Sep 17 00:00:00 2001 From: gaoqr <13665037151@163.com> Date: Fri, 14 Jun 2024 11:41:26 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81access=5Ftoken=E5=A4=9A=E7=BA=A7?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E5=90=8C=E6=97=B6=E5=A4=B1=E6=95=88=E4=B8=8B?= =?UTF-8?q?=E7=9A=84=E5=88=B7=E6=96=B0=E5=A4=84=E7=90=86=EF=BC=9B=202?= =?UTF-8?q?=E3=80=81loginUser=E6=95=B4=E4=BD=93=E7=94=A8utf-8=E7=BC=96?= =?UTF-8?q?=E7=A0=81=E5=A4=84=E7=90=86=E4=B8=AD=E6=96=87=E4=B9=B1=E7=A0=81?= =?UTF-8?q?=EF=BC=9B=203=E3=80=81=E8=A7=92=E8=89=B2=E7=94=A8sort=E6=8E=92?= =?UTF-8?q?=E5=BA=8F=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/context/OrganContextHolder.java | 8 +++++-- .../filter/TokenAuthenticationFilter.java | 16 +++++++++----- .../core/rpc/LoginUserRequestInterceptor.java | 18 +++++++++++++-- .../security/TokenAuthenticationFilter.java | 22 +------------------ .../gateway/util/SecurityFrameworkUtils.java | 16 +++++++++++++- .../dal/mysql/permission/RoleMapper.java | 8 +++---- .../oauth2/OAuth2TokenServiceImpl.java | 12 +++++++++- 7 files changed, 63 insertions(+), 37 deletions(-) diff --git a/cf-framework/cf-spring-boot-starter-biz-organ/src/main/java/com/cf/imes/framework/organ/core/context/OrganContextHolder.java b/cf-framework/cf-spring-boot-starter-biz-organ/src/main/java/com/cf/imes/framework/organ/core/context/OrganContextHolder.java index 1912f9a3f..62df76118 100644 --- a/cf-framework/cf-spring-boot-starter-biz-organ/src/main/java/com/cf/imes/framework/organ/core/context/OrganContextHolder.java +++ b/cf-framework/cf-spring-boot-starter-biz-organ/src/main/java/com/cf/imes/framework/organ/core/context/OrganContextHolder.java @@ -21,6 +21,11 @@ public class OrganContextHolder { */ private static final ThreadLocal IGNORE = new TransmittableThreadLocal<>(); + /** + * 上下文获取不到organid异常提示 + */ + public static final String ORGANID_NOT_EXIST_EXCEPTION = "OrganContextHolder 不存在组织编号!"; + /** * 获得组织编号 * @@ -48,8 +53,7 @@ public class OrganContextHolder { public static Long getRequiredOrganId() { Long organId = getOrganId(); if (organId == null) { - throw new NullPointerException("OrganContextHolder 不存在组织编号!可参考文档:" - + DocumentEnum.ORGAN.getUrl()); + throw new NullPointerException(ORGANID_NOT_EXIST_EXCEPTION); } return organId; } diff --git a/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/filter/TokenAuthenticationFilter.java b/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/filter/TokenAuthenticationFilter.java index e11ef1707..fe6a24e5b 100644 --- a/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/filter/TokenAuthenticationFilter.java +++ b/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/filter/TokenAuthenticationFilter.java @@ -15,6 +15,8 @@ import com.cf.imes.module.system.api.oauth2.OAuth2TokenApi; import com.cf.imes.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO; import com.cf.imes.module.system.api.permission.PermissionApi; import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.AccessDeniedException; import org.springframework.web.filter.OncePerRequestFilter; @@ -33,6 +35,7 @@ import java.nio.charset.StandardCharsets; * @author 晨丰科技 */ @RequiredArgsConstructor +@Slf4j public class TokenAuthenticationFilter extends OncePerRequestFilter { private final SecurityProperties securityProperties; @@ -129,14 +132,17 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { .setOrganId(WebFrameworkUtils.getOrganId(request)); } + @SneakyThrows private LoginUser buildLoginUserByHeader(HttpServletRequest request) { String loginUserStr = request.getHeader(SecurityFrameworkUtils.LOGIN_USER_HEADER); if(StrUtil.isNotEmpty(loginUserStr)) { - LoginUser loginUser = JsonUtils.parseObject(loginUserStr, LoginUser.class); - //CommonResult superAdmin = permissionApi.hasAnyRoles(loginUser.getId(), "super_admin"); - //loginUser.setIsSupAdmin(superAdmin.getCheckedData()); - loginUser.setNickname(URLDecoder.decode(loginUser.getNickname(),StandardCharsets.UTF_8)); - return loginUser; + try { + loginUserStr = URLDecoder.decode(loginUserStr, StandardCharsets.UTF_8.name()); // 解码,解决中文乱码问题 + return JsonUtils.parseObject(loginUserStr, LoginUser.class); + } catch (Exception ex) { + log.error("[buildLoginUserByHeader][解析 LoginUser({}) 发生异常]", loginUserStr, ex); ; + throw ex; + } } return null; } diff --git a/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/rpc/LoginUserRequestInterceptor.java b/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/rpc/LoginUserRequestInterceptor.java index 4737ec895..c77b94564 100644 --- a/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/rpc/LoginUserRequestInterceptor.java +++ b/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/rpc/LoginUserRequestInterceptor.java @@ -1,23 +1,37 @@ package com.cf.imes.framework.security.core.rpc; -import com.cf.imes.framework.rpc.core.util.FeignUtils; +import com.cf.imes.framework.common.util.json.JsonUtils; import com.cf.imes.framework.security.core.LoginUser; import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; import feign.RequestInterceptor; import feign.RequestTemplate; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; /** * LoginUser 的 RequestInterceptor 实现类:Feign 请求时,将 {@link LoginUser} 设置到 header 中,继续透传给被调用的服务 * * @author 晨丰科技 */ +@Slf4j public class LoginUserRequestInterceptor implements RequestInterceptor { @Override + @SneakyThrows public void apply(RequestTemplate requestTemplate) { LoginUser user = SecurityFrameworkUtils.getLoginUser(); if (user != null) { - FeignUtils.createJsonHeader(requestTemplate, SecurityFrameworkUtils.LOGIN_USER_HEADER, user); + try { + String userStr = JsonUtils.toJsonString(user); + userStr = URLEncoder.encode(userStr, StandardCharsets.UTF_8.name()); // 编码,避免中文乱码 + requestTemplate.header(SecurityFrameworkUtils.LOGIN_USER_HEADER, userStr); + } catch (Exception ex) { + log.error("[apply][序列化 LoginUser({}) 发生异常]", user, ex); + throw ex; + } } } diff --git a/cf-gateway/src/main/java/com/cf/imes/gateway/filter/security/TokenAuthenticationFilter.java b/cf-gateway/src/main/java/com/cf/imes/gateway/filter/security/TokenAuthenticationFilter.java index 65fe68b7a..8da358fce 100644 --- a/cf-gateway/src/main/java/com/cf/imes/gateway/filter/security/TokenAuthenticationFilter.java +++ b/cf-gateway/src/main/java/com/cf/imes/gateway/filter/security/TokenAuthenticationFilter.java @@ -22,11 +22,7 @@ import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -import java.net.URLEncoder; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.List; import java.util.Objects; import java.util.function.Function; @@ -74,14 +70,6 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered { }); - private final LoadingCache organIdCache = CacheUtils.buildAsyncReloadingCache(Duration.ofMinutes(1L), // 过期时间 1 分钟 - new CacheLoader<>() { - @Override - public Long load(String token) { - return getOrganIdByToken(token).block(); - } - }); - public TokenAuthenticationFilter(ReactorLoadBalancerExchangeFilterFunction lbFunction) { // Q:为什么不使用 OAuth2TokenApi 进行调用? // A1:Spring Cloud OpenFeign 官方未内置 Reactive 的支持 https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#reactive-support @@ -144,17 +132,9 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered { private Mono checkAccessToken(Long organId, String token) { return webClient.get() .uri(OAuth2TokenApi.URL_CHECK, uriBuilder -> uriBuilder.queryParam("accessToken", token).build()) - // .headers(httpHeaders -> WebFrameworkUtils.setOrganIdHeader(organId, httpHeaders)) // 设置组织的 Header .retrieve().bodyToMono(String.class); } - private Mono getOrganIdByToken(String token) { - return webClient.get() - .uri(OAuth2TokenApi.URL_ORGAN, uriBuilder -> uriBuilder.queryParam("accessToken", token).build()) - .retrieve() - .bodyToMono(Long.class); - } - private LoginUser buildUser(String body) { // 处理结果,结果不正确 CommonResult result = JsonUtils.parseObject(body, CHECK_RESULT_TYPE_REFERENCE); @@ -175,7 +155,7 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered { .setOrganId(tokenInfo.getOrganId()).setScopes(tokenInfo.getScopes()) .setLarge(tokenInfo.getLarge()).setDbNo(tokenInfo.getDbNo()).setTableNo(tokenInfo.getTableNo()) .setDataCode(tokenInfo.getDataCode()).setIsSupAdmin(tokenInfo.getIsSupAdmin()) - .setNickname(URLEncoder.encode(tokenInfo.getNickname(), StandardCharsets.UTF_8)); + .setNickname(tokenInfo.getNickname()); } @Override diff --git a/cf-gateway/src/main/java/com/cf/imes/gateway/util/SecurityFrameworkUtils.java b/cf-gateway/src/main/java/com/cf/imes/gateway/util/SecurityFrameworkUtils.java index aeea82db1..897c0a4c6 100644 --- a/cf-gateway/src/main/java/com/cf/imes/gateway/util/SecurityFrameworkUtils.java +++ b/cf-gateway/src/main/java/com/cf/imes/gateway/util/SecurityFrameworkUtils.java @@ -3,10 +3,15 @@ package com.cf.imes.gateway.util; import cn.hutool.core.map.MapUtil; import com.cf.imes.framework.common.util.json.JsonUtils; import com.cf.imes.gateway.filter.security.LoginUser; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + /** * 安全服务工具类 * @@ -14,6 +19,7 @@ import org.springframework.web.server.ServerWebExchange; * * @author 晨丰科技 */ +@Slf4j public class SecurityFrameworkUtils { private static final String AUTHORIZATION_HEADER = "Authorization"; @@ -101,8 +107,16 @@ public class SecurityFrameworkUtils { * @param builder 请求 * @param user 用户 */ + @SneakyThrows public static void setLoginUserHeader(ServerHttpRequest.Builder builder, LoginUser user) { - builder.header(LOGIN_USER_HEADER, JsonUtils.toJsonString(user)); + try { + String userStr = JsonUtils.toJsonString(user); + userStr = URLEncoder.encode(userStr, StandardCharsets.UTF_8.name()); // 编码,避免中文乱码 + builder.header(LOGIN_USER_HEADER, userStr); + } catch (Exception ex) { + log.error("[setLoginUserHeader][序列化 user({}) 发生异常]", user, ex); + throw ex; + } builder.header(DATA_CODE, user.getDataCode()); builder.header(ORGAN_ID, user.getOrganId().toString()); } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/permission/RoleMapper.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/permission/RoleMapper.java index 6bb522ceb..486b92c2c 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/permission/RoleMapper.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/permission/RoleMapper.java @@ -1,7 +1,7 @@ package com.cf.imes.module.system.dal.mysql.permission; +import cn.hutool.core.util.ObjectUtil; import com.cf.imes.framework.common.pojo.PageResult; -import com.cf.imes.framework.mybatis.core.dataobject.BaseDO; import com.cf.imes.framework.mybatis.core.mapper.BaseMapperX; import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX; import com.cf.imes.framework.security.core.LoginUser; @@ -12,9 +12,7 @@ import org.apache.ibatis.annotations.Mapper; import org.springframework.lang.Nullable; import java.util.Collection; -import java.util.HashSet; import java.util.List; -import java.util.Objects; @Mapper public interface RoleMapper extends BaseMapperX { @@ -28,7 +26,7 @@ public interface RoleMapper extends BaseMapperX { //如果是超级管理员并且未传organId,就查看自己的的 lambdaQueryWrapperX.inIfPresent(RoleDO::getOrganId,0L,loginUser.getOrganId()); }*/ - if(isSupAdmin && !Objects.isNull(reqVO.getOrganId())) { + if(isSupAdmin && ObjectUtil.isNotNull(reqVO.getOrganId())) { //如果是超级管理员并且传入organId,就查看传入的组织 lambdaQueryWrapperX.eqIfPresent(RoleDO::getOrganId, reqVO.getOrganId()); } else { @@ -41,7 +39,7 @@ public interface RoleMapper extends BaseMapperX { .eqIfPresent(RoleDO::getStatus, reqVO.getStatus()) .betweenIfPresent(RoleDO::getCreateTime, reqVO.getCreateTime()) .ne(RoleDO::getId, 1) - .orderByDesc(RoleDO::getId); + .orderByAsc(RoleDO::getSort); return selectPage(reqVO, lambdaQueryWrapperX); } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/oauth2/OAuth2TokenServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/oauth2/OAuth2TokenServiceImpl.java index 82f71e54f..1d1d1be02 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/oauth2/OAuth2TokenServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/oauth2/OAuth2TokenServiceImpl.java @@ -15,6 +15,7 @@ import com.cf.imes.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO; import com.cf.imes.module.system.dal.mysql.oauth2.OAuth2AccessTokenMapper; import com.cf.imes.module.system.dal.mysql.oauth2.OAuth2RefreshTokenMapper; import com.cf.imes.module.system.dal.redis.oauth2.OAuth2AccessTokenRedisDAO; +import org.mybatis.spring.MyBatisSystemException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -97,7 +98,16 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { } // 获取不到,从 MySQL 中获取 - accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken); + try { + accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken); + } catch (MyBatisSystemException e) { + // 可能存在本地缓存和redis缓存都失效的情况下,mybatis-plus的organid填充器拿不到组织id,只捕获“不存在组织编号”的异常返回null让前端refresh-accesstoken,其他异常正常继续抛出 + if (e.getMessage().contains(OrganContextHolder.ORGANID_NOT_EXIST_EXCEPTION)) { + return null; + } else { + throw e; + } + } // 如果在 MySQL 存在,则往 Redis 中写入 if (accessTokenDO != null && !DateUtils.isExpired(accessTokenDO.getExpiresTime())) { oauth2AccessTokenRedisDAO.set(accessTokenDO);