GatewayConfigure.java 16 KB

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