微服务鉴权中心客户端
鉴权中心客户端: fly-auth-client
pom 文件依赖
xml
<dependencies>
<dependency>
<groupId>com.github.itdachen.framework</groupId>
<artifactId>fly-core</artifactId>
</dependency>
<dependency>
<groupId>com.github.itdachen.framework</groupId>
<artifactId>fly-body-advice</artifactId>
</dependency>
<!-- openfeign 远程服务调用模块, openfeign 封装, RestTemplate 封装 -->
<dependency>
<groupId>com.github.itdachen.framework.cloud</groupId>
<artifactId>fly-cloud-openfeign</artifactId>
</dependency>
<!-- token 解析模块 -->
<dependency>
<groupId>com.github.itdachen.framework.cloud</groupId>
<artifactId>fly-cloud-jwt-parse</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
</dependencies>获取 token 公钥
openfeign 获取公钥
新建 IAuthorizedClientTokenSecretFeign 接口, 获取 token 公钥
@FeignClient(value = "${fly.cloud.auth.app.service-id}", configuration = {})
public interface IAuthorizedClientTokenSecretFeign {
@RequestMapping(value = "/auth/client/user/secret/key", method = RequestMethod.GET)
ServerResponse<String> getSecretPublicKey(@RequestParam("appId") String appId,
@RequestParam("appSecret") String appSecret) throws Exception;
}- fly.cloud.auth.app.service-id: 配置文件配置的认证中心服务名称
- 需要对
feign远程调用有一定的了解
同步 token 公钥
新建 AuthorizedClientTokenSecretRunner 类, 项目启动时, 初始化 Token 公钥
public class AuthorizedClientTokenSecretRunner implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(AuthorizedClientTokenSecretRunner.class);
@Autowired
private AuthClientTokenSecretKey authClientTokenSecretKey;
private final IAuthorizedClientTokenSecretFeign clientTokenSecretFeign;
private final FlyCloudAppClientProperties appClientProperties;
public AuthorizedClientTokenSecretRunner(IAuthorizedClientTokenSecretFeign clientTokenSecretFeign,
FlyCloudAppClientProperties appClientProperties) {
this.clientTokenSecretFeign = clientTokenSecretFeign;
this.appClientProperties = appClientProperties;
}
@Override
public void run(String... args) throws Exception {
logger.info("正在初始化用户 pubKey");
try {
refreshUserSecretKey();
logger.info("初始化用户 pubKey 完成");
} catch (Exception e) {
logger.error("初始化用户 pubKey 失败, 1 分钟后自动重试!", e);
}
}
/***
* 每隔一分钟同步一次
*
* @author 王大宸
* @date 2023/5/5 15:09
* @return void
*/
@Scheduled(cron = "0 0/1 * * * ?")
public void refreshUserSecretKey() throws Exception {
ServerResponse<String> res = clientTokenSecretFeign.getSecretPublicKey(
appClientProperties.getAppId(),
appClientProperties.getAppSecret()
);
if (res.getSuccess()) {
this.authClientTokenSecretKey.setTokenPublicKey(res.getData());
}
}
}配置 token 解析拦截器
新建 DefaultBootstrapWebMvcConfigurer 类, 配置 token 解析拦截器
public class DefaultBootstrapWebMvcConfigurer implements WebMvcConfigurer {
private static final Logger logger = LoggerFactory.getLogger(FlyCloudBootstrapWebMvcConfig.class);
private final IVerifyTicketTokenHelper verifyTicketTokenService;
private final IRequestPassMatchers requestPassMatchers;
public FlyCloudBootstrapWebMvcConfig(IVerifyTicketTokenHelper verifyTicketTokenService,
IRequestPassMatchers requestPassMatchers) {
this.verifyTicketTokenService = verifyTicketTokenService;
this.requestPassMatchers = requestPassMatchers;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
}
/***
* 拦截器配置
*
* @author 王大宸
* @date 2022/9/25 16:28
* @param registry registry
* @return void
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor())
.addPathPatterns("/**")
.excludePathPatterns(requestPassMatchers.passMatchers());
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(userAuthRestMethodArgumentResolver());
}
@Bean
public UserAuthRestMethodArgumentResolver userAuthRestMethodArgumentResolver() {
return new UserAuthRestMethodArgumentResolver();
}
/***
* token 解析拦截器
*
* @author 王大宸
* @date 2023/9/3 17:30
* @return com.github.itdachen.framework.cloud.jwt.parse.interceptor.UserAuthRestInterceptor
*/
@Bean
public UserAuthRestInterceptor authInterceptor() {
return new UserAuthRestInterceptor(verifyTicketTokenService);
}
}配置默认的拦截器 bean
@Configuration
public class DefaultWebMvcBeanConfigurer {
/**
* 解析 token 接口
*/
private final IVerifyTicketTokenHelper verifyTicketTokenService;
/**
* 获取不拦截的路径
*/
private final IRequestPassMatchers requestPassMatchers;
public DefaultWebMvcBeanConfigurer(IVerifyTicketTokenHelper verifyTicketTokenService,
IRequestPassMatchers requestPassMatchers) {
this.verifyTicketTokenService = verifyTicketTokenService;
this.requestPassMatchers = requestPassMatchers;
}
/***
* 配置默认的 WebMvcConfigurer 配置
* 如果有需要, 可以重新写一个类, 实现 WebMvcConfigurer 中的接口即可
* @author 王大宸
* @date 2023/9/3 17:56
* @return org.springframework.web.servlet.config.annotation.WebMvcConfigurer
*/
@Bean
@ConditionalOnMissingBean(WebMvcConfigurer.class)
public WebMvcConfigurer smsCodeSender() {
return new DefaultBootstrapWebMvcConfigurer(verifyTicketTokenService,requestPassMatchers );
}
}添加认证客户端注解
新建 EnableAuthClient 注解, 客户端启动时, 需要在 Spring Boot 启动类上添加 @EnableAuthClient 注解
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({AuthorizedClientTokenSecretRunner.class})
public @interface EnableAuthClient {
}注解启动时, 加载 AuthorizedClientTokenSecretRunner 类
- AuthorizedClientTokenSecretRunner: 从认证中心获取 token 公钥
- DefaultBootstrapWebMvcConfigurer: 添加系统默认拦截器, 在拦截器中解析 token 信息
- 如果需要自定义加载 token 公钥, 启动类上不需要添加
@EnableAuthClient注解 - token 解析公钥, 在项目初始化时, 需要存放在
AuthClientTokenSecretKey类中
服务端接口
微服务鉴权中心服务端 fly-auth-server, 编写获取公钥接口
@RestController
@IgnoreUserToken // 忽略 token 注解
@RequestMapping(value = "/client")
public class AuthAppClientUserKeyController {
private static final Logger logger = LoggerFactory.getLogger(AuthAppClientUserKeyController.class);
private final AuthClientTokenSecretKey authClientTokenSecretKey;
public AuthAppClientUserKeyController(AuthClientTokenSecretKey authClientTokenSecretKey) {
this.authClientTokenSecretKey = authClientTokenSecretKey;
}
/***
* 客户端同步用户公钥
*
* @author 王大宸
* @date 2023/5/5 15:01
* @param appId 客户端ID
* @param appSecret 客户端秘钥
* @return com.github.itdachen.framework.core.response.ServerResponse<java.lang.String>
*/
@GetMapping("/user/secret/key")
public ServerResponse<String> userSecretKey(@RequestParam(value = "appId") String appId,
@RequestParam(value = "appSecret") String appSecret) throws Exception {
logger.info("获取客户端 token 公钥, appId: {}, appSecret: {}", appId, appSecret);
// TODO 自行根据 appId 和 appSecret 校验是否能获取 token 公钥
return ServerResponse.okData(authClientTokenSecretKey.getTokenPublicKey());
}
}测试
新建 fly-auth-client-dome 模块
pom 依赖
xml
<dependencies>
<dependency>
<groupId>com.github.itdachen.framework</groupId>
<artifactId>fly-runner</artifactId>
</dependency>
<!-- 认证中心客户端 -->
<dependency>
<groupId>com.github.itdachen</groupId>
<artifactId>fly-auth-client</artifactId>
</dependency>
<!-- 項目启动容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<!-- nacos discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 新版本的微服务默认不加载 bootstrap.yml 文件, 需要添加 bootstrap 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!-- 健康检查 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>启动类
@EnableAuthClient // 启动认证客户端注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients({"com.github.itdachen"})
@ComponentScan(basePackages = {"com.github.itdachen"})
public class AuthClientDemoBootstrap {
public static void main(String[] args) {
SpringBootBootstrap.run(AuthClientDemoBootstrap.class);
}
}配置文件 bootstrap.yml
yaml
# 端口配置
server:
port: 8002
servlet:
context-path: /${spring.application.name}
# 认证中心配置
fly:
cloud:
auth:
token:
type: rsa # 指定 token 类型
app:
service-id: auth # 认证中心服务名称
app-id: ${spring.application.name}
app-secret: 123456
spring:
application:
name: auth-client-demo
main:
allow-circular-references: true
allow-bean-definition-overriding: true
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
cloud:
loadbalancer:
# 重试策略
retry:
maxRetriesOnSameServiceInstance: 0
maxRetriesOnNextServiceInstance: 0
clients:
service-product:
retry:
maxRetriesOnSameServiceInstance: 0
maxRetriesOnNextServiceInstance: 0
## 向 nacos 发起注册
nacos:
discovery:
username: nacos
password: nacos
enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
server-addr: 127.0.0.1:8848
namespace: 07656b3c-bb69-470a-a942-6ae27b5287cb
group: FLY_GROUP
metadata:
management:
context-path: ${server.servlet.context-path}/actuator
# 暴露端点
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: ALWAYS启动项目, 控制台打印如下信息 
项目启动后, 服务端控制台打印日志
获取客户端 token 公钥, appId: auth-client-demo, appSecret: 123456编写 Controller
编写 Controller 测试获取用户信息
@RestController
@RequestMapping("/current/user")
public class CurrentUserController {
@GetMapping("/details")
public ServerResponse<CurrentUserDetails> userDetails(@CurrentUser CurrentUserDetails userDetails) {
return ServerResponse.okData(userDetails);
}
}编写 http 请求文件
### 测试登录用户
GET http://localhost:8002/auth-client-demo/current/user/details
Accept: application/json
Verified-Ticket: verifiedTicket
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJQUzUxMiJ9.eyJuaWNrTmFtZSI6IueOi-Wkp-WuuCIsInVzZXJUeXBlIjoiNCIsInVzZXJJZCI6IjEiLCJhY2NvdW50IjoiYWRtaW4iLCJpc3MiOiJjb20uZ2l0aHViLml0ZGFjaGVuIiwiaWF0IjoxNjkzNzM2ODE5LCJleHAiOjE2OTM3NTQ4MTl9.ljf1uPVIfSdhU3QBplZh1X1sJEMKM0mYXQ-eJPkmwpLA8zo8my8zPoHJCCRlf_9ZjGhZC4OAQceAYAQXoTRVUbV8MC8ZuFg1zuz0OJW_rw0u9P5yGlc5W5sMhI70QJ42zVBkb4NiOwCZ8UPqMqHoE5eNtbPTQcUy1RX98iGT7t7d0In8bxBnaa38Euq7CiBul4lOE7hJqkhZDFuC-ZTzWAeZbAmReCirzom0Jtvyjb53u3TW_A4Hcd5FbXyo6p2zfrokj44yqI-JB76BMlm64_Rp01BkCaNfWNyd6a-0UEL1fEoKdLmEU5kZKBBm8xcNyylsSQUVTq_fI8bnzW-17_d6-KMa8YLDnEbbpxAthEw3vVv5Cbg7PYwTIt3vY-bx3NVmsUlvx2MKOsU8MdRWda4Q-hfr5RR_t-EAKdS2Q2EuNh-3jf64ljE1sNaL1RN_qj3g1psUVB5u9SDbDfe2lfVFGwN79hjYIWq2XMu_FApUM28SKg-VM1ZiMxIq4a8t3AQR7NX7WLTVEfutYbc6uwkpiqGt2lfH_vCeyZwnPDB-d25QA5aor1HH0PnkwcUcc6lAbTZ5GCIZYNNroPHNkWMvxP3eRi4KD5mcTC1ydbljiJCDdgxauKH1GAob8KuiJjqXQXUiCf5RvR8uRdMlcQShWQ_yeVpRFJBIahpN3rI先进行登录, 替换 token 信息, 运行 测试登录用户, 返回下面 JSON 信息
json
{
"success": true,
"status": 200,
"msg": "操作成功!",
"data": {
"id": "1",
"tenantId": null,
"clientId": null,
"signMethod": null,
"nickName": "王大宸",
"avatar": null,
"anId": null,
"anTitle": null,
"telephone": null,
"email": null,
"account": "admin",
"accountSecret": null,
"status": null,
"appId": null,
"openId": null,
"userType": "4",
"sex": null,
"deptId": null,
"deptTitle": null,
"postId": null,
"postTitle": null,
"grade": null,
"isSuperAdmin": null,
"delFlag": null,
"canDel": null,
"expireTime": null,
"other": null
}
}
剑鸣秋朔