初始化提交

This commit is contained in:
USER-20221017CE\Administrator
2022-11-01 12:14:54 +08:00
commit d31fad2aa9
1733 changed files with 370203 additions and 0 deletions

31
tulingmall-gateway/.gitignore vendored Normal file
View File

@@ -0,0 +1,31 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**
!**/src/test/**
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
### VS Code ###
.vscode/

148
tulingmall-gateway/pom.xml Normal file
View File

@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>tulingmall-gateway</artifactId>
<name>tulingmall-gateway</name>
<description>商城网关</description>
<parent>
<groupId>com.tuling</groupId>
<artifactId>tuling-mall</artifactId>
<version>0.0.5</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.tuling</groupId>
<artifactId>tulingmall-common</artifactId>
<exclusions>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</exclusion>
<exclusion>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--加入nacos的依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--添加jwt相关的包-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--添加Sentinel的依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.2.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- <plugin>-->
<!-- <groupId>com.spotify</groupId>-->
<!-- <artifactId>docker-maven-plugin</artifactId>-->
<!-- <version>${docker.maven.plugin.version}</version>-->
<!-- <executions>-->
<!-- <execution>-->
<!-- <id>build-image</id>-->
<!-- <phase>package</phase>-->
<!-- <goals>-->
<!-- <goal>build</goal>-->
<!-- </goals>-->
<!-- </execution>-->
<!-- </executions>-->
<!-- <configuration>-->
<!-- <imageName>mall/${project.artifactId}:${project.version}</imageName>-->
<!-- <dockerHost>${docker.host}</dockerHost>-->
<!-- <baseImage>java:8</baseImage>-->
<!-- <entryPoint>["java", "-jar", "-Dspring.profiles.active=prod","/${project.build.finalName}.jar"]</entryPoint>-->
<!-- <resources>-->
<!-- <resource>-->
<!-- <targetPath>/</targetPath>-->
<!-- <directory>${project.build.directory}</directory>-->
<!-- <include>${project.build.finalName}.jar</include>-->
<!-- </resource>-->
<!-- </resources>-->
<!-- </configuration>-->
<!-- </plugin>-->
</plugins>
</build>
</project>

View File

@@ -0,0 +1,98 @@
package com.tuling.tulingmall.Component;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.client.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Random;
/**
* 根据RestTemplate特性自己改造
* Created by smlz on 2019/11/19.
*/
@Slf4j
public class TulingRestTemplate extends RestTemplate {
private DiscoveryClient discoveryClient;
public TulingRestTemplate(DiscoveryClient discoveryClient) {
this.discoveryClient = discoveryClient;
}
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
Assert.notNull(url, "URI is required");
Assert.notNull(method, "HttpMethod is required");
ClientHttpResponse response = null;
try {
//判断url的拦截路径,然后去redis(作为注册中心)获取地址随机选取一个
log.info("请求的url路径为:{}",url);
url = replaceUrl(url);
log.info("替换后的路径:{}",url);
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
handleResponse(url, method, response);
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}
catch (IOException ex) {
String resource = url.toString();
String query = url.getRawQuery();
resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
throw new ResourceAccessException("I/O error on " + method.name() +
" request for \"" + resource + "\": " + ex.getMessage(), ex);
} finally {
if (response != null) {
response.close();
}
}
}
/**
* 把服务实例名称替换为ip:端口
* @param url
* @return
*/
private URI replaceUrl(URI url){
//解析我们的微服务的名称
String sourceUrl = url.toString();
String [] httpUrl = sourceUrl.split("//");
int index = httpUrl[1].replaceFirst("/","@").indexOf("@");
String serviceName = httpUrl[1].substring(0,index);
//通过微服务的名称去nacos服务端获取 对应的实例列表
List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances(serviceName);
if(serviceInstanceList.isEmpty()) {
throw new RuntimeException("没有可用的微服务实例列表:"+serviceName);
}
//采取随机的获取一个
Random random = new Random();
Integer randomIndex = random.nextInt(serviceInstanceList.size());
log.info("随机下标:{}",randomIndex);
String serviceIp = serviceInstanceList.get(randomIndex).getUri().toString();
log.info("随机选举的服务IP:{}",serviceIp);
String targetSource = httpUrl[1].replace(serviceName,serviceIp);
try {
return new URI(targetSource);
} catch (URISyntaxException e) {
e.printStackTrace();
}
return url;
}
}

View File

@@ -0,0 +1,15 @@
package com.tuling.tulingmall;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class MallGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(MallGatewayApplication.class, args);
}
}

View File

@@ -0,0 +1,29 @@
package com.tuling.tulingmall.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
/**
* 允许跨域请求
* @return
* bean
*/
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); // 允许cookies跨域
config.addAllowedOrigin("*");// #允许向该服务器提交请求的URI*表示全部允许在SpringMVC中如果设成*会自动转成当前请求头中的Origin
config.addAllowedHeader("*");// #允许访问的头信息,*表示全部
config.setMaxAge(18000L);// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
config.addAllowedMethod("*");// 允许提交请求的方法,*表示全部允许
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}

View File

@@ -0,0 +1,50 @@
package com.tuling.tulingmall.config;
import com.tuling.tulingmall.Component.TulingRestTemplate;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.List;
/**
* Created by smlz on 2019/12/26.
*/
@Configuration
public class RibbonConfig {
/**
* 方法实现说明:原生的RestTemplate +@LB不行 因为在
* InitializingBean方法执行前我们的RestTemplate还没有被增强
* 需要自己改写RestTemplate
* @author:smlz
* @return:
* @exception:
* @date:2020/1/22 14:28
*/
// @Bean
// public TulingRestTemplate restTemplate(DiscoveryClient discoveryClient) {
// return new TulingRestTemplate(discoveryClient);
// }
/**
*
* 手动注入loadBalancerInterceptor拦截器实现负载均衡功能
* @param loadBalancerInterceptor
* @return
*
*/
@Bean
public RestTemplate restTemplate(LoadBalancerInterceptor loadBalancerInterceptor){
RestTemplate restTemplate = new RestTemplate();
List<ClientHttpRequestInterceptor> list = new ArrayList();
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
return restTemplate;
}
}

View File

@@ -0,0 +1,48 @@
package com.tuling.tulingmall.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.*;
import reactor.core.publisher.Mono;
@Slf4j
public class CustomErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {
@Autowired
private GateWayExceptionHandlerAdvice gateWayExceptionHandlerAdvice;
/**
* Create a new {@code DefaultErrorWebExceptionHandler} instance.
*
* @param errorAttributes the error attributes
* @param resourceProperties the resources configuration properties
* @param errorProperties the error configuration properties
* @param applicationContext the current application context
*/
public CustomErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
ErrorProperties errorProperties, ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, errorProperties, applicationContext);
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
@Override
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
Throwable throwable = getError(request);
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(gateWayExceptionHandlerAdvice.handle(throwable)));
}
}

View File

@@ -0,0 +1,70 @@
package com.tuling.tulingmall.exception;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import java.util.Collections;
import java.util.List;
/**
* 这里通过观察网关 ErrorWebFluxAutoConfiguration 的配置
* 我们只需要 把该该配置类copy下来,然后定义errorWebExceptionHandler的逻辑
* 我们需要写一个自定义的异常处理器
*
*/
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class)
@AutoConfigureBefore(WebFluxAutoConfiguration.class)
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ExceptionAutoConfiguration {
private ServerProperties serverProperties;
private ApplicationContext applicationContext;
private ResourceProperties resourceProperties;
private List<ViewResolver> viewResolvers;
private ServerCodecConfigurer serverCodecConfigurer;
public ExceptionAutoConfiguration(ServerProperties serverProperties,
ResourceProperties resourceProperties,
ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer,
ApplicationContext applicationContext) {
this.serverProperties = serverProperties;
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.viewResolvers = viewResolversProvider
.getIfAvailable(() -> Collections.emptyList());
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
DefaultErrorWebExceptionHandler exceptionHandler = new CustomErrorWebExceptionHandler(
errorAttributes, this.resourceProperties,
this.serverProperties.getError(), this.applicationContext);
exceptionHandler.setViewResolvers(this.viewResolvers);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}
}

View File

@@ -0,0 +1,34 @@
package com.tuling.tulingmall.exception;
import com.tuling.tulingmall.common.api.CommonResult;
import com.tuling.tulingmall.common.exception.GateWayException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
@Slf4j
@Component
public class GateWayExceptionHandlerAdvice {
@ExceptionHandler(value = {GateWayException.class})
public CommonResult handle(GateWayException ex) {
log.error("网关异常code:{},msg:{}", ex.getCode(),ex.getMessage());
return CommonResult.failed(ex.getCode(),ex.getMessage());
}
@ExceptionHandler(value = {Throwable.class})
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public CommonResult handle(Throwable throwable) {
if(throwable instanceof GateWayException) {
return handle((GateWayException) throwable);
}else {
return CommonResult.failed();
}
}
}

View File

@@ -0,0 +1,89 @@
package com.tuling.tulingmall.exception;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.DefaultBlockRequestHandler;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.fastjson.JSON;
import com.tuling.tulingmall.common.api.CommonResult;
import com.tuling.tulingmall.common.api.ResultCode;
import org.springframework.http.HttpStatus;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
/**
* Created by smlz on 2020/2/20.
*/
@Component
public class GatewayBlockExceptionHandler extends DefaultBlockRequestHandler {
private static final String DEFAULT_BLOCK_MSG_PREFIX = "Blocked by Sentinel: ";
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable ex) {
if (acceptsHtml(exchange)) {
return htmlErrorResponse(ex);
}
// JSON result by default.
return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(fromObject(buildErrorResult(ex)));
}
private Mono<ServerResponse> htmlErrorResponse(Throwable ex) {
return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
.contentType(MediaType.TEXT_PLAIN)
.syncBody(new String(JSON.toJSONString(buildErrorResult(ex))));
}
private CommonResult buildErrorResult(Throwable ex) {
if(ex instanceof ParamFlowException) {
return CommonResult.failed(ResultCode.TOMANY_REQUEST_ERROR);
}else if (ex instanceof DegradeException) {
return CommonResult.failed(ResultCode.BACKGROUD_DEGRADE_ERROR);
}else{
return CommonResult.failed(ResultCode.BAD_GATEWAY);
}
}
/**
* Reference from {@code DefaultErrorWebExceptionHandler} of Spring Boot.
*/
private boolean acceptsHtml(ServerWebExchange exchange) {
try {
List<MediaType> acceptedMediaTypes = exchange.getRequest().getHeaders().getAccept();
acceptedMediaTypes.remove(MediaType.ALL);
MediaType.sortBySpecificityAndQuality(acceptedMediaTypes);
return acceptedMediaTypes.stream()
.anyMatch(MediaType.TEXT_HTML::isCompatibleWith);
} catch (InvalidMediaTypeException ex) {
return false;
}
}
private static class ErrorResult {
private final int code;
private final String message;
ErrorResult(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
}

View File

@@ -0,0 +1,180 @@
package com.tuling.tulingmall.filter;
import com.alibaba.fastjson.JSON;
import com.tuling.tulingmall.Component.TulingRestTemplate;
import com.tuling.tulingmall.common.api.ResultCode;
import com.tuling.tulingmall.common.exception.GateWayException;
import com.tuling.tulingmall.properties.NotAuthUrlProperties;
import com.tuling.tulingmall.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.security.PublicKey;
import java.util.Map;
/**
* 认证过滤器,根据url判断用户请求是要经过认证 才能访问
* Created by smlz on 2019/12/17.
*/
@Component
@Slf4j
@EnableConfigurationProperties(value = NotAuthUrlProperties.class)
public class AuthorizationFilter implements GlobalFilter,Ordered,InitializingBean {
// @Autowired
// private TulingRestTemplate restTemplate;
@Autowired
private RestTemplate restTemplate;
/**
* 请求各个微服务 不需要用户认证的URL
*/
@Autowired
private NotAuthUrlProperties notAuthUrlProperties;
/**
* jwt的公钥,需要网关启动,远程调用认证中心去获取公钥
*/
private PublicKey publicKey;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String currentUrl = exchange.getRequest().getURI().getPath();
//1:不需要认证的url
if(shouldSkip(currentUrl)) {
//log.info("跳过认证的URL:{}",currentUrl);
return chain.filter(exchange);
}
//log.info("需要认证的URL:{}",currentUrl);
//第一步:解析出我们Authorization的请求头 value为: “bearer XXXXXXXXXXXXXX”
String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
//第二步:判断Authorization的请求头是否为空
if(StringUtils.isEmpty(authHeader)) {
log.warn("需要认证的url,请求头为空");
throw new GateWayException(ResultCode.AUTHORIZATION_HEADER_IS_EMPTY);
}
//第三步 校验我们的jwt 若jwt不对或者超时都会抛出异常
Claims claims = JwtUtils.validateJwtToken(authHeader,publicKey);
//第四步 把从jwt中解析出来的 用户登陆信息存储到请求头中
ServerWebExchange webExchange = wrapHeader(exchange,claims);
return chain.filter(webExchange);
}
/**
* 方法实现说明:把我们从jwt解析出来的用户信息存储到请求中
* @author:smlz
* @param serverWebExchange
* @param claims
* @return: ServerWebExchange
* @exception:
* @date:2020/1/22 12:12
*/
private ServerWebExchange wrapHeader(ServerWebExchange serverWebExchange,Claims claims) {
String loginUserInfo = JSON.toJSONString(claims);
//log.info("jwt的用户信息:{}",loginUserInfo);
String memberId = claims.get("additionalInfo",Map.class).get("memberId").toString();
String nickName = claims.get("additionalInfo",Map.class).get("nickName").toString();
//向headers中放文件记得build
ServerHttpRequest request = serverWebExchange.getRequest().mutate()
.header("username",claims.get("user_name",String.class))
.header("memberId",memberId)
.header("nickName",nickName)
.build();
//将现在的request 变成 change对象
return serverWebExchange.mutate().request(request).build();
}
/* private boolean hasPremisson(Claims claims,String currentUrl) {
boolean hasPremisson = false;
//登陆用户的权限集合判断
List<String> premessionList = claims.get("authorities",List.class);
for (String url: premessionList) {
if(currentUrl.contains(url)) {
hasPremisson = true;
break;
}
}
if(!hasPremisson){
log.warn("权限不足");
throw new GateWayException(SystemErrorType.FORBIDDEN);
}
return hasPremisson;
}*/
/**
* 方法实现说明:不需要授权的路径
* @author:smlz
* @param currentUrl 当前请求路径
* @return:
* @exception:
* @date:2019/12/26 13:49
*/
private boolean shouldSkip(String currentUrl) {
//路径匹配器(简介SpringMvc拦截器的匹配器)
//比如/oauth/** 可以匹配/oauth/token /oauth/check_token等
PathMatcher pathMatcher = new AntPathMatcher();
for(String skipPath:notAuthUrlProperties.getShouldSkipUrls()) {
if(pathMatcher.match(skipPath,currentUrl)) {
return true;
}
}
return false;
}
@Override
public int getOrder() {
return 0;
}
/**
* 方法实现说明:网关服务启动 生成公钥
* @author:smlz
* @return:
* @exception:
* @date:2020/1/22 11:58
*/
@Override
public void afterPropertiesSet() throws Exception {
//初始化公钥
this.publicKey = JwtUtils.genPulicKey(restTemplate);
}
}

View File

@@ -0,0 +1,22 @@
package com.tuling.tulingmall.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.LinkedHashSet;
/**
* @vlog: 高于生活,源于生活
* @desc: 类的描述:网关跳过认证的配置类
* @author: smlz
* @createDate: 2020/1/22 10:56
* @version: 1.0
*/
@Data
@ConfigurationProperties("tuling.gateway")
public class NotAuthUrlProperties {
private LinkedHashSet<String> shouldSkipUrls;
}

View File

@@ -0,0 +1,144 @@
package com.tuling.tulingmall.utils;
import com.tuling.tulingmall.common.api.ResultCode;
import com.tuling.tulingmall.common.exception.GateWayException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.*;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
/**
* @vlog: 高于生活,源于生活
* @desc: 类的描述:Jwt操作类
* @author: smlz
* @createDate: 2020/1/22 11:29
* @version: 1.0
*/
@Slf4j
public class JwtUtils {
/**
* 认证服务器许可我们的网关的clientId(需要在oauth_client_details表中配置)
*/
private static final String CLIENT_ID = "api-gateway";
/**
* 认证服务器许可我们的网关的client_secret(需要在oauth_client_details表中配置)
*/
private static final String CLIENT_SECRET = "tlmall";
/**
* 认证服务器暴露的获取token_key的地址
*/
private static final String AUTH_TOKEN_KEY_URL = "http://tulingmall-authcenter/oauth/token_key";
/**
* 请求头中的 token的开始
*/
private static final String AUTH_HEADER = "bearer ";
/**
* 方法实现说明: 通过远程调用获取认证服务器颁发jwt的解析的key
* @author:smlz
* @param restTemplate 远程调用的操作类
* @return: tokenKey 解析jwt的tokenKey
* @exception:
* @date:2020/1/22 11:31
*/
private static String getTokenKeyByRemoteCall(RestTemplate restTemplate) throws GateWayException {
//第一步:封装请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setBasicAuth(CLIENT_ID,CLIENT_SECRET);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(null, headers);
//第二步:远程调用获取token_key
try {
ResponseEntity<Map> response = restTemplate.exchange(AUTH_TOKEN_KEY_URL, HttpMethod.GET, entity, Map.class);
String tokenKey = response.getBody().get("value").toString();
log.info("去认证服务器获取Token_Key:{}",tokenKey);
return tokenKey;
}catch (Exception e) {
log.error("远程调用认证服务器获取Token_Key失败:{}",e.getMessage());
throw new GateWayException(ResultCode.GET_TOKEN_KEY_ERROR);
}
}
/**
* 方法实现说明:生成公钥
* @author:smlz
* @param restTemplate:远程调用操作类
* @return: PublicKey 公钥对象
* @exception:
* @date:2020/1/22 11:52
*/
public static PublicKey genPulicKey(RestTemplate restTemplate) throws GateWayException {
String tokenKey = getTokenKeyByRemoteCall(restTemplate);
try{
//把获取的公钥开头和结尾替换掉
String dealTokenKey =tokenKey.replaceAll("\\-*BEGIN PUBLIC KEY\\-*", "").replaceAll("\\-*END PUBLIC KEY\\-*", "").trim();
java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(dealTokenKey));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
log.info("生成公钥:{}",publicKey);
return publicKey;
}catch (Exception e) {
log.info("生成公钥异常:{}",e.getMessage());
throw new GateWayException(ResultCode.GEN_PUBLIC_KEY_ERROR);
}
}
public static Claims validateJwtToken(String authHeader,PublicKey publicKey) {
String token =null ;
try{
token = StringUtils.substringAfter(authHeader, AUTH_HEADER);
Jwt<JwsHeader, Claims> parseClaimsJwt = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
Claims claims = parseClaimsJwt.getBody();
//log.info("claims:{}",claims);
return claims;
}catch(Exception e){
log.error("校验token异常:{},异常信息:{}",token,e.getMessage());
throw new GateWayException(ResultCode.JWT_TOKEN_EXPIRE);
}
}
}

View File

@@ -0,0 +1,66 @@
server:
port: 8888
#spring:
# application:
# name: tulingmall-gateway
# cloud:
# gateway:
# discovery:
# locator:
# lower-case-service-id: true
# enabled: true
# routes:
# - id: tulingmall-authcenter
# uri: lb://tulingmall-authcenter
# predicates:
# - Path=/oauth/**
# - id: tulingmall-member
# uri: lb://tulingmall-member
# predicates:
# - Path=/sso/**,/member/**
# - id: tulingmall-order-curr
# uri: lb://tulingmall-order-curr
# predicates:
# - Path=/order/**
# - id: tulingmall-cart
# uri: lb://tulingmall-cart
# predicates:
# - Path=/cart/**
# - id: tulingmall-product
# uri: lb://tulingmall-product
# predicates:
# - Path=/pms/**
# - id: tulingmall-coupons
# uri: lb://tulingmall-coupons
# predicates:
# - Path=/coupon/**
# - id: tulingmall-portal
# uri: lb://tulingmall-portal
# predicates:
# - Path=/home/**,/portal/**
# nacos:
# discovery:
# server-addr: 192.168.65.206:8848,192.168.65.209:8848,192.168.65.210:8848
#
#management: #开启SpringBoot Admin的监控
# endpoints:
# web:
# exposure:
# include: '*'
# endpoint:
# health:
# show-details: always
logging:
level:
org.springframework.cloud.gateway: debug
#tuling:
# gateway:
# shouldSkipUrls:
# - /oauth/**
# - /sso/**
# - /home/**
# - /portal/commentlist/**
# - /order/paySuccess/**
# - /pms/**
# - /static/qrcode/**

View File

@@ -0,0 +1,53 @@
server:
port: 8888
spring:
application:
name: tulingmall-gateway
cloud:
gateway:
discovery:
locator:
lower-case-service-id: true
enabled: true
routes:
- id: tulingmall-authcenter
uri: lb://tulingmall-authcenter
predicates:
- Path=/oauth/**
- id: tulingmall-member
uri: lb://tulingmall-member
predicates:
- Path=/sso/**,/member/**
- id: tulingmall-order
uri: lb://tulingmall-order
predicates:
- Path=/cart/**,/order/**
- id: tulingmall-product
uri: lb://tulingmall-product
predicates:
- Path=/pms/**
- id: tulingmall-coupons
uri: lb://tulingmall-coupons
predicates:
- Path=/coupon/**
nacos:
discovery:
server-addr: localhost:8848
management: #开启SpringBoot Admin的监控
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
logging:
level:
org.springframework.cloud.gateway: debug
tuling:
gateway:
shouldSkipUrls:
- /oauth/**
- /sso/**

View File

@@ -0,0 +1,19 @@
spring:
application:
name: tulingmall-gateway
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848 #配置中心的地址
file-extension: yml #配置文件结尾的配置
shared-configs[0]:
data-id: tulingmall-nacos.yml
group: DEFAULT_GROUP
refresh: true
shared-configs[1]:
data-id: tulingmall-db-common.yml
group: DEFAULT_GROUP
refresh: true
profiles:
active: dev

View File

@@ -0,0 +1,14 @@
spring:
application:
name: tulingmall-gateway
cloud:
nacos:
config:
server-addr: 192.168.159.8:8848 #配置中心的地址
file-extension: yml #配置文件结尾的配置
shared-dataids: tulingmall-common.yml #图灵商城公共配置
discovery:
server-addr: 192.168.159.8:8848
profiles:
active: dev

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This is the JRebel configuration file. It maps the running application to your IDE workspace, enabling JRebel reloading for this project.
Refer to https://manuals.jrebel.com/jrebel/standalone/config.html for more information.
-->
<application generated-by="intellij" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.zeroturnaround.com" xsi:schemaLocation="http://www.zeroturnaround.com http://update.zeroturnaround.com/jrebel/rebel-2_3.xsd">
<id>tulingmall-gateway</id>
<classpath>
<dir name="D:/GitSource/tmallV5/tulingmall-gateway/target/classes">
</dir>
</classpath>
</application>