spring cloud gateway基于jwt实现用户鉴权+GatewayFilter自定义拦截器(完整demo)
SpringCloud Gateway 简介SpringCloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。SpringCloud Gateway 作为 Spring Cloud 生态系统中
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
更多推荐
所有评论(0)