SpringCloud Gateway 簡介
SpringCloud Gateway 是 Spring Cloud 的一個全新專案,該專案是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技術開發的網關,它旨在為微服務架構提供一種簡單有效的統一的 API 路由管理方式,
SpringCloud Gateway 作為 Spring Cloud 生態系統中的網關,目標是替代 Zuul,在Spring Cloud 2.0以上版本中,沒有對新版本的Zuul 2.0以上最新高性能版本進行集成,仍然還是使用的Zuul 2.0之前的非Reactor模式的老版本,而為了提升網關的性能,SpringCloud Gateway是基于WebFlux框架實作的,而WebFlux框架底層則使用了高性能的Reactor模式通信框架Netty,
Spring Cloud Gateway 的目標,不僅提供統一的路由方式,并且基于 Filter 鏈的方式提供了網關基本的功能,例如:安全,監控/指標,和限流,
提前宣告:Spring Cloud Gateway 底層使用了高性能的通信框架Netty,
SpringCloud Gatewayvszuul


專案截圖

springcloud 和springboot的版本
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
feign-client專案我在之前的文章已經復制過了不在復制
https://blog.csdn.net/qq_40297844/article/details/109777120
auth-service-api(介面層)
pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-demo</artifactId>
<groupId>com.demo</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>auth-service-api</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>
Account.java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Account implements Serializable {
private String username;
private String token;
private String refreshToken;
}
AuthResponse.java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AuthResponse {
private Account account;
private Long code;
}
AuthResponseCode.java
public class AuthResponseCode {
public static final Long SUCCESS = 1L;
public static final Long INCORRECT_PWD = 1000L;
public static final Long USER_NOT_FOUND = 1001L;
}
AuthService.java
@FeignClient("auth-service")
public interface AuthService {
@PostMapping("/login")
@ResponseBody
public AuthResponse login(@RequestParam("username") String username,
@RequestParam("password") String password);
@GetMapping("/verify")
public AuthResponse verify(@RequestParam("token") String token,
@RequestParam("username") String username);
@PostMapping("/refresh")
@ResponseBody
public AuthResponse refresh(@RequestParam("refresh") String refresh);
}
auth-service
pom.xml
```xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-demo</artifactId>
<groupId>com.demo</groupId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>auth-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.7.0</version>
</dependency>
<dependency>
<groupId>com.demo</groupId>
<artifactId>auth-service-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
application.properties
spring.application.name=auth-service
server.port=65100
eureka.client.serviceUrl.defaultZone=http://localhost:20000/eureka/
logging.file=${spring.application.name}.log
spring.redis.database=0
spring.redis.host=192.168.60.39
spring.redis.port=30379
spring.redis.password=zkxtRedis_2019
info.app.name=auth-service
info.app.description=test
management.security.enabled=false
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
AuthApplication.java
@SpringBootApplication
@EnableEurekaClient
public class AuthApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(AuthApplication.class)
.web(WebApplicationType.SERVLET)
.run(args);
}
}
Controller.java
@RestController
@Slf4j
public class Controller {
@Autowired
private JwtService jwtService;
@Autowired
private RedisTemplate redisTemplate;
@PostMapping("/login")
@ResponseBody
public AuthResponse login(@RequestParam String username,
@RequestParam String password) {
Account account = Account.builder()
.username(username)
.build();
// TODO 驗證username + password
String token = jwtService.token(account);
account.setToken(token);
account.setRefreshToken(UUID.randomUUID().toString());
redisTemplate.opsForValue().set(account.getRefreshToken(), account);
return AuthResponse.builder()
.account(account)
.code(SUCCESS)
.build();
}
@PostMapping("/refresh")
@ResponseBody
public AuthResponse refresh(@RequestParam String refreshToken) {
Account account = (Account) redisTemplate.opsForValue().get(refreshToken);
if (account == null) {
return AuthResponse.builder()
.code(USER_NOT_FOUND)
.build();
}
String jwt = jwtService.token(account);
account.setToken(jwt);
account.setRefreshToken(UUID.randomUUID().toString());
redisTemplate.delete(refreshToken);
redisTemplate.opsForValue().set(account.getRefreshToken(), account);
return AuthResponse.builder()
.account(account)
.code(SUCCESS)
.build();
}
@GetMapping("/verify")
public AuthResponse verify(@RequestParam String token,
@RequestParam String username) {
boolean success = jwtService.verify(token, username);
return AuthResponse.builder()
// TODO 此處最好用invalid token之類的錯誤資訊
.code(success ? SUCCESS : USER_NOT_FOUND)
.build();
}
}
JwtService.java
@Slf4j
@Service
public class JwtService {
// 生產環境不能這么用
private static final String KEY = "changeIt";
private static final String ISSUER = "yao";
private static final long TOKEN_EXP_TIME = 60000;
private static final String USER_NAME = "username";
/**
* 生成Token
*
* @param acct
* @return
*/
public String token(Account acct) {
Date now = new Date();
Algorithm algorithm = Algorithm.HMAC256(KEY);
String token = JWT.create()
.withIssuer(ISSUER)
.withIssuedAt(now)
.withExpiresAt(new Date(now.getTime() + TOKEN_EXP_TIME))
.withClaim(USER_NAME, acct.getUsername())
// .withClaim("ROLE", "")
.sign(algorithm);
log.info("jwt generated user={}", acct.getUsername());
return token;
}
/**
* 校驗Token
*
* @param token
* @param username
* @return
*/
public boolean verify(String token, String username) {
log.info("verifying jwt - username={}", username);
try {
Algorithm algorithm = Algorithm.HMAC256(KEY);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(ISSUER)
.withClaim(USER_NAME, username)
.build();
verifier.verify(token);
return true;
} catch (Exception e) {
log.error("auth failed", e);
return false;
}
}
}
gateway-sample
pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-demo</artifactId>
<groupId>com.demo</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>gateway-sample</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- 因為spring cloud gateway是基于webflux的,
如果非要web支持的話需要匯入spring-boot-starter-webflux而不是spring-boot-start-web-->
<dependency>
<groupId>com.imooc</groupId>
<artifactId>auth-service-api</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
</dependencies>
</project>
application.yml
在這里插入代碼片
GatewayApplication.java
spring:
application:
name: gateway-service
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: feignclient
uri: lb://FEIGN-CLIENT
predicates:
- Path=/yml/**
filters:
- StripPrefix=1
server:
port: 65009
eureka:
client:
serviceUrl:
defaultZone: http://localhost:20000/eureka/
management:
security:
enabled: false
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
AuthFilter.java
@Component("authFilter")
@Slf4j
public class AuthFilter implements GatewayFilter, Ordered {
private static final String AUTH = "Authorization";
private static final String USERNAME = "demo-user-name";
@Autowired
private AuthService authService;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("Auth start");
ServerHttpRequest request = exchange.getRequest();
HttpHeaders header = request.getHeaders();
String token = header.getFirst(AUTH);
String username = header.getFirst(USERNAME);
ServerHttpResponse response = exchange.getResponse();
if (StringUtils.isBlank(token)) {
log.error("token not found");
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
AuthResponse resp = authService.verify(token, username);
if (resp.getCode() != 1L) {
log.error("invalid token");
response.setStatusCode(HttpStatus.FORBIDDEN);
return response.setComplete();
}
// TODO 將用戶資訊存放在請求header中傳遞給下游業務
ServerHttpRequest.Builder mutate = request.mutate();
mutate.header("demo-user-name", username);
ServerHttpRequest buildReuqest = mutate.build();
//todo 如果回應中需要放資料,也可以放在response的header中
response.setStatusCode(HttpStatus.OK);
response.getHeaders().add("demo-user-name",username);
return chain.filter(exchange.mutate()
.request(buildReuqest)
.response(response)
.build());
}
@Override
public int getOrder() {
return 0;
}
}
GatewayConfiguration.java
@Configuration
public class GatewayConfiguration {
@Autowired
private AuthFilter authFilter;
@Bean
@Order
public RouteLocator customizedRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route(r -> r.path("/java/**")
.and().method(HttpMethod.GET)
.and().header("name")
.filters(f -> f.stripPrefix(1)
.addResponseHeader("java-param", "gateway-config")
.filter(timerFilter)
.filter(authFilter)
)
.uri("lb://FEIGN-CLIENT")
)
.build();
}
}
GatewayApplication.java
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
測驗
1.輸入用戶名和密碼 我這里就不做用戶名和密碼的校驗了 畢竟是一個demo主要是功能是gateway基于jwt實作用戶鑒權+GatewayFilter自定義攔截器
@PostMapping("/login")
@ResponseBody
public AuthResponse login(@RequestParam String username,
@RequestParam String password) {
Account account = Account.builder()
.username(username)
.build();
// TODO 驗證username + password
String token = jwtService.token(account);
account.setToken(token);
account.setRefreshToken(UUID.randomUUID().toString());
redisTemplate.opsForValue().set(account.getRefreshToken(), account);
return AuthResponse.builder()
.account(account)
.code(SUCCESS)
.build();
}
2.獲取到token
/**
* 生成Token
*
* @param acct
* @return
*/
public String token(Account acct) {
Date now = new Date();
Algorithm algorithm = Algorithm.HMAC256(KEY);
String token = JWT.create()
.withIssuer(ISSUER)
.withIssuedAt(now)
.withExpiresAt(new Date(now.getTime() + TOKEN_EXP_TIME))
.withClaim(USER_NAME, acct.getUsername())
// .withClaim("ROLE", "")
.sign(algorithm);
log.info("jwt generated user={}", acct.getUsername());
return token;
}
3.postman來一手

4.使用toKen鑒權成功
5.不加token的情況會回傳401控制臺報錯 token not found


6.錯誤的token 403 后臺invalid token


轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/229150.html
標籤:java
