微服务体系一般由服务注册发现中心、网关、资源服务和认证服务这几个部分组成。这是微服务系列的第一篇,搭建资源服务。
本次微服务系列搭建的服务注册与配置组件使用的是nacos。所以需要事先安装nacos。参考官网教程:
什么是资源服务
在微服务体系中,包含多种角色,比如服务的注册与发现组件、网关组件、认证服务程序和资源服务程序。资源服务就是提供具体服务的应用,比如在电商系统中的支付服务,商品服务,订单服务等。
开始
创建spring boot项目,修改pom文件。
1<?xml version="1.0" encoding="UTF-8"?> 2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 <modelVersion>4.0.0</modelVersion> 5 <parent> 6 <groupId>org.springframework.boot</groupId> 7 <artifactId>spring-boot-starter-parent</artifactId> 8 <version>2.3.4.RELEASE</version> 9 <relativePath/> <!-- lookup parent from repository --> 10 </parent> 11 <groupId>com.example</groupId> 12 <artifactId>resource-service</artifactId> 13 <version>0.0.1-SNAPSHOT</version> 14 <name>user-service</name> 15 <description>resource-service</description> 16 <properties> 17 <java.version>1.8</java.version> 18 <org.projectlombok.version>1.18.16</org.projectlombok.version> 19 </properties> 20 <dependencyManagement> 21 <dependencies> 22 <!-- spring cloud 依赖 --> 23 <dependency> 24 <groupId>org.springframework.cloud</groupId> 25 <artifactId>spring-cloud-dependencies</artifactId> 26 <version>Hoxton.SR12</version> 27 <type>pom</type> 28 <scope>import</scope> 29 </dependency> 30 <!-- spring cloud alibaba 依赖--> 31 <dependency> 32 <groupId>com.alibaba.cloud</groupId> 33 <artifactId>spring-cloud-alibaba-dependencies</artifactId> 34 <version>2.2.1.RELEASE</version> 35 <type>pom</type> 36 <scope>import</scope> 37 </dependency> 38 </dependencies> 39 </dependencyManagement> 40 <dependencies> 41 <dependency> 42 <groupId>com.alibaba.cloud</groupId> 43 <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> 44 </dependency> 45 <dependency> 46 <groupId>org.springframework.boot</groupId> 47 <artifactId>spring-boot-starter</artifactId> 48 </dependency> 49 <dependency> 50 <groupId>org.springframework.boot</groupId> 51 <artifactId>spring-boot-starter-web</artifactId> 52 </dependency> 53 <!-- 引入Swagger3依赖 --> 54 <dependency> 55 <groupId>io.springfox</groupId> 56 <artifactId>springfox-boot-starter</artifactId> 57 <version>3.0.0</version> 58 </dependency> 59 <dependency> 60 <groupId>org.springframework.boot</groupId> 61 <artifactId>spring-boot-devtools</artifactId> 62 <scope>runtime</scope> 63 <optional>true</optional> 64 </dependency> 65 <dependency> 66 <groupId>org.springframework.boot</groupId> 67 <artifactId>spring-boot-configuration-processor</artifactId> 68 <optional>true</optional> 69 </dependency> 70 <dependency> 71 <groupId>org.projectlombok</groupId> 72 <artifactId>lombok</artifactId> 73 <optional>true</optional> 74 </dependency> 75 <dependency> 76 <groupId>org.springframework.boot</groupId> 77 <artifactId>spring-boot-starter-test</artifactId> 78 <scope>test</scope> 79 </dependency> 80 <dependency> 81 <groupId>org.springframework.cloud</groupId> 82 <artifactId>spring-cloud-starter-oauth2</artifactId> 83 </dependency> 84 </dependencies> 85 86 <build> 87 <plugins> 88 <plugin> 89 <groupId>org.springframework.boot</groupId> 90 <artifactId>spring-boot-maven-plugin</artifactId> 91 <configuration> 92 <excludes> 93 <exclude> 94 <groupId>org.projectlombok</groupId> 95 <artifactId>lombok</artifactId> 96 </exclude> 97 </excludes> 98 </configuration> 99 </plugin> 100 <plugin> 101 <groupId>org.apache.maven.plugins</groupId> 102 <artifactId>maven-compiler-plugin</artifactId> 103 <version>${maven-compiler-plugin.version}</version> 104 <configuration> 105 <source>${java.version}</source> 106 <target>${java.version}</target> 107 <annotationProcessorPaths> 108 <path> 109 <groupId>org.projectlombok</groupId> 110 <artifactId>lombok</artifactId> 111 <version>${org.projectlombok.version}</version> 112 </path> 113 </annotationProcessorPaths> 114 </configuration> 115 </plugin> 116 </plugins> 117 </build> 118</project>
spring boot版本和spring cloud版本是有对应关系的,可以通过一下网站进行查看:
这里使用的是spring boot 2.3.4.RELEASE和spring cloud Hoxton.SR12。
application.yml
1server: 2 port: 9091 3 4spring: 5 application: 6 name: user-service 7 cloud: 8 nacos: 9 discovery: 10 server-addr: localhost:8848 11 username: nacos 12 password: nacos 13 gateway: 14 discovery: 15 locator: 16 enabled: true # gateway 可以从 nacos 发现微服务 17management: 18 endpoints: 19 web: 20 exposure: 21 include: '*' 22token_check_url: http://localhost:9090/oauth2/oauth/check_token 23logging: 24 file: 25 path: ./logs
一个接口(controller/HelloController.java)
1@RestController 2@Api(tags = "hello world") 3public class HomeController { 4 @GetMapping("/hello") 5 @ApiOperation(value = "hello") 6 public String query() { 7 return "hello world"; 8 } 9}
配置权限不足处理类(config/CustomAccessDeniedHandler.java)
1@Component("CustomAccessDeniedHandler") 2@Slf4j 3public class CustomAccessDeniedHandler implements AccessDeniedHandler { 4 @Override 5 public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException { 6 response.setStatus(HttpStatus.OK.value()); 7 response.setCharacterEncoding("UTF-8"); 8 response.setContentType("application/json; charset=utf-8"); 9 PrintWriter printWriter = response.getWriter(); 10 String body = ResultJson.failure(ResultCode.FORBIDDEN, accessDeniedException.getMessage()).toString(); 11 printWriter.write(body); 12 printWriter.flush(); 13 } 14 15}
配置认证类(config/CustomAuthenticationEntryPointHandler.java)
1@Slf4j 2@Component("customAuthenticationEntryPointHandler") 3public class CustomAuthenticationEntryPointHandler extends OAuth2AuthenticationEntryPoint { 4 5 @Override 6 public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { 7 //验证为未登陆状态会进入此方法,认证错误 8 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 9 response.setCharacterEncoding("UTF-8"); 10 response.setContentType("application/json; charset=utf-8"); 11 Throwable cause = authException.getCause(); 12 String errorMsg = ""; 13 if (cause instanceof OAuth2AccessDeniedException) { 14 errorMsg = "未获得访问该资源权限"; 15 } else if (cause instanceof InvalidTokenException) { 16 log.warn("Token解析失败"); 17 errorMsg = "token解析失败"; 18 }else if (authException instanceof InsufficientAuthenticationException) { 19 log.warn("未携带token"); 20 errorMsg = "未携带token"; 21 } 22 PrintWriter printWriter = response.getWriter(); 23 String body = ResultJson.failure(ResultCode.UNAUTHORIZED, errorMsg).toString(); 24 printWriter.write(body); 25 printWriter.flush(); 26 } 27 28}
配置资源服务器(config/ResourceServerConfig.java)
1@Configuration 2@EnableResourceServer 3public class ResourceServerConfig extends ResourceServerConfigurerAdapter { 4 @Value("${token_check_url}") 5 private String tokenCheckUrl; 6 private final AccessDeniedHandler customAccessDeniedHandler; 7 8 private final OAuth2AuthenticationEntryPoint customAuthenticationEntryPointHandler; 9 10 public ResourceServerConfig(CustomAccessDeniedHandler customAccessDeniedHandler, OAuth2AuthenticationEntryPoint customAuthenticationEntryPointHandler) { 11 this.customAccessDeniedHandler = customAccessDeniedHandler; 12 this.customAuthenticationEntryPointHandler = customAuthenticationEntryPointHandler; 13 } 14 15 @Override 16 public void configure(ResourceServerSecurityConfigurer resources) throws Exception { 17 // 本资源服务的ID,用于在授权服务器那里验证是否存在这个ID的资源服务 18 resources.resourceId("resource-service") 19 .tokenServices(tokenService()) 20 .accessDeniedHandler(customAccessDeniedHandler) 21 .authenticationEntryPoint(customAuthenticationEntryPointHandler) 22 // 禁用session 23 .stateless(true); 24 } 25 26 @Override 27 public void configure(HttpSecurity http) throws Exception { 28 http 29 .authorizeRequests() 30 // 配置客户端权限scope 31 .antMatchers("/swagger-ui.html", 32 "/swagger-ui/*", 33 "/swagger-resources/**", 34 "/v3/api-docs", 35 "/a/resources/all", 36 "/a/resources/services/all", 37 "/webjars/**").permitAll() 38 .and().csrf().disable() 39 // 关闭session 40 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); 41 } 42 43 /** 44 * 资源服务令牌解析服务,调用远程服务解析 45 */ 46 @Bean 47 public ResourceServerTokenServices tokenService() { 48 //使用远程服务请求授权服务器校验token,必须指定校验token 的url、client_id,client_secret 49 RemoteTokenServices service = new RemoteTokenServices(); 50 service.setCheckTokenEndpointUrl(tokenCheckUrl); 51 service.setClientSecret("demo"); 52 service.setClientId("resource-service"); 53 return service; 54 } 55}
@EnableResourceServer定义该程序为资源服务器。
配置swagger(config/Swagger2Configuration.java)
1@EnableOpenApi 2@Configuration 3public class Swagger2Configuration { 4 @Bean 5 Docket createRestApi() { 6 return new Docket(DocumentationType.OAS_30) 7 .apiInfo(apiInfo()) 8 .enable(true) 9 .select() 10 .apis(RequestHandlerSelectors.withClassAnnotation(Api.class)) 11 .paths(PathSelectors.any()) 12 .build() 13 .pathMapping("/rs")//配置网关路由,这样子微服务本体通过本端口调用需注释掉 14 .securitySchemes(securitySchemes()) 15 .securityContexts(Arrays.asList(SecurityContext.builder() 16 .securityReferences(Arrays.asList(SecurityReference.builder() 17 .reference("Authorization") 18 .scopes(new AuthorizationScope[]{new AuthorizationScope("global", "accessEverything")}) 19 .build())) 20 .build())) 21 ; 22 } 23 24 /** 25 * 请求头配置 26 * @return 27 */ 28 private List<SecurityScheme> securitySchemes() { 29 List<SecurityScheme> securitySchemes = new ArrayList<>(); 30 securitySchemes.add(new ApiKey("Authorization", "Authorization", "header")); 31 return securitySchemes; 32 } 33 34 private ApiInfo apiInfo() { 35 return new ApiInfoBuilder() 36 .title("资源服务器 API 文档") 37 .description("资源服务器接口说明") 38 .termsOfServiceUrl("@Author joetao") 39 .version("1.0.0") 40 .build(); 41 } 42}
启动服务后就可以在nacos的服务列表中看到服务了。
使用postman访问接口:
成功!
到此资源服务器搭建告一段落。在搭建完网关服务后,会再来完善资源服务器,增加认证访问。
源码地址:
2023年04月18日