SpringCloud 2020.0.4 系列之 JWT用户鉴权

2024-05-31 23:58

本文主要是介绍SpringCloud 2020.0.4 系列之 JWT用户鉴权,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1. 概述

2. 开发 授权鉴权服务接口层 my-auth-api

3. 开发 授权鉴权服务 my-auth-service

4. 在网关层(Gateway工程)添加鉴权过滤器

5. 综述

6. 个人公众号


1. 概述

老话说的好:善待他人就是善待自己,虽然可能有所付出,但也能得到应有的收获。

言归正传,之前我们聊了 Gateway 组件,今天来聊一下如何使用 JWT 技术给用户授权,以及如果在 Gateway 工程使用自定义 filter 验证用户权限。

闲话不多说,直接上代码。

2. 开发 授权鉴权服务接口层 my-auth-api

2.1 主要依赖

    <artifactId>my-auth-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>

2.2 实体类

/*** 账户实体类*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Account implements java.io.Serializable {// 用户名private String userName;// tokenprivate String token;// 刷新tokenprivate String refreshToken;
}

/*** 响应实体类*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AuthResponse implements java.io.Serializable {// 账户private Account account;// 响应码private Integer code;
}

2.3 授权鉴权 Service 接口

/***  授权鉴权 Service 接口*/
@FeignClient("my-auth-service")
public interface AuthService {/*** 登录接口* @param userName  用户名* @param password  密码* @return*/@PostMapping("/login")AuthResponse login(@RequestParam("userName") String userName,@RequestParam("password") String password);/*** 校验token* @param token     token* @param userName  用户名* @return*/@GetMapping("/verify")AuthResponse verify(@RequestParam("token") String token,@RequestParam("userName") String userName);/*** 刷新token* @param refreshToken   刷新token*/@PostMapping("/refresh")AuthResponse refresh(@RequestParam("refreshToken") String refreshToken);
}

3. 开发 授权鉴权服务 my-auth-service

3.1 主要依赖

        <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-web</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.18.2</version></dependency><dependency><groupId>cn.zhuifengren</groupId><artifactId>my-auth-api</artifactId><version>${project.version}</version></dependency>

3.2 主要配置

server:port: 45000
spring:application:name: my-auth-serviceredis:database: 0host: 192.168.1.22port: 6379password: zhuifengreneureka:client:service-url:defaultZone: http://zhuifengren1:35000/eureka/,http://zhuifengren2:35001/eureka/    # Eureka Server的地址

3.3 启动类添加注解

@SpringBootApplication
@EnableDiscoveryClient

3.4 JWT 核心Service方法

/*** 获得 token* @param account   账户实体* @return*/public String token(Account account) {log.info("获取token");Date now = new Date();// 指定算法,KEY是自定义的秘钥Algorithm algorithm = Algorithm.HMAC256(KEY);// 生成tokenString token = JWT.create().withIssuer(ISSUER) // 发行人,自定义.withIssuedAt(now).withExpiresAt(new Date(now.getTime() + TOKEN_EXPIRES)) // 设置token过期时间.withClaim("userName", account.getUserName())   // 自定义属性.sign(algorithm);log.info(account.getUserName() + " token 生成成功");return token;}/*** 验证token* @param token* @param userName* @return*/public boolean verify(String token, String userName) {log.info("验证token");try {// 指定算法,KEY是自定义的秘钥Algorithm algorithm = Algorithm.HMAC256(KEY);// 验证tokenJWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER)     // 发行人,自定义.withClaim("userName", userName)   // 自定义属性.build();verifier.verify(token);return true;} catch (Exception ex) {log.error("验证失败", ex);return false;}}

3.5 授权鉴权业务Service

/*** 授权鉴权 Service*/
@RestController
@Slf4j
public class AuthServiceImpl implements AuthService {@Autowiredprivate JwtService jwtService;@Autowiredprivate RedisTemplate redisTemplate;/*** 登录* @param userName  用户名* @param password  密码* @return*/public AuthResponse login(@RequestParam("userName") String userName,@RequestParam("password") String password) {Account account = Account.builder().userName(userName).build();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(200)  // 200 代表成功.build();}/*** 刷新token* @param refreshToken   刷新token* @return*/public AuthResponse refresh(@RequestParam("refreshToken") String refreshToken) {Account account = (Account)redisTemplate.opsForValue().get(refreshToken);if(account == null) {return AuthResponse.builder().code(-1)       // -1 代表用户未找到.build();}String newToken = jwtService.token(account);account.setToken(newToken);account.setRefreshToken(UUID.randomUUID().toString());redisTemplate.delete(refreshToken);redisTemplate.opsForValue().set(account.getRefreshToken(), account);return AuthResponse.builder().account(account).code(200)  // 200 代表成功.build();}/*** 验证token* @param token     token* @param userName  用户名* @return*/public AuthResponse verify(@RequestParam("token") String token,@RequestParam("userName") String userName) {log.info("verify start");boolean isSuccess = jwtService.verify(token, userName);log.info("verify result:" + isSuccess);return AuthResponse.builder().code(isSuccess ? 200 : -2)     // -2 代表验证不通过.build();}
}

4. 在网关层(Gateway工程)添加鉴权过滤器

4.1 增加依赖

        <dependency><groupId>cn.zhuifengren</groupId><artifactId>my-auth-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></dependency>

4.2 启动类增加注解

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(clients = AuthService.class)

4.3 鉴权过滤器

@Slf4j
@Component
public class AuthFilter implements GatewayFilter, Ordered {private static final String AUTH = "Authorization";private static final String USER_NAME = "userName";@Autowiredprivate AuthService authService;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {log.info("开始验证");// 从 header 中得到 token 和 用户名ServerHttpRequest request = exchange.getRequest();HttpHeaders headers = request.getHeaders();String token = headers.getFirst(AUTH);String userName= headers.getFirst(USER_NAME);ServerHttpResponse response = exchange.getResponse();if(StringUtils.isBlank(token)) {log.error("token没有找到");response.setStatusCode(HttpStatus.UNAUTHORIZED);return response.setComplete();}// 验证用户名log.info("执行验证方法");AuthResponse resp = authService.verify(token, userName);       log.info("执行验证方法完毕");if(resp == null || resp.getCode() != 200) {log.error("无效的token");response.setStatusCode(HttpStatus.FORBIDDEN);return response.setComplete();}return chain.filter(exchange);}@Overridepublic int getOrder() {return 0;}
}

4.4 在路由规则中配置鉴权过滤器

这里我们随便找一个接口实验

@Configuration
public class GatewayConfig {@Bean@Orderpublic RouteLocator myRoutes(RouteLocatorBuilder builder, AuthFilter authFilter) {return builder.routes().route(r -> r.path("/business/**").and().method(HttpMethod.GET).filters(f -> f.stripPrefix(1).filter(authFilter)).uri("lb://MY-EUREKA-CLIENT")).build();}
}

4.5 block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3 错误解决

此时,启动 Gateway 工程,调用实验接口:

GET  http://Gateway IP:端口/business/eurekaClient/hello

此时 Gateway 工程会报如下错误:

java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:83) ~[reactor-core-3.4.11.jar:3.4.11]Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):*__checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]*__checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]*__checkpoint ⇢ HTTP GET "/business/eurekaClient/hello" [ExceptionHandlingWebHandler]

这是因为在自定义过滤器 AuthFilter 的 filter 方法中,不能同步的调用 Feign 接口,需要异步去调。

我们修改 AuthFilter 中的代码

将 AuthResponse resp = authService.verify(token, userName); 这行代码改为如下代码:

        CompletableFuture<AuthResponse> completableFuture = CompletableFuture.supplyAsync(()-> {return authService.verify(token, userName);});AuthResponse resp = null;try {resp = completableFuture.get();} catch (Exception ex) {log.error("调用验证接口错误", ex);}

4.6 feign.codec.DecodeException: No qualifying bean of type 'org.springframework.boot.autoconfigure.http.HttpMessageConverters' available 错误解决

我们重启 Gateway 服务,再次调用实验接口:

GET  http://Gateway IP:端口/business/eurekaClient/hello

此时 Feign 接口调通了,但 Gateway 工程报了如下错误:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.boot.autoconfigure.http.HttpMessageConverters' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1790) ~[spring-beans-5.3.12.jar:5.3.12]at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1346) ~[spring-beans-5.3.12.jar:5.3.12]at org.springframework.beans.factory.support.DefaultListableBeanFactory$DependencyObjectProvider.getObject(DefaultListableBeanFactory.java:1979) ~[spring-beans-5.3.12.jar:5.3.12]

似乎是 HttpMessageConverters 这个 Bean 没有找到,经查阅资料,我们在启动类中添加如下代码

    @Bean@ConditionalOnMissingBeanpublic HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));}

4.7 实验授权鉴权

1)再次重启 Gateway 工程

2)调用登录接口获取 token

POST http://Gateway IP:端口/my-auth-service/login?userName=zhangsan&password=12345

3)调用业务接口,将 token 和用户名放到 header 中,可以正常访问接口

5. 综述

今天聊了一下 JWT用户鉴权,希望可以对大家的工作有所帮助。

欢迎帮忙点赞、评论、转发、加关注 :)

关注追风人聊Java,每天更新Java干货。

6. 个人公众号

追风人聊Java,欢迎大家关注

这篇关于SpringCloud 2020.0.4 系列之 JWT用户鉴权的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1019412

相关文章

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

在Spring Boot中浅尝内存泄漏的实战记录

《在SpringBoot中浅尝内存泄漏的实战记录》本文给大家分享在SpringBoot中浅尝内存泄漏的实战记录,结合实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录使用静态集合持有对象引用,阻止GC回收关键点:可执行代码:验证:1,运行程序(启动时添加JVM参数限制堆大小):2,访问 htt

SpringBoot集成Milvus实现数据增删改查功能

《SpringBoot集成Milvus实现数据增删改查功能》milvus支持的语言比较多,支持python,Java,Go,node等开发语言,本文主要介绍如何使用Java语言,采用springboo... 目录1、Milvus基本概念2、添加maven依赖3、配置yml文件4、创建MilvusClient

浅析Java中如何优雅地处理null值

《浅析Java中如何优雅地处理null值》这篇文章主要为大家详细介绍了如何结合Lambda表达式和Optional,让Java更优雅地处理null值,感兴趣的小伙伴可以跟随小编一起学习一下... 目录场景 1:不为 null 则执行场景 2:不为 null 则返回,为 null 则返回特定值或抛出异常场景

SpringMVC获取请求参数的方法

《SpringMVC获取请求参数的方法》:本文主要介绍SpringMVC获取请求参数的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下... 目录1、通过ServletAPI获取2、通过控制器方法的形参获取请求参数3、@RequestParam4、@

SpringBoot应用中出现的Full GC问题的场景与解决

《SpringBoot应用中出现的FullGC问题的场景与解决》这篇文章主要为大家详细介绍了SpringBoot应用中出现的FullGC问题的场景与解决方法,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录Full GC的原理与触发条件原理触发条件对Spring Boot应用的影响示例代码优化建议结论F

springboot项目中常用的工具类和api详解

《springboot项目中常用的工具类和api详解》在SpringBoot项目中,开发者通常会依赖一些工具类和API来简化开发、提高效率,以下是一些常用的工具类及其典型应用场景,涵盖Spring原生... 目录1. Spring Framework 自带工具类(1) StringUtils(2) Coll

SpringBoot条件注解核心作用与使用场景详解

《SpringBoot条件注解核心作用与使用场景详解》SpringBoot的条件注解为开发者提供了强大的动态配置能力,理解其原理和适用场景是构建灵活、可扩展应用的关键,本文将系统梳理所有常用的条件注... 目录引言一、条件注解的核心机制二、SpringBoot内置条件注解详解1、@ConditionalOn

通过Spring层面进行事务回滚的实现

《通过Spring层面进行事务回滚的实现》本文主要介绍了通过Spring层面进行事务回滚的实现,包括声明式事务和编程式事务,具有一定的参考价值,感兴趣的可以了解一下... 目录声明式事务回滚:1. 基础注解配置2. 指定回滚异常类型3. ​不回滚特殊场景编程式事务回滚:1. ​使用 TransactionT

Spring LDAP目录服务的使用示例

《SpringLDAP目录服务的使用示例》本文主要介绍了SpringLDAP目录服务的使用示例... 目录引言一、Spring LDAP基础二、LdapTemplate详解三、LDAP对象映射四、基本LDAP操作4.1 查询操作4.2 添加操作4.3 修改操作4.4 删除操作五、认证与授权六、高级特性与最佳