初始化
This commit is contained in:
19
pqs-gateway/src/main/java/com/njcn/gateway/GatewayMain.java
Normal file
19
pqs-gateway/src/main/java/com/njcn/gateway/GatewayMain.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.njcn.gateway;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
|
||||
/**
|
||||
* @author hongawen
|
||||
* @version 1.0.0
|
||||
* @date 2021年05月10日 14:03
|
||||
*/
|
||||
@EnableFeignClients
|
||||
@SpringBootApplication
|
||||
public class GatewayMain {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(GatewayMain.class,args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.njcn.gateway.config;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import com.njcn.common.pojo.constant.SecurityConstants;
|
||||
import com.njcn.gateway.enums.GateWayEnum;
|
||||
import com.njcn.gateway.security.AuthorizationManager;
|
||||
import com.njcn.gateway.utils.ResponseUtils;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
/**
|
||||
* @author hongawen
|
||||
* 资源服务器配置
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
public class ResourceServerConfig {
|
||||
|
||||
private final AuthorizationManager authorizationManager;
|
||||
|
||||
private final WhiteListConfig whiteListConfig;
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
||||
|
||||
http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter())
|
||||
// 本地获取公钥
|
||||
.publicKey(rsaPublicKey());
|
||||
|
||||
http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());
|
||||
|
||||
http.oauth2ResourceServer().authenticationEntryPoint(authenticationEntryPoint());
|
||||
|
||||
http.authorizeExchange()
|
||||
.pathMatchers(Convert.toStrArray(whiteListConfig.getUrls())).permitAll()
|
||||
.anyExchange().access(authorizationManager)
|
||||
.and()
|
||||
.exceptionHandling()
|
||||
// 处理未授权
|
||||
.accessDeniedHandler(accessDeniedHandler())
|
||||
// 处理未认证
|
||||
.authenticationEntryPoint(authenticationEntryPoint())
|
||||
.and().csrf().disable();
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 访问拒绝处理器
|
||||
*/
|
||||
@Bean
|
||||
ServerAccessDeniedHandler accessDeniedHandler() {
|
||||
return (exchange, denied) -> Mono.defer(() -> Mono.just(exchange.getResponse()))
|
||||
.flatMap(response -> ResponseUtils.writeErrorInfo(response, GateWayEnum.NO_AUTHORIZATION));
|
||||
}
|
||||
|
||||
/**
|
||||
* token无效或者已过期自定义响应
|
||||
*/
|
||||
@Bean
|
||||
ServerAuthenticationEntryPoint authenticationEntryPoint() {
|
||||
return (exchange, e) -> Mono.defer(() -> Mono.just(exchange.getResponse()))
|
||||
.flatMap(response -> ResponseUtils.writeErrorInfo(response, GateWayEnum.ACCESS_TOKEN_EXPIRE_JWT));
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://blog.csdn.net/qq_24230139/article/details/105091273
|
||||
* ServerHttpSecurity没有将jwt中authorities的负载部分当做Authentication
|
||||
* 需要把jwt的Claim中的authorities加入
|
||||
* 方案:重新定义权限管理器,默认转换器JwtGrantedAuthoritiesConverter
|
||||
*/
|
||||
@Bean
|
||||
public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
|
||||
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
|
||||
jwtGrantedAuthoritiesConverter.setAuthorityPrefix(SecurityConstants.AUTHORITY_PREFIX);
|
||||
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(SecurityConstants.JWT_AUTHORITIES_KEY);
|
||||
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
|
||||
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
|
||||
return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地获取JWT验签公钥
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Bean
|
||||
public RSAPublicKey rsaPublicKey() {
|
||||
Resource resource = new ClassPathResource("public.key");
|
||||
InputStream is = resource.getInputStream();
|
||||
String publicKeyData = IoUtil.read(is).toString();
|
||||
X509EncodedKeySpec keySpec = new X509EncodedKeySpec((Base64.decode(publicKeyData)));
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
return (RSAPublicKey) keyFactory.generatePublic(keySpec);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.njcn.gateway.config;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.gateway.config.GatewayProperties;
|
||||
import org.springframework.cloud.gateway.route.RouteLocator;
|
||||
import org.springframework.cloud.gateway.support.NameUtils;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Component;
|
||||
import springfox.documentation.swagger.web.SwaggerResource;
|
||||
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* @author hongawen
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@Primary
|
||||
@AllArgsConstructor
|
||||
public class SwaggerResourceConfig implements SwaggerResourcesProvider {
|
||||
|
||||
private final RouteLocator routeLocator;
|
||||
private final GatewayProperties gatewayProperties;
|
||||
|
||||
|
||||
@Override
|
||||
public List<SwaggerResource> get() {
|
||||
List<SwaggerResource> resources = new ArrayList<>();
|
||||
List<String> routes = new ArrayList<>();
|
||||
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
|
||||
gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId())).forEach(route -> {
|
||||
route.getPredicates().stream()
|
||||
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
|
||||
.forEach(predicateDefinition -> resources.add(swaggerResource(route.getId(),
|
||||
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
|
||||
.replace("**", "v2/api-docs"))));
|
||||
});
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
private SwaggerResource swaggerResource(String name, String location) {
|
||||
log.info("name:{},location:{}",name,location);
|
||||
SwaggerResource swaggerResource = new SwaggerResource();
|
||||
swaggerResource.setName(name);
|
||||
swaggerResource.setLocation(location);
|
||||
swaggerResource.setSwaggerVersion("2.0");
|
||||
return swaggerResource;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.njcn.gateway.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 网关白名单配置
|
||||
* @author hongawen
|
||||
* @version 1.0.0
|
||||
* @date 2021年05月11日 10:33
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "whitelist")
|
||||
public class WhiteListConfig {
|
||||
|
||||
private List<String> urls;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.njcn.gateway.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author hongawen
|
||||
* @version 1.0.0
|
||||
* @date 2021年06月04日 11:17
|
||||
*/
|
||||
@Getter
|
||||
public enum GateWayEnum {
|
||||
|
||||
/**
|
||||
* A0200 ~ A0249 作用于网关业务枚举
|
||||
* 网关业务枚举响应
|
||||
*/
|
||||
NO_AUTHORIZATION("A0201", "访问未授权"),
|
||||
|
||||
//特殊的code,与common中的code保持一致
|
||||
TOKEN_EXPIRE_JWT("A0024", "token已过期"),
|
||||
|
||||
ACCESS_TOKEN_EXPIRE_JWT("A0202", "access_token已过期"),
|
||||
|
||||
TOKEN_FORBIDDEN_JWT("A0203", "token已被禁止访问");
|
||||
|
||||
|
||||
private final String code;
|
||||
|
||||
private final String message;
|
||||
|
||||
GateWayEnum(String code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.njcn.gateway.filter;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.alibaba.nacos.common.utils.CollectionUtils;
|
||||
import com.nimbusds.jose.JWSObject;
|
||||
import com.njcn.common.pojo.constant.SecurityConstants;
|
||||
import com.njcn.common.pojo.dto.UserTokenInfo;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.gateway.enums.GateWayEnum;
|
||||
import com.njcn.gateway.utils.IpUtils;
|
||||
import com.njcn.gateway.utils.ResponseUtils;
|
||||
import com.njcn.redis.utils.RedisUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 全局过滤器
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AuthGlobalFilter implements GlobalFilter, Ordered {
|
||||
|
||||
private final static List<String> USER_AGENT_IP = Arrays.asList("/pqs-auth/auth/getImgCode", "/pqs-auth/oauth/token", "/user-boot/user/generateSm2Key", "/user-boot/user/updateFirstPassword");
|
||||
|
||||
private final RedisUtil redisUtil;
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
String realIp = IpUtils.getRealIpAddress(request);
|
||||
// 非JWT或者JWT为空不作处理
|
||||
String token = request.getHeaders().getFirst(SecurityConstants.AUTHORIZATION_KEY);
|
||||
String path = request.getURI().getPath();
|
||||
if (StrUtil.isBlank(token) || !token.startsWith(SecurityConstants.AUTHORIZATION_PREFIX)) {
|
||||
// String path = request.getURI().getPath();
|
||||
//未认证且这些请求需要
|
||||
if (USER_AGENT_IP.contains(path)) {
|
||||
request = exchange.getRequest().mutate()
|
||||
.header(SecurityConstants.REQUEST_HEADER_KEY_CLIENT_REAL_IP, realIp)
|
||||
.build();
|
||||
return chain.filter(exchange.mutate().request(request).build());
|
||||
}
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
// 解析JWT获取jti,以jti为key判断redis的黑名单列表是否存在,存在拦截响应token失效
|
||||
token = token.replace(SecurityConstants.AUTHORIZATION_PREFIX, Strings.EMPTY);
|
||||
JWSObject jwsObject = JWSObject.parse(token);
|
||||
String payload = jwsObject.getPayload().toString();
|
||||
JSONObject jsonObject = JSONUtil.parseObj(payload);
|
||||
String userIndex = jsonObject.getStr(SecurityConstants.USER_INDEX_KEY);
|
||||
String jti = jsonObject.getStr(SecurityConstants.JWT_JTI);
|
||||
String blackUserKey = SecurityConstants.TOKEN_BLACKLIST_PREFIX + userIndex;
|
||||
List<UserTokenInfo> blackUsers = (List<UserTokenInfo>) redisUtil.getObjectByKey(blackUserKey);
|
||||
if (CollectionUtils.isNotEmpty(blackUsers)) {
|
||||
for (UserTokenInfo blackUser : blackUsers) {
|
||||
//存在当前的刷新token,则抛出业务异常
|
||||
if(blackUser.getAccessTokenJti().equalsIgnoreCase(jti)){
|
||||
return ResponseUtils.writeErrorInfo(response, GateWayEnum.TOKEN_EXPIRE_JWT);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 存在token且不是黑名单,request写入JWT的载体信息
|
||||
request = exchange.getRequest().mutate()
|
||||
.header(SecurityConstants.JWT_PAYLOAD_KEY, URLEncoder.encode(payload, StandardCharsets.UTF_8.toString()))
|
||||
.header(SecurityConstants.REQUEST_HEADER_KEY_CLIENT_REAL_IP, realIp)
|
||||
.build();
|
||||
exchange = exchange.mutate().request(request).build();
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.njcn.gateway.filter;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* @author hongawen
|
||||
* @version 1.0.0
|
||||
* @date 2021年05月11日 10:33
|
||||
*/
|
||||
@Component
|
||||
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
|
||||
|
||||
private static final String HEADER_NAME = "X-Forwarded-Prefix";
|
||||
|
||||
private static final String URI = "/v2/api-docs";
|
||||
|
||||
@Override
|
||||
public GatewayFilter apply(Object config) {
|
||||
return (exchange, chain) -> {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String path = request.getURI().getPath();
|
||||
if (!StringUtils.endsWithIgnoreCase(path, URI)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
String basePath = path.substring(0, path.lastIndexOf(URI));
|
||||
ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
|
||||
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
|
||||
return chain.filter(newExchange);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.njcn.gateway.handler;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Mono;
|
||||
import springfox.documentation.swagger.web.*;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* * @author hongawen
|
||||
* * @version 1.0.0
|
||||
* * @createTime 2021年05月10日 14:03
|
||||
*/
|
||||
@RestController
|
||||
public class SwaggerHandler {
|
||||
|
||||
@Autowired(required = false)
|
||||
private SecurityConfiguration securityConfiguration;
|
||||
|
||||
@Autowired(required = false)
|
||||
private UiConfiguration uiConfiguration;
|
||||
|
||||
private final SwaggerResourcesProvider swaggerResources;
|
||||
|
||||
@Autowired
|
||||
public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
|
||||
this.swaggerResources = swaggerResources;
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/swagger-resources/configuration/security")
|
||||
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
|
||||
return Mono.just(new ResponseEntity<>(
|
||||
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
|
||||
}
|
||||
|
||||
@GetMapping("/swagger-resources/configuration/ui")
|
||||
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
|
||||
return Mono.just(new ResponseEntity<>(
|
||||
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
|
||||
}
|
||||
|
||||
@GetMapping("/swagger-resources")
|
||||
public Mono<ResponseEntity> swaggerResources() {
|
||||
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.njcn.gateway.security;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.njcn.redis.pojo.enums.RedisKeyEnum;
|
||||
import com.njcn.redis.utils.RedisUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.web.server.authorization.AuthorizationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author hongawen
|
||||
* 鉴权管理器
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
|
||||
|
||||
private final RedisUtil redisUtil;
|
||||
|
||||
@Override
|
||||
public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
|
||||
PathMatcher pathMatcher = new AntPathMatcher();
|
||||
ServerHttpRequest request = authorizationContext.getExchange().getRequest();
|
||||
//不追究请求方式,如果需追求,需要在数据库中添加请求方式的字段request.getMethodValue()
|
||||
String restPath = request.getURI().getPath();
|
||||
log.info("请求路径:{}", restPath);
|
||||
// 对应跨域的预检请求直接放行
|
||||
// if (request.getMethod() == HttpMethod.OPTIONS) {
|
||||
// return Mono.just(new AuthorizationDecision(true));
|
||||
// }
|
||||
// 是否需要鉴权,默认未设置拦截规则需要鉴权
|
||||
boolean requireCheck = true;
|
||||
//查看是否为公共资源
|
||||
List<String> publicFunctions = (List<String>) redisUtil.getObjectByKey(RedisKeyEnum.PUBLIC_FUNCTIONS_KEY.getKey());
|
||||
if (CollectionUtil.isNotEmpty(publicFunctions) && publicFunctions.contains(restPath)) {
|
||||
requireCheck = false;
|
||||
}
|
||||
// 从缓存取资源权限角色关系列表
|
||||
Object roleFunctionsObject = redisUtil.getObjectByKey(RedisKeyEnum.ROLE_FUNCTION_KEY.getKey());
|
||||
if (!Objects.isNull(roleFunctionsObject)) {
|
||||
Map<String, List<String>> roleFunctions = (Map<String, List<String>>) roleFunctionsObject;
|
||||
//当前资源需要的角色
|
||||
Set<String> authorities = new HashSet<>();
|
||||
Set<String> roleNames = roleFunctions.keySet();
|
||||
roleNames.forEach(roleName -> {
|
||||
List<String> functionPaths = roleFunctions.get(roleName);
|
||||
if (CollectionUtil.isNotEmpty(functionPaths) && functionPaths.contains(restPath)) {
|
||||
authorities.add(roleName);
|
||||
}
|
||||
});
|
||||
boolean finalRequireCheck = requireCheck;
|
||||
return mono
|
||||
.filter(Authentication::isAuthenticated)
|
||||
.flatMapIterable(Authentication::getAuthorities)
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.any(roleId -> {
|
||||
if (!finalRequireCheck || roleId.equals("ROLE_root")) {
|
||||
return true;
|
||||
} else {
|
||||
return authorities.contains(roleId);
|
||||
}
|
||||
})
|
||||
.map(AuthorizationDecision::new)
|
||||
.defaultIfEmpty(new AuthorizationDecision(false));
|
||||
}
|
||||
return Mono.just(new AuthorizationDecision(true));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.njcn.gateway.utils;
|
||||
|
||||
import com.njcn.common.pojo.response.HttpResult;
|
||||
import com.njcn.common.utils.HttpResultUtil;
|
||||
import com.njcn.gateway.enums.GateWayEnum;
|
||||
|
||||
/**
|
||||
* @author hongawen
|
||||
* @version 1.0.0
|
||||
* @date 2021年06月04日 11:22
|
||||
*/
|
||||
public class GatePubUtils {
|
||||
|
||||
/**
|
||||
* 通用结果集
|
||||
*/
|
||||
public static <T> HttpResult<T> assembleGateWayResponseResult(GateWayEnum responseEnum, T result, String methodDescribe) {
|
||||
return HttpResultUtil.assembleResult(responseEnum.getCode(), result, methodDescribe + responseEnum.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.njcn.gateway.utils;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author hongawen
|
||||
* @version 1.0.0
|
||||
* @date 2021年05月25日 11:25
|
||||
*/
|
||||
@Slf4j
|
||||
public class IpUtils {
|
||||
|
||||
|
||||
private static final String UNKNOWN = "unknown";
|
||||
private static final String LOCALHOST_IPV4 = "127.0.0.1";
|
||||
private static final String LOCALHOST_IPV6 = "0:0:0:0:0:0:0:1";
|
||||
private static final String SEPARATOR = ",";
|
||||
|
||||
private static final String HEADER_X_FORWARDED_FOR = "x-forwarded-for";
|
||||
private static final String HEADER_PROXY_CLIENT_IP = "Proxy-Client-IP";
|
||||
private static final String HEADER_WL_PROXY_CLIENT_IP = "WL-Proxy-Client-IP";
|
||||
|
||||
private static final int IP_LEN = 15;
|
||||
|
||||
/**
|
||||
* 获取用户真实IP地址,不直接使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址,
|
||||
* <p>
|
||||
* 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢?
|
||||
* 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。
|
||||
* <p>
|
||||
* 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130,
|
||||
* 192.168.1.100
|
||||
* <p>
|
||||
* 用户真实IP为: 192.168.1.110
|
||||
*/
|
||||
public static String getRealIpAddress(ServerHttpRequest request) {
|
||||
HttpHeaders headers = request.getHeaders();
|
||||
String ipAddress = headers.getFirst(HEADER_X_FORWARDED_FOR);
|
||||
if (StrUtil.isBlankIfStr(ipAddress) || UNKNOWN.equalsIgnoreCase(ipAddress)) {
|
||||
ipAddress = headers.getFirst(HEADER_PROXY_CLIENT_IP);
|
||||
}
|
||||
if (StrUtil.isBlankIfStr(ipAddress) || UNKNOWN.equalsIgnoreCase(ipAddress)) {
|
||||
ipAddress = headers.getFirst(HEADER_WL_PROXY_CLIENT_IP);
|
||||
}
|
||||
if (StrUtil.isBlankIfStr(ipAddress) || UNKNOWN.equalsIgnoreCase(ipAddress)) {
|
||||
ipAddress = Optional.ofNullable(request.getRemoteAddress())
|
||||
.map(address -> address.getAddress().getHostAddress())
|
||||
.orElse("");
|
||||
if (LOCALHOST_IPV4.equals(ipAddress) || LOCALHOST_IPV6.equals(ipAddress)) {
|
||||
// 根据网卡取本机配置的IP
|
||||
try {
|
||||
InetAddress inet = InetAddress.getLocalHost();
|
||||
ipAddress = inet.getHostAddress();
|
||||
} catch (UnknownHostException e) {
|
||||
log.error("解析请求IP失败,{}", Stream.of(e.getStackTrace()).map(StackTraceElement::toString).collect(Collectors.joining(StrUtil.CRLF)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
|
||||
if (ipAddress != null && ipAddress.length() > IP_LEN) {
|
||||
int index = ipAddress.indexOf(SEPARATOR);
|
||||
if (index > 0) {
|
||||
ipAddress = ipAddress.substring(0, index);
|
||||
}
|
||||
}
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端端口
|
||||
*/
|
||||
public static Integer getPort(ServerHttpRequest request) {
|
||||
return request.getRemoteAddress().getPort();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.njcn.gateway.utils;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.njcn.common.pojo.response.HttpResult;
|
||||
import com.njcn.common.utils.HttpResultUtil;
|
||||
import com.njcn.gateway.enums.GateWayEnum;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* @author hongawen
|
||||
* @version 1.0.0
|
||||
* @date 2021年12月16日 10:26
|
||||
*/
|
||||
public class ResponseUtils {
|
||||
|
||||
public static Mono<Void> writeErrorInfo(ServerHttpResponse response, GateWayEnum gateWayEnum) {
|
||||
// switch (gateWayEnum) {
|
||||
// case NO_AUTHORIZATION:
|
||||
// case PARSE_TOKEN_EXPIRE_JWT:
|
||||
// response.setStatusCode(HttpStatus.OK);
|
||||
// break;
|
||||
// case PARSE_TOKEN_FORBIDDEN_JWT:
|
||||
// response.setStatusCode(HttpStatus.FORBIDDEN);
|
||||
// break;
|
||||
// default:
|
||||
// response.setStatusCode(HttpStatus.BAD_REQUEST);
|
||||
// break;
|
||||
// }
|
||||
response.setStatusCode(HttpStatus.OK);
|
||||
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||
response.getHeaders().set("Access-Control-Allow-Origin", "*");
|
||||
response.getHeaders().set("Cache-Control", "no-cache");
|
||||
String body = JSONUtil.toJsonStr(
|
||||
HttpResultUtil.assembleResult(gateWayEnum.getCode(), null, StringUtil.EMPTY + gateWayEnum.getMessage())
|
||||
);
|
||||
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
|
||||
return response.writeWith(Mono.just(buffer))
|
||||
.doOnError(error -> DataBufferUtils.release(buffer));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
138
pqs-gateway/src/main/resources/bootstrap.yml
Normal file
138
pqs-gateway/src/main/resources/bootstrap.yml
Normal file
@@ -0,0 +1,138 @@
|
||||
#当前服务的基本信息
|
||||
microservice:
|
||||
ename: @artifactId@
|
||||
name: "@name@"
|
||||
version: @version@
|
||||
sentinel:
|
||||
url: @sentinel.url@
|
||||
gateway:
|
||||
url: @gateway.url@
|
||||
server:
|
||||
port: 10215
|
||||
spring:
|
||||
application:
|
||||
name: @artifactId@
|
||||
main:
|
||||
allow-bean-definition-overriding: true
|
||||
#nacos注册中心以及配置中心的指定
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: @nacos.url@
|
||||
namespace: @nacos.namespace@
|
||||
config:
|
||||
server-addr: @nacos.url@
|
||||
namespace: @nacos.namespace@
|
||||
file-extension: yaml
|
||||
shared-configs:
|
||||
- data-id: share-config.yaml
|
||||
refresh: true
|
||||
- data-Id: share-config-datasource-db.yaml
|
||||
refresh: true
|
||||
gateway:
|
||||
globalcors:
|
||||
corsConfigurations:
|
||||
'[/**]':
|
||||
allowCredentials: true
|
||||
exposedHeaders: "Content-Disposition,Content-Type,Cache-Control"
|
||||
allowedHeaders: "*"
|
||||
allowedOrigins: "*"
|
||||
allowedMethods: "*"
|
||||
discovery:
|
||||
locator:
|
||||
# 开启自动代理 (自动装载从配置中心serviceId)
|
||||
enabled: true
|
||||
# 服务id为true --> 这样小写服务就可访问了
|
||||
lower-case-service-id: true
|
||||
routes:
|
||||
- id: pqs-auth
|
||||
uri: lb://pqs-auth
|
||||
predicates:
|
||||
- Path=/pqs-auth/**
|
||||
filters:
|
||||
- SwaggerHeaderFilter
|
||||
- StripPrefix=1
|
||||
- id: user-boot
|
||||
uri: lb://user-boot
|
||||
predicates:
|
||||
- Path=/user-boot/**
|
||||
filters:
|
||||
- SwaggerHeaderFilter
|
||||
- StripPrefix=1
|
||||
- id: device-boot
|
||||
uri: lb://device-boot
|
||||
predicates:
|
||||
- Path=/device-boot/**
|
||||
filters:
|
||||
- SwaggerHeaderFilter
|
||||
- StripPrefix=1
|
||||
- id: system-boot
|
||||
uri: lb://system-boot
|
||||
predicates:
|
||||
- Path=/system-boot/**
|
||||
filters:
|
||||
- SwaggerHeaderFilter
|
||||
- StripPrefix=1
|
||||
- id: harmonic-boot
|
||||
uri: lb://harmonic-boot
|
||||
predicates:
|
||||
- Path=/harmonic-boot/**
|
||||
filters:
|
||||
- SwaggerHeaderFilter
|
||||
- StripPrefix=1
|
||||
- id: energy-boot
|
||||
uri: lb://energy-boot
|
||||
predicates:
|
||||
- Path=/energy-boot/**
|
||||
filters:
|
||||
- SwaggerHeaderFilter
|
||||
- StripPrefix=1
|
||||
- id: event-boot
|
||||
uri: lb://event-boot
|
||||
predicates:
|
||||
- Path=/event-boot/**
|
||||
filters:
|
||||
- SwaggerHeaderFilter
|
||||
- StripPrefix=1
|
||||
|
||||
#项目日志的配置
|
||||
logging:
|
||||
config: http://@nacos.url@/nacos/v1/cs/configs?tenant=@nacos.namespace@&group=DEFAULT_GROUP&dataId=logback.xml
|
||||
level:
|
||||
root: info
|
||||
|
||||
whitelist:
|
||||
urls:
|
||||
- /
|
||||
- /user-boot/user/generateSm2Key
|
||||
- /user-boot/theme/getTheme
|
||||
- /user-boot/user/updateFirstPassword
|
||||
- /pqs-auth/oauth/logout
|
||||
- /pqs-auth/oauth/token
|
||||
- /pqs-auth/auth/getImgCode
|
||||
- /pqs-auth/oauth/getPublicKey
|
||||
- /pqs-auth/judgeToken/guangZhou
|
||||
- /webjars/**
|
||||
- /doc.html
|
||||
- /swagger-resources/**
|
||||
- /*/v2/api-docs
|
||||
- /favicon.ico
|
||||
- /device-boot/**
|
||||
- /system-boot/**
|
||||
- /harmonic-boot/**
|
||||
- /energy-boot/**
|
||||
- /event-boot/**
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
7
pqs-gateway/src/main/resources/public.key
Normal file
7
pqs-gateway/src/main/resources/public.key
Normal file
@@ -0,0 +1,7 @@
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn0GHes2u9gE0rAS3S0zM
|
||||
20gJJdRr1yKPGs/yp7cTuz2wAYTsFdO2WKx+GS+DyHgBznJsbPS1KLpyAEJLRaGe
|
||||
xz7T6GPK7yJExxU2eq0gmOLwFa9NS8gtsKpfpAc4C4J/Vc8S6X4vDEEQBdREM+lP
|
||||
fmM4+694ev2+4hP0BbropMMrZXx49GxubmqGK1Ka2EUaHWrEG4Ik5ukQyYvNf8Ns
|
||||
6KcaIUP7hHoha/z5cZkjFOOpXBGwx3AQvkuaMkhtOnrMBvLmmiP4TXErnJPXPz8s
|
||||
xfeJXoz8T9aS+Dnvcpogqye2nam/CReLMikTXCPDDVyyT6sVNWVBfJZ/BPCR9gI1
|
||||
3wIDAQAB
|
||||
Reference in New Issue
Block a user