swagger在微服务下的实战应用(微服务整合、token认证、规范化入参)

前言

6月19入的职到现在的7月27号,没错我已经实习了1个多月了,emmm怎么来说呢,拿我在学校敲代码的状态来比喻吧,如果把我在学校敲的代码比喻成一匹矫健驰骋在草原的黑马,那么我在公司敲的代码就更像是正规军,驰骋在赛场的汗血宝马了。

个人所感

以前在学校没怎么重视网络编程这块,即使以前有看过springCloud、duuble相关的视频,但是当时看视频的时候觉着这样开发项目太繁琐了,也没有坚持去系统化的去总结这一块的内容。以前我也刷到过牛客网上那些牛逼简历上写的项目是手写RPC框架、基于scoket开发的实时在线聊天系统相关的项目,当时觉着这些技术简直可以用俩个字“牛逼”来形容。当时本人痴迷于一个集合类、spring框架的源码分析就没有太注意这一块的技术。但是现在在公司开发的是一个微服务项目,核心业务都是涉及到网络编程相关的,迫于压力也是对于技术的一种热爱,于是我现在研究起了微服务相关框架的使用、源码、scoket、netty、dubbo、springCloud这些技术了,后续会陆续更新相关文章。

模块间依赖关系

email:依赖payment模块
gateway:网关负责路由相关到对应的实例(注册中心用的nacos)
payment:单独的模块
在这里插入图片描述

email、payment模块添加swagger依赖支持

 <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.9.2</version>
    </dependency>
    <dependency>
        <groupId>io.swagger</groupId>
        <artifactId>swagger-annotations</artifactId>
        <version>1.5.20</version>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.9.2</version>
    </dependency>

email、payment模块开启swagger模块配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.context.request.async.DeferredResult;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.Arrays;

@Configuration
@EnableSwagger2
@Import(BeanValidatorPluginsConfiguration.class)
public class Swagger2Config {

	@Bean
	public Docket customDocket() {
		return new Docket(DocumentationType.SWAGGER_2)
				.genericModelSubstitutes(DeferredResult.class)
				.useDefaultResponseMessages(false)
				.forCodeGeneration(false)
				.pathMapping("/")
				.apiInfo(apiInfo())
				.select()
				.build();
	}

	private ApiInfo apiInfo() {
		return new ApiInfoBuilder()
				.title("接口文档title")
				.description("接口文档说明")
				.version("1.0.0")
				.build();
	}
}

网关模块配置(重点)

添加swagger接口支撑

/**
 * zzh
 */
@RestController
class SwaggerHandler {
    @Autowired(required = false)
    private SecurityConfiguration securityConfiguration;
    @Autowired(required = false)
    private UiConfiguration uiConfiguration;
    private final SwaggerResourcesProvider swaggerResources;

    @Autowired
    public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
        this.swaggerResources = swaggerResources;
    }


    @GetMapping("/swagger-resources/configuration/security")
    public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
    }

    @GetMapping("/swagger-resources/configuration/ui")
    public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
    }

    @GetMapping("/swagger-resources")
    public Mono<ResponseEntity> swaggerResources() {
        return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
    }

    @GetMapping("/")
    public Mono<ResponseEntity> swaggerResourcesN() {
        return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
    }

    @GetMapping("/csrf")
    public Mono<ResponseEntity> swaggerResourcesCsrf() {
        return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
    }

}

对swagger请求进行过滤(可有可无)

/**
 * zzh
 */
@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
 
    private static final String HEADER_NAME = "X-Forwarded-Prefix";

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getURI().getPath();
            if (!StringUtils.endsWithIgnoreCase(path, SwaggerProvider.API_URI)) {
                return chain.filter(exchange);
            }
            String basePath = path.substring(0, path.lastIndexOf(SwaggerProvider.API_URI));
            ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
            return chain.filter(newExchange);
 
        };
 
    }
}

全局swagger配置(放在网关模块)

/**
 * 全局swagger配置
 */
@Component
@Primary
@AllArgsConstructor
public class SwaggerProvider implements SwaggerResourcesProvider {
    //注意这个地址
    public static final String API_URI = "/v2/api-docs?urls.primaryName";
    private final RouteLocator routeLocator;
    private final GatewayProperties gatewayProperties;

    /**
     * swagger会与返回值中的SwaggerResource进行路径匹配,如果匹配不到那么会出现swagger页面404无法访问的情况
     *
     * @return
     */
    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = new ArrayList<>();
        List<String> routes = new ArrayList<>();
        routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
        gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId()))
                .forEach(routeDefinition -> routeDefinition.getPredicates().stream()
                        .filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
                        .forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(), predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
                                //swagger API地址
                                .replace("/**", API_URI + routeDefinition.getId())))));
        return resources;
    }

    private SwaggerResource swaggerResource(String name, String location) {
        SwaggerResource swaggerResource = new SwaggerResource();
        swaggerResource.setName(name);
        swaggerResource.setLocation(location);
        swaggerResource.setSwaggerVersion("2.0");
        return swaggerResource;
    }
}

网关路由配置

server:
  port: 8005
spring:
  application:
    name: gateway
  cloud:
    discovery:
      locator:
        enabled: true
    gateway:
      routes:
        - id: email_id
          uri: lb://email
          predicates:
            - Path=/email/**
          filters:
            - name: SwaggerHeaderFilter
            - StripPrefix=1
        - id: payment_id
          uri: lb://payment
          predicates:
            - Path=/payment/**
          filters:
            - name: SwaggerHeaderFilter
            - StripPrefix=1
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
      config:
        server-addr: 127.0.0.1:8848
mybatis-plus:
  mapper-locations: classpath:mapperXml/*.xml

全局token验证、方式一

在swagger配置文件中加入如下配置,每次请求都会把 name为 X-Auth-Token 的参数放入请求中进行请求

/**
 * 对每个接口都添加token认证
 *
 * @return
 */
private List<Parameter> setHeaderToken() {
    ParameterBuilder tokenPar = new ParameterBuilder();
    List<Parameter> pars = new ArrayList<>();
    tokenPar.name("X-Auth-Token").description("token").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
    pars.add(tokenPar.build());
    return pars;
}

加入swagger配置
在这里插入图片描述

全局token验证、方式二

一次添加模块下的所有接口下次请求都会携带 token


    @Bean
    public Docket customDocket() {
        // 配置全局参数返回状态
        java.util.List<ResponseMessage> resMsgList = Arrays.asList(
                new ResponseMessageBuilder().code(200).message("成功!").build(),
                new ResponseMessageBuilder().code(-1).message("失败!").build(),
                new ResponseMessageBuilder().code(401).message("参数校验错误!").build(),
                new ResponseMessageBuilder().code(403).message("没有权限操作,请后台添加相应权限!").build(),
                new ResponseMessageBuilder().code(500).message("服务器内部异常,请稍后重试!").build(),
                new ResponseMessageBuilder().code(501).message("请登录!").build());
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo()).select()
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .paths(PathSelectors.any())
                .build()
                .globalResponseMessage(RequestMethod.GET, resMsgList)
                .globalResponseMessage(RequestMethod.POST, resMsgList)
                .globalResponseMessage(RequestMethod.PUT, resMsgList)
                .globalResponseMessage(RequestMethod.DELETE, resMsgList)
                .globalOperationParameters(setHeaderToken())
                .securityContexts(securityContexts());
    }
	/**
     * 以下代码添加全局token配置
     *
     * @return
     */
    private List<ApiKey> security() {
        ArrayList list = new ArrayList();
        list.add(new ApiKey("Authorization", "Authorization", "header"));
        return list;
    }

    private List<SecurityContext> securityContexts() {
        List<SecurityContext> securityContexts = new ArrayList<>();
        securityContexts.add(
                SecurityContext.builder()
                        .securityReferences(defaultAuth())
                        .forPaths(PathSelectors.regex("^(?!auth).*$"))
                        .build());
        return securityContexts;
    }

    private List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        List<SecurityReference> securityReferences = new ArrayList<>();
        securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
        return securityReferences;
    }

在这里插入图片描述

规范化入参

我们的post请求尽量使用vo接受参数,不要用map(网上有map接受的方法我也试过,但是放在生产线上会爆bug不推荐)、多个@Requestparam去接受参数,后期不好整理swagger文档!!!
在这里插入图片描述
在这里插入图片描述
vo接受参数的好处:前端调试可以使用json传值,并且有model注释可以看传的值是什么意思。
在这里插入图片描述

在这里插入图片描述

坑一 swagger页面404

在这里插入图片描述
观察到正确的swagger地址有尾缀
在这里插入图片描述
解决办法一:访问swagger的地址加上尾缀
在这里插入图片描述
解决办法二:去除前缀这个必须加上

在这里插入图片描述

坑二(swagger页面有了、访问接口出现碟名)

一开始我以为是由于网关的配置引起的,也就是下面这种情况:网关配置了路由/a/** , 请求/a/b/c经过网关,由于前缀 /a 匹配上了,/a/b/c的请求会替代 /** 的位置,经过网关转发后的请求实际上是 /a/a/b/c。但是后来想着网关的路由匹配规则也太变态了吧,都已经模糊匹配上了,还给我恶意拼接上去。经过本人实测 gateWay 不是这么个路由匹配规则,出现路径碟名的bug只存在于swagger中,下文有详细描述gateWay路由匹配规则。

在这里插入图片描述

gateWay路由匹配

下图对应payment模块controller中的一个方法,返回值包含了请求路径以及端口号

在这里插入图片描述

下图对应email模块controller中的一个方法,返回值包含了请求路径以及端口号
在这里插入图片描述

gateWay 中 payment 对应的路由配置 注释掉 - StripPrefix=1,而 email 对应的路由配置开启 - StripPrefix=1,我们通过直接走 gateWay 网关来调用我们的接口,来观察经过网关分发后,到达接口的真实请求路径、端口号是多少。并且我们通过 -Dserver.port=7009 指令,分别对email、payment模块开启端口不同的俩个实例,来检测 gateWay 的负载均衡功能是否生效
在这里插入图片描述

事实证明: 我们的请求路径为 /payment/getUser/1,经过网关转发之后的请求路径依然为 /payment/getUser/1 而不是/payment/payment/getUser/1 且负载均衡也生效
在这里插入图片描述
在这里插入图片描述
事实证明: 我们的请求路径为 /email/email/getUser/1、/email/getUser/1,由于配置了去除前缀的规则,经过网关转发之后的请求路径分别为 /email/getUser/1、/getUser/1 因此下图一为404,下图二、三为正常结果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

警戒(总结)

不要被swagger蒙蔽了我们的双眼,gateWay路由匹配规则如下

  • 去除前缀执行的时机:经过网关分发后去除前缀。例如 /a/a/b/c 到达了网关,经过网关分发后到达接口的请求为 /a/b/c
  • 路由匹配规则: /a/** 会匹配以 /a 开头的请求,路由到对应的服务器实例上面去。但是不是这么一种情况, /a/b的请求打到网关 (gateWay) ,匹配上了 /a/** 后,经过网关分发后的请求依然是/a/b , 而不是/a/a/b,至于swagger页面中的碟名这个效果 个人认为是个 bug,上文有证实。

上文为我亲测后推导出来的个人想法的总结,如有不对之处,望各位读者斧正

小咸鱼的技术窝

关注不迷路,日后分享更多技术干货,B站、CSDN、微信公众号同名,名称都是(小咸鱼的技术窝)更多详情在主页
在这里插入图片描述

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
为了在Swagger中添加token,可以使用Spring Security来保护API并生成token。以下是如何在Spring Boot应用程序中使用Swagger和Spring Security整合token的步骤: 1. 添加Spring Security和JWT依赖 在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> ``` 2. 创建Spring Security配置类 创建一个类,继承WebSecurityConfigurerAdapter并重写configure(HttpSecurity http)方法。在该方法中,配置Spring Security以保护API并生成token。以下是一个示例: ``` @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and().csrf().disable() .authorizeRequests() .antMatchers("/api/auth/**").permitAll() .anyRequest().authenticated() .and() .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint) .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } } ``` 3. 创建JWT工具类 创建一个类,用于生成和解析JWT token。以下是一个示例: ``` @Component public class JwtUtils { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private int expiration; public String generateToken(Authentication authentication) { UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal(); Date now = new Date(); Date expiryDate = new Date(now.getTime() + expiration); return Jwts.builder() .setSubject(userPrincipal.getUsername()) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public String getUsernameFromToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject(); } public boolean validateToken(String authToken) { try { Jwts.parser().setSigningKey(secret).parseClaimsJws(authToken); return true; } catch (SignatureException ex) { log.error("Invalid JWT signature"); } catch (MalformedJwtException ex) { log.error("Invalid JWT token"); } catch (ExpiredJwtException ex) { log.error("Expired JWT token"); } catch (UnsupportedJwtException ex) { log.error("Unsupported JWT token"); } catch (IllegalArgumentException ex) { log.error("JWT claims string is empty"); } return false; } } ``` 4. 创建JWT认证过滤器 创建一个类,用于验证token并将用户信息添加到Spring Security上下文中。以下是一个示例: ``` public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtUtils jwtUtils; @Autowired private UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String jwt = getJwtFromRequest(request); if (StringUtils.hasText(jwt) && jwtUtils.validateToken(jwt)) { String username = jwtUtils.getUsernameFromToken(jwt); UserDetails userDetails = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (Exception ex) { log.error("Could not set user authentication in security context", ex); } filterChain.doFilter(request, response); } private String getJwtFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } } ``` 5. 在Swagger中添加token 在Spring Boot应用程序中添加Swagger,并在Swagger配置类中添加以下代码: ``` @Configuration @EnableSwagger2 public class SwaggerConfig { @Autowired private JwtUtils jwtUtils; @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .securityContexts(Arrays.asList(securityContext())) .securitySchemes(Arrays.asList(apiKey())) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build(); } private ApiKey apiKey() { return new ApiKey("JWT", "Authorization", "header"); } private SecurityContext securityContext() { return SecurityContext.builder() .securityReferences(Arrays.asList(new SecurityReference("JWT", new AuthorizationScope[]{}))) .forPaths(PathSelectors.any()) .build(); } @Bean public SecurityConfiguration security() { return SecurityConfigurationBuilder.builder() .clientId(null) .clientSecret(null) .realm(null) .appName(null) .scopeSeparator(",") .additionalQueryStringParams(null) .useBasicAuthenticationWithAccessCodeGrant(false) .build(); } @Bean public UiConfiguration uiConfig() { return UiConfigurationBuilder.builder() .displayRequestDuration(true) .validatorUrl("") .build(); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("swagger-ui.html") .addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/"); } @Bean public SecurityConfiguration securityConfiguration() { return SecurityConfigurationBuilder.builder() .clientId("test-app-client-id") .clientSecret("test-app-client-secret") .realm("test-app-realm") .appName("test-app") .scopeSeparator(",") .additionalQueryStringParams(null) .useBasicAuthenticationWithAccessCodeGrant(false) .build(); } @Bean public SecurityConfiguration securityConfiguration() { return SecurityConfigurationBuilder.builder() .clientId("test-app-client-id") .clientSecret("test-app-client-secret") .realm("test-app-realm") .appName("test-app") .scopeSeparator(",") .additionalQueryStringParams(null) .useBasicAuthenticationWithAccessCodeGrant(false) .build(); } @Bean public OAuth securitySchema() { List<AuthorizationScope> authorizationScopeList = new ArrayList<>(); authorizationScopeList.add(new AuthorizationScope("read", "read all")); authorizationScopeList.add(new AuthorizationScope("write", "access all")); List<GrantType> grantTypes = new ArrayList<>(); GrantType passwordCredentialsGrant = new ResourceOwnerPasswordCredentialsGrant("http://localhost:8080/auth/token"); grantTypes.add(passwordCredentialsGrant); return new OAuth("oauth2schema", authorizationScopeList, grantTypes); } @Bean public SecurityConfiguration securityInfo() { return new SecurityConfiguration( "test-app-client-id", "test-app-client-secret", "test-app-realm", "test-app", "", ApiKeyVehicle.HEADER, "Authorization", "," ); } @Bean public SecurityConfiguration security() { return SecurityConfigurationBuilder.builder() .clientId("test-app-client-id") .clientSecret("test-app-client-secret") .realm("test-app-realm") .appName("test-app") .scopeSeparator(",") .additionalQueryStringParams(null) .useBasicAuthenticationWithAccessCodeGrant(false) .build(); } @Bean public UiConfiguration uiConfiguration() { return new UiConfiguration( null, "none", "alpha", "schema", UiConfiguration.Constants.DEFAULT_SUBMIT_METHODS, false, true, 60000L ); } } ``` 完成以上步骤后,您就可以在Swagger中使用token来访问受保护的API了。在Swagger界面的右上方,单击“Authorize”按钮并输入您的token即可。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小咸鱼的技术窝

你的鼓励将是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值