GatewayConfigure.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. /*
  2. * Copyright [2022] [https://www.xiaonuo.vip]
  3. *
  4. * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
  5. *
  6. * 1.请不要删除和修改根目录下的LICENSE文件。
  7. * 2.请不要删除和修改Snowy源码头部的版权声明。
  8. * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
  9. * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
  10. * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
  11. * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
  12. */
  13. package vip.xiaonuo.gateway.config;
  14. import cn.dev33.satoken.context.SaHolder;
  15. import cn.dev33.satoken.context.model.SaResponse;
  16. import cn.dev33.satoken.reactor.filter.SaReactorFilter;
  17. import cn.dev33.satoken.router.SaHttpMethod;
  18. import cn.dev33.satoken.router.SaRouter;
  19. import cn.dev33.satoken.stp.StpInterface;
  20. import cn.dev33.satoken.stp.StpLogic;
  21. import cn.dev33.satoken.stp.StpUtil;
  22. import cn.hutool.core.collection.CollectionUtil;
  23. import cn.hutool.core.util.CharsetUtil;
  24. import cn.hutool.http.ContentType;
  25. import cn.hutool.http.Header;
  26. import feign.codec.Decoder;
  27. import org.springframework.beans.BeansException;
  28. import org.springframework.beans.factory.ObjectFactory;
  29. import org.springframework.beans.factory.ObjectProvider;
  30. import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  31. import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
  32. import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
  33. import org.springframework.cloud.openfeign.support.SpringDecoder;
  34. import org.springframework.context.annotation.Bean;
  35. import org.springframework.context.annotation.Configuration;
  36. import org.springframework.context.annotation.Import;
  37. import org.springframework.context.annotation.Primary;
  38. import org.springframework.data.redis.connection.RedisConnectionFactory;
  39. import org.springframework.data.redis.core.RedisTemplate;
  40. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
  41. import org.springframework.data.redis.serializer.StringRedisSerializer;
  42. import org.springframework.http.MediaType;
  43. import org.springframework.http.converter.HttpMessageConverter;
  44. import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
  45. import org.springframework.stereotype.Component;
  46. import org.springframework.web.reactive.socket.client.TomcatWebSocketClient;
  47. import org.springframework.web.reactive.socket.client.WebSocketClient;
  48. import org.springframework.web.reactive.socket.server.RequestUpgradeStrategy;
  49. import org.springframework.web.reactive.socket.server.upgrade.TomcatRequestUpgradeStrategy;
  50. import vip.xiaonuo.auth.core.enums.SaClientTypeEnum;
  51. import vip.xiaonuo.auth.core.util.StpClientLoginUserUtil;
  52. import vip.xiaonuo.auth.core.util.StpClientUtil;
  53. import vip.xiaonuo.auth.core.util.StpLoginUserUtil;
  54. import vip.xiaonuo.common.enums.SysBuildInEnum;
  55. import vip.xiaonuo.gateway.core.util.GlobalExceptionUtil;
  56. import java.util.ArrayList;
  57. import java.util.List;
  58. import java.util.stream.Collectors;
  59. /**
  60. * @author : dongxiayu
  61. * @classname : GatewayConfig
  62. * @description : 网关配置
  63. * @date : 2022/11/16 15:32
  64. */
  65. @Import({cn.hutool.extra.spring.SpringUtil.class})
  66. @Configuration
  67. public class GatewayConfigure {
  68. /**
  69. * 无需登录的接口地址集合
  70. */
  71. private static final String[] NO_LOGIN_PATH_ARR = {
  72. /* 主入口 */
  73. "/",
  74. /* 静态资源 */
  75. "/favicon.ico",
  76. "/doc.html",
  77. "/webjars/**",
  78. "/swagger-resources/**",
  79. "/v2/api-docs",
  80. "/v2/api-docs-ext",
  81. "/configuration/ui",
  82. "/configuration/security",
  83. "/ureport/**",
  84. "/druid/**",
  85. "/api/webapp/images/**",
  86. "/api/webapp/v2/api-docs",
  87. "/api/bizapp/v2/api-docs",
  88. "/api/tenapp/v2/api-docs",
  89. "/api/flwapp/v2/api-docs",
  90. /* 认证相关 */
  91. "/api/webapp/auth/c/getPicCaptcha",
  92. "/api/webapp/auth/c/getPhoneValidCode",
  93. "/api/webapp/auth/c/doLogin",
  94. "/api/webapp/auth/c/doLoginByPhone",
  95. "/api/webapp/auth/b/getPicCaptcha",
  96. "/api/webapp/auth/b/getPhoneValidCode",
  97. "/api/webapp/auth/b/doLogin",
  98. "/api/webapp/auth/b/doLoginByPhone",
  99. /* 三方登录相关 */
  100. "/api/webapp/auth/third/render",
  101. "/api/webapp/auth/third/callback",
  102. /* 系统基础配置 */
  103. "/api/webapp/dev/config/sysBaseList",
  104. /* 系统字典树 */
  105. "/api/webapp/dev/dict/tree",
  106. /* 文件下载 */
  107. "/api/webapp/dev/file/download",
  108. /* 用户个人中心相关 */
  109. "/api/webapp/sys/userCenter/getPicCaptcha",
  110. "/api/webapp/sys/userCenter/findPasswordGetPhoneValidCode",
  111. "/api/webapp/sys/userCenter/findPasswordGetEmailValidCode",
  112. "/api/webapp/sys/userCenter/findPasswordByPhone",
  113. "/api/webapp/sys/userCenter/findPasswordByEmail",
  114. /* actuator */
  115. "/actuator",
  116. "/actuator/**",
  117. "/api/webapp/filetransfer/preview",
  118. "/api/webapp/filetransfer/downloadfile",
  119. "/api/webapp/filetransfer/batchDownloadFile",
  120. "/api/webapp/resourceFile/preview",
  121. "/api/webapp/resourceFile/downloadfile",
  122. "/api/webapp/resourceFile/batchDownloadFile",
  123. /* 支付相关回调通知 */
  124. "/api/bizapp/pay/ali/notifyUrl",
  125. "/api/bizapp/pay/wx/notifyUrl",
  126. "/api/bizapp/pay/wx/authNotifyUrl",
  127. "/api/bizapp/pay/wx/jsPay",
  128. "/api/bizapp/pay/order/sample/doCreateOrder",
  129. "/api/tenapp/ten/storage/tenSelector",
  130. "/api/webapp/test/auto",
  131. "/api/webapp/disk/courseauditrecord/getShareInfoPage",
  132. /* 资源中心 */
  133. "/api/webapp/disk/resourcecentre/page",
  134. "/api/webapp/disk/resourcecentre/detail",
  135. "/api/webapp/disk/courseauditrecord/addViewCount",
  136. "/webSocket/**"
  137. };
  138. /**
  139. * 仅超管使用的接口地址集合
  140. */
  141. private static final String[] SUPER_PERMISSION_PATH_ARR = {
  142. "/api/webapp/auth/session/**",
  143. "/api/webapp/auth/third/page",
  144. "/api/webapp/client/user/**",
  145. "/api/webapp/sys/org/**",
  146. "/api/webapp/sys/position/**",
  147. "/api/webapp/sys/button/**",
  148. "/api/webapp/sys/menu/**",
  149. "/api/webapp/sys/module/**",
  150. "/api/webapp/sys/spa/**",
  151. "/api/webapp/sys/role/**",
  152. "/api/webapp/sys/user/**",
  153. "/api/webapp/dev/config/**",
  154. "/api/webapp/dev/dict/**",
  155. "/api/webapp/dev/email/page",
  156. "/api/webapp/dev/email/delete",
  157. "/api/webapp/dev/email/detail",
  158. "/api/webapp/dev/file/page",
  159. "/api/webapp/dev/file/list",
  160. "/api/webapp/dev/file/delete",
  161. "/api/webapp/dev/file/detail",
  162. "/api/webapp/dev/job/**",
  163. "/api/webapp/dev/log/**",
  164. "/api/webapp/dev/message/page",
  165. "/api/webapp/dev/message/delete",
  166. "/api/webapp/dev/message/detail",
  167. "/api/webapp/dev/monitor/**",
  168. "/api/webapp/dev/sms/page",
  169. "/api/webapp/dev/sms/delete",
  170. "/api/webapp/dev/sms/detail",
  171. "/api/flwapp/flw/model/**",
  172. "/api/flwapp/flw/templatePrint/**",
  173. "/api/flwapp/flw/templateSn/**",
  174. "/api/bizapp/pay/**",
  175. "/api/bizapp/urp/**",
  176. "/api/tenapp/dbs/**",
  177. "/api/tenapp/ten/"
  178. };
  179. /**
  180. * B端要排除的,相当于C端要认证的
  181. */
  182. private static final String[] CLIENT_USER_PERMISSION_PATH_ARR = {
  183. "/auth/c/**",
  184. "/client/c/**"
  185. };
  186. /**
  187. * @description Feign解码器
  188. * @author dongxiayu
  189. * @date 2022/11/16 1:45
  190. * @return Decoder
  191. **/
  192. @Bean
  193. public Decoder feignDecoder() {
  194. return new ResponseEntityDecoder(new SpringDecoder(feignHttpMessageConverter()));
  195. }
  196. /**
  197. * @description Feign http 消息转换器
  198. * @author dongxiayu
  199. * @date 2022/11/16 1:46
  200. * @return
  201. **/
  202. public ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
  203. final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(new GateWayMappingJackson2HttpMessageConverter());
  204. return new ObjectFactory<HttpMessageConverters>() {
  205. @Override
  206. public HttpMessageConverters getObject() throws BeansException {
  207. return httpMessageConverters;
  208. }
  209. };
  210. }
  211. /**
  212. * @description 消息转换器
  213. * @author dongxiayu
  214. * @date 2022/11/16 1:47
  215. * @return
  216. **/
  217. @Bean
  218. @ConditionalOnMissingBean
  219. public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
  220. return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
  221. }
  222. public class GateWayMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
  223. GateWayMappingJackson2HttpMessageConverter(){
  224. List<MediaType> mediaTypes = new ArrayList<>();
  225. mediaTypes.add(MediaType.valueOf(MediaType.TEXT_HTML_VALUE + ";charset=UTF-8"));
  226. setSupportedMediaTypes(mediaTypes);
  227. }
  228. }
  229. /**
  230. * @description loadBlance WebClient构建器
  231. * @author dongxiayu
  232. * @date 2022/11/16 1:47
  233. * @return
  234. **/
  235. // @Bean
  236. // @LoadBalanced
  237. // public WebClient.Builder loadBalancedWebClientBuilder() {
  238. // return WebClient.builder();
  239. // }
  240. /**
  241. * RedisTemplate序列化
  242. *
  243. * @author xuyuxiang
  244. * @date 2022/6/21 17:01
  245. **/
  246. @Bean
  247. public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
  248. RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
  249. redisTemplate.setConnectionFactory(redisConnectionFactory);
  250. StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
  251. redisTemplate.setKeySerializer(stringRedisSerializer);
  252. redisTemplate.setHashKeySerializer(stringRedisSerializer);
  253. Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
  254. redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
  255. redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
  256. redisTemplate.afterPropertiesSet();
  257. return redisTemplate;
  258. }
  259. @Bean("stpLogic")
  260. public StpLogic getStpLogic() {
  261. // 重写Sa-Token的StpLogic,默认客户端类型为B
  262. return new StpLogic(SaClientTypeEnum.B.getValue());
  263. }
  264. @Bean("stpClientLogic")
  265. public StpLogic getStpClientLogic() {
  266. // 重写Sa-Token的StpLogic,默认客户端类型为C
  267. return new StpLogic(SaClientTypeEnum.C.getValue());
  268. }
  269. /**
  270. * 权限认证接口实现类,集成权限认证功能
  271. *
  272. * @author dongxiayu
  273. * @date 2022/7/7 16:16
  274. **/
  275. @Component
  276. public static class StpInterfaceImpl implements StpInterface {
  277. /**
  278. * 返回一个账号所拥有的权限码集合
  279. */
  280. @Override
  281. public List<String> getPermissionList(Object loginId, String loginType) {
  282. if (SaClientTypeEnum.B.getValue().equals(loginType)) {
  283. return StpLoginUserUtil.getLoginUser().getPermissionCodeList();
  284. } else {
  285. return StpClientLoginUserUtil.getClientLoginUser().getPermissionCodeList();
  286. }
  287. }
  288. /**
  289. * 返回一个账号所拥有的角色标识集合
  290. */
  291. @Override
  292. public List<String> getRoleList(Object loginId, String loginType) {
  293. if (SaClientTypeEnum.B.getValue().equals(loginType)) {
  294. return StpLoginUserUtil.getLoginUser().getRoleCodeList();
  295. } else {
  296. return StpClientLoginUserUtil.getClientLoginUser().getRoleCodeList();
  297. }
  298. }
  299. }
  300. /**
  301. * @description 注册跨域过滤器
  302. * @author dongxiayu
  303. * @date 2022/11/16 1:40
  304. * @return SaReactorFilter
  305. **/
  306. @Bean
  307. public SaReactorFilter getSaReactorFilter() {
  308. return new SaReactorFilter()
  309. // 指定拦截路由
  310. .addInclude("/**")
  311. // 设置鉴权的接口
  312. .setAuth(r -> {
  313. // B端的接口校验B端登录
  314. SaRouter.match("/**")
  315. // 排除无需登录接口
  316. .notMatch(CollectionUtil.newArrayList(NO_LOGIN_PATH_ARR))
  317. // 排除C端认证接口
  318. .notMatch(CollectionUtil.newArrayList(CLIENT_USER_PERMISSION_PATH_ARR))
  319. // 校验B端登录
  320. .check(r1 -> StpUtil.checkLogin());
  321. // C端的接口校验C端登录
  322. SaRouter.match("/**")
  323. // 排除无需登录接口
  324. .notMatch(CollectionUtil.newArrayList(NO_LOGIN_PATH_ARR))
  325. // 匹配C端认证接口
  326. .match(CollectionUtil.newArrayList(CLIENT_USER_PERMISSION_PATH_ARR))
  327. // 校验C端登录
  328. .check(r1 -> StpClientUtil.checkLogin());
  329. // B端的超管接口校验B端超管角色
  330. SaRouter.match("/**")
  331. // 排除无需登录接口
  332. .notMatch(CollectionUtil.newArrayList(NO_LOGIN_PATH_ARR))
  333. // 匹配超管接口
  334. .match(CollectionUtil.newArrayList(SUPER_PERMISSION_PATH_ARR))
  335. // 校验B端超管角色
  336. .check(r1 -> StpUtil.checkRole(SysBuildInEnum.BUILD_IN_ROLE_CODE.getValue()));
  337. })
  338. // 前置函数:在每次认证函数之前执行
  339. .setBeforeAuth(obj -> {
  340. // ---------- 设置跨域响应头 ----------
  341. SaHolder.getResponse()
  342. // 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
  343. // .setHeader("X-Frame-Options", "SAMEORIGIN")
  344. // 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
  345. .setHeader("X-XSS-Protection", "1; mode=block")
  346. // 禁用浏览器内容嗅探
  347. .setHeader("X-Content-Type-Options", "nosniff")
  348. // 允许指定域访问跨域资源
  349. .setHeader("Access-Control-Allow-Origin", "*")
  350. // 允许所有请求方式
  351. .setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
  352. // 有效时间
  353. .setHeader("Access-Control-Max-Age", "3600")
  354. // 允许的header参数
  355. .setHeader("Access-Control-Allow-Headers", "*");
  356. // 如果是预检请求,则立即返回到前端
  357. SaRouter.match(SaHttpMethod.OPTIONS)
  358. // OPTIONS预检请求,不做处理
  359. .free(r -> {})
  360. .back();
  361. })
  362. // 异常处理
  363. .setError(e -> {
  364. // 由于过滤器中抛出的异常不进入全局异常处理,所以必须提供[异常处理函数]来处理[认证函数]里抛出的异常
  365. // 在[异常处理函数]里的返回值,将作为字符串输出到前端,此处统一转为JSON输出前端
  366. SaResponse saResponse = SaHolder.getResponse();
  367. saResponse.setHeader(Header.CONTENT_TYPE.getValue(), ContentType.JSON + ";charset=" + CharsetUtil.UTF_8);
  368. return GlobalExceptionUtil.getCommonResult((Exception) e);
  369. });
  370. }
  371. @Bean
  372. @Primary
  373. public RequestUpgradeStrategy requestUpgradeStrategy() {
  374. return new TomcatRequestUpgradeStrategy();
  375. }
  376. @Bean
  377. @Primary
  378. public WebSocketClient webSocketClient() {
  379. return new TomcatWebSocketClient();
  380. }
  381. }