用 Swagger 測試接口,怎麼在請求頭中攜帶 Token?

來自一個小夥伴在微信上的提問:html

看到這個問題,鬆哥突然想到我本身以前寫過 Spring Boot+Swagger 的用法:java

也寫過 OAuth2 + Jwt 的用法:git

可是尚未將這兩個結合在一塊兒寫過,因此小夥伴們對此有了疑問,想想這仍是一個很是常見的問題,由於如今使用令牌登陸的場景愈來愈多,在這種狀況下,若是使用 Swagger 來測試接口,要怎麼在請求頭中攜帶 Token 呢?今天鬆哥就來和你們聊一聊。github

1.項目規劃

若是小夥伴們沒有看過鬆哥以前發的 OAuth2 系列文章,建議必定先看下(公衆號江南一點雨後臺回覆 OAuth2 獲取),再來看本文內容,不然接下來的內容可能會犯迷糊。web

這裏鬆哥搭建一個 OAuth2+JWT 的環境來作演示。一共搭建兩個服務:spring

服務名 端口 備註
auth-server 8080 受權服務器
user-server 8081 資源服務器

我稍微解釋一下:數據庫

  • auth-server 就是個人資源服務器,用來頒發 JWT 令牌。
  • user-server 則是資源服務器,訪問 user-server 上的資源,都須要攜帶令牌才能訪問。
  • swagger 則用來給 user-server 上的接口生成文檔。

OK,這是咱們項目的一個大體規劃。api

2.環境搭建

接下來咱們來搭建 OAuth2 測試環境。跨域

2.1 受權服務器搭建

首先咱們搭建一個名爲 auth-server 的受權服務,搭建的時候,選擇以下三個依賴:瀏覽器

  • Web
  • Spring Cloud Security
  • Spirng Cloud OAuth2

項目建立完成後,首先提供一個 Spring Security 的基本配置:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("sang")
                .password(passwordEncoder().encode("123"))
                .roles("admin")
                .and()
                .withUser("javaboy")
                .password(passwordEncoder().encode("123"))
                .roles("user");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().formLogin();
    }
}

在這段代碼中,爲了代碼簡潔,我就不把 Spring Security 用戶存到數據庫中去了,直接存在內存中。

這裏我建立了一個名爲 sang 的用戶,密碼是 123,角色是 admin。同時我還配置了一個表單登陸。

這段配置的目的,實際上就是配置用戶。例如你想用微信登陸第三方網站,在這個過程當中,你得先登陸微信,登陸微信就要你的用戶名/密碼信息,那麼咱們在這裏配置的,其實就是用戶的用戶名/密碼/角色信息。

須要注意的是,在當前案例中,我將採用 OAuth2 中的 password 模式進行登陸,所以這裏還須要明確的提供一個 AuthenticationManager 的 Bean。

基本的用戶信息配置完成後,接下來咱們來配置受權服務器。

首先來配置 TokenStore:

@Configuration
public class AccessTokenConfig {
    @Bean
    TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("javaboy");
        return converter;
    }
}
  1. TokenStore 咱們使用 JwtTokenStore 這個實例。使用了 JWT,access_token 實際上就不用存儲了(無狀態登陸,服務端不須要保存信息),由於用戶的全部信息都在 jwt 裏邊,因此這裏配置的 JwtTokenStore 本質上並非作存儲。
  2. 另外咱們還提供了一個 JwtAccessTokenConverter,這個 JwtAccessTokenConverter 能夠實現將用戶信息和 JWT 進行轉換(將用戶信息轉爲 jwt 字符串,或者從 jwt 字符串提取出用戶信息)。
  3. 另外,在 JWT 字符串生成的時候,咱們須要一個簽名,這個簽名須要本身保存好。

接下來對受權服務器進行詳細配置:

@EnableAuthorizationServer
@Configuration
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
    @Autowired
    TokenStore tokenStore;
    @Autowired
    ClientDetailsService clientDetailsService;
    @Autowired
    AuthenticationManager authenticationManager;
    @Autowired
    PasswordEncoder passwordEncoder;
    @Autowired
    JwtAccessTokenConverter jwtAccessTokenConverter;

    @Bean
    AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        services.setClientDetailsService(clientDetailsService);
        services.setSupportRefreshToken(true);
        services.setTokenStore(tokenStore);
        services.setAccessTokenValiditySeconds(60 * 60 * 24 * 2);
        services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));
        services.setTokenEnhancer(tokenEnhancerChain);
        return services;
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("javaboy")
                .secret(passwordEncoder.encode("123"))
                .resourceIds("res1")
                .authorizedGrantTypes("password", "refresh_token")
                .scopes("all")
                .redirectUris("http://localhost:8082/index.html");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authenticationManager(authenticationManager)
                .tokenServices(tokenServices());
    }
}

這段代碼有點長,我來給你們挨個解釋:

  1. 建立 AuthorizationServer 類繼承自 AuthorizationServerConfigurerAdapter,來對受權服務器作進一步的詳細配置,AuthorizationServer 類記得加上 @EnableAuthorizationServer 註解,表示開啓受權服務器的自動化配置。
  2. 在 AuthorizationServer 類中,咱們其實主要重寫三個 configure 方法。
  3. AuthorizationServerSecurityConfigurer 用來配置令牌端點的安全約束,也就是這個端點誰能訪問,誰不能訪問。
  4. ClientDetailsServiceConfigurer 用來配置客戶端的詳細信息,在以前文章中,鬆哥和你們講過,受權服務器要作兩方面的檢驗,一方面是校驗客戶端,另外一方面則是校驗用戶,校驗用戶,咱們前面已經配置了,這裏就是配置校驗客戶端。客戶端的信息咱們能夠存在數據庫中,這其實也是比較容易的,和用戶信息存到數據庫中相似,可是這裏爲了簡化代碼,我仍是將客戶端信息存在內存中,這裏咱們分別配置了客戶端的 id,secret、資源 id、受權類型、受權範圍以及重定向 uri。受權類型我在以前文章中和你們一共講了四種,四種之中不包含 refresh_token 這種類型,可是在實際操做中,refresh_token 也被算做一種。
  5. AuthorizationServerEndpointsConfigurer 這裏用來配置令牌的訪問端點和令牌服務。
  6. tokenServices 這個 Bean 主要用來配置 Token 的一些基本信息,例如 Token 是否支持刷新、Token 的存儲位置、Token 的有效期以及刷新 Token 的有效期等等。Token 有效期這個好理解,刷新 Token 的有效期我說一下,當 Token 快要過時的時候,咱們須要獲取一個新的 Token,在獲取新的 Token 時候,須要有一個憑證信息,這個憑證信息不是舊的 Token,而是另一個 refresh_token,這個 refresh_token 也是有有效期的。

好了,如此以後,咱們的受權服務器就算是配置完成了,接下來咱們啓動受權服務器。

若是小夥伴們對於上面的配置感到迷糊,能夠在公衆號後臺回覆 OAuth2,先系統的學習一下鬆哥的 OAuth2 教程。

2.2 資源服務器搭建

接下來咱們搭建一個資源服務器。你們網上看到的例子,資源服務器大多都是和受權服務器放在一塊兒的,若是項目比較小的話,這樣作是沒問題的,可是若是是一個大項目,這種作法就不合適了。

資源服務器就是用來存放用戶的資源,例如你在微信上的圖像、openid 等信息,用戶從受權服務器上拿到 access_token 以後,接下來就能夠經過 access_token 來資源服務器請求數據。

咱們建立一個新的 Spring Boot 項目,叫作 user-server ,做爲咱們的資源服務器,建立時,添加以下依賴:

項目建立成功以後,先把前面的 AccessTokenConfig 拷貝到資源服務器上,而後添加以下配置:

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    TokenStore tokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("res1").tokenStore(tokenStore);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("admin")
                .anyRequest().authenticated();
    }
}

這段配置代碼很簡單,我簡單的說一下:

  1. 首先在 configure 方法中配置資源 ID 和 TokenStore,這裏配置好以後,會自動調用 JwtAccessTokenConverter 將 jwt 解析出來,jwt 裏邊就包含了用戶的基本信息,因此就不用遠程校驗 access_token 了。
  2. 最後配置一下資源的攔截規則,這就是 Spring Security 中的基本寫法,我就再也不贅述。

接下來咱們再來配置兩個測試接口:

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
    @GetMapping("/admin/hello")
    public String admin() {
        return "admin";
    }
}

如此以後,咱們的資源服務器就算配置成功了。

2.3 測試

分別啓動受權服務器和資源服務器,先訪問受權服務器獲取 access_token:

再利用拿到的 access_token 去訪問資源服務器:

OK,測試沒問題。

3.整合 Swagger

接下來,咱們在 user-server 中加入 swagger 功能,首先咱們加入 swagger 依賴:

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

這裏加入的依賴有兩個,一個用來生成接口數據,另外一個 swagger-ui 用來作數據展現。

3.1 認證方式一

請求頭加參數,這裏給你們介紹兩種,先來看第一種。

先配置一個 Docket 實例,以下:

@Configuration
@EnableSwagger2
public class Swagger2Config {
    @Bean
    Docket docket() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("org.javaboy.oauth2.res.controller"))
                .paths(PathSelectors.any())
                .build()
                .securityContexts(Arrays.asList(securityContexts()))
                .securitySchemes(Arrays.asList(securitySchemes()))
                .apiInfo(new ApiInfoBuilder()
                        .description("接口文檔的描述信息")
                        .title("微人事項目接口文檔")
                        .contact(new Contact("javaboy","http://www.javaboy.org","wangsong0210@gmail.com"))
                        .version("v1.0")
                        .license("Apache2.0")
                        .build());
    }
    private SecurityScheme securitySchemes() {
        return new ApiKey("Authorization", "Authorization", "header");
    }

    private SecurityContext securityContexts() {
        return SecurityContext.builder()
                        .securityReferences(defaultAuth())
                        .forPaths(PathSelectors.any())
                        .build();
    }

    private List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("xxx", "描述信息");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        return Arrays.asList(new SecurityReference("Authorization", authorizationScopes));
    }
}

這裏的配置稍微有點長,我來給你們解釋下:

  • 首先經過 @EnableSwagger2 註解啓用 Swagger2。
  • 配置一個 Docket Bean,這個 Bean 中,配置映射路徑和要掃描的接口的位置。
  • 在 apiInfo 中,主要配置一下 Swagger2 文檔網站的信息,例如網站的 title,網站的描述,聯繫人的信息,使用的協議等等。
  • 經過 securitySchemes 來配置全局參數,這裏的配置是一個名爲 Authorization 的請求頭(OAuth2 中須要攜帶的請求頭)。
  • securityContexts 則用來配置有哪些請求須要攜帶 Token,這裏咱們配置了全部請求。

配置完成後,咱們還須要給 swagger-ui 放行,不然 swagger-ui 相關的靜態資源會被 Spring Security 攔截下來:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/swagger-ui.html")
                .antMatchers("/webjars/**")
                .antMatchers("/v2/**")
                .antMatchers("/swagger-resources/**");
    }
}

配置完成後,重啓 user-server,瀏覽器輸入 http://localhost:8081/swagger-ui.html,結果以下:

你們能夠看到,頁面中多了一個 Authorize 按鈕,點擊該按鈕,輸入 Bearer ${token},以下:

輸入完成後,點擊 Authorize 按鈕,完成認證,接下來,user-server 中的各類接口就能夠直接調用測試了。

上面這種方式比較通用,不只僅適用於 OAuth2,也適用於其餘一些自定義的 token 登陸方式。

可是這種方式須要開發者先經過其餘途徑獲取到 access_token,有的人會以爲這樣有點麻煩,那麼有沒有更好的辦法呢?請看方式二。

3.2 認證方式二

認證方式二就是直接在 Swagger 中填入認證信息,這樣就不用從外部去獲取 access_token 了,效果以下:


咱們來看下這個怎麼配置。

因爲 swagger 去請求 /oauth/token 接口會跨域,因此咱們首先要修改 auth-server ,使之支持跨域:

主要是兩方面的修改,首先是配置 CorsFilter,容許跨域,以下:

@Configuration
public class GlobalCorsConfiguration {
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(urlBasedCorsConfigurationSource);
    }
}

而後在 SecurityConfig 中開啓跨域支持:

@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    ...
    ...
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .requestMatchers().antMatchers(HttpMethod.OPTIONS, "/oauth/**")
                .and()
                .csrf().disable().formLogin()
                .and()
                .cors();
    }
}

通過這兩步的配置,服務端的跨域支持就開啓了。

接下來咱們在 user-server 中修改關於 Docket bean 的定義:

@Configuration
@EnableSwagger2
public class Swagger2Config {
    @Bean
    Docket docket() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("org.javaboy.oauth2.res.controller"))
                .paths(PathSelectors.any())
                .build()
                .securityContexts(Arrays.asList(securityContext()))
                .securitySchemes(Arrays.asList(securityScheme()))
                .apiInfo(new ApiInfoBuilder()
                        .description("接口文檔的描述信息")
                        .title("微人事項目接口文檔")
                        .contact(new Contact("javaboy","http://www.javaboy.org","wangsong0210@gmail.com"))
                        .version("v1.0")
                        .license("Apache2.0")
                        .build());
    }

    private AuthorizationScope[] scopes() {
        return new AuthorizationScope[]{
                new AuthorizationScope("all", "all scope")
        };
    }

    private SecurityScheme securityScheme() {
        GrantType grant = new ResourceOwnerPasswordCredentialsGrant("http://localhost:8080/oauth/token");
        return new OAuthBuilder().name("OAuth2")
                .grantTypes(Arrays.asList(grant))
                .scopes(Arrays.asList(scopes()))
                .build();
    }

    private SecurityContext securityContext() {
        return SecurityContext.builder()
                .securityReferences(Arrays.asList(new SecurityReference("OAuth2", scopes())))
                .forPaths(PathSelectors.any())
                .build();
    }
}

這段配置跟前面的相似,主要是 SecurityScheme 不一樣。這裏採用了 OAuthBuilder 來構建,構建時即得配置 token 的獲取地址。

好了,配置完成,重啓 auth-server 和 user-server 進行測試。測試效果就是鬆哥前面給出的圖片,再也不贅述。

這種方式最大的好處就是不用經過其餘途徑獲取 access_token,直接在 swagger-ui 頁面輸入 password 模式的認證參數便可。很是方便,僅限於 OAuth2 模式。

4.小結

好了,今天就和小夥伴們介紹了在 Swagger 請求中,如何修改請求頭的問題,感興趣的小夥伴能夠下來試試哦~

本文案例下載地址:https://github.com/lenve/spring-security-samples

好啦,小夥伴們若是以爲有收穫,記得點個在看鼓勵下鬆哥哦~

相關文章
相關標籤/搜索