來自一個小夥伴在微信上的提問:html
看到這個問題,鬆哥突然想到我本身以前寫過 Spring Boot+Swagger 的用法:java
也寫過 OAuth2 + Jwt 的用法:git
可是尚未將這兩個結合在一塊兒寫過,因此小夥伴們對此有了疑問,想想這仍是一個很是常見的問題,由於如今使用令牌登陸的場景愈來愈多,在這種狀況下,若是使用 Swagger 來測試接口,要怎麼在請求頭中攜帶 Token 呢?今天鬆哥就來和你們聊一聊。github
若是小夥伴們沒有看過鬆哥以前發的 OAuth2 系列文章,建議必定先看下(公衆號江南一點雨後臺回覆 OAuth2 獲取),再來看本文內容,不然接下來的內容可能會犯迷糊。web
這裏鬆哥搭建一個 OAuth2+JWT 的環境來作演示。一共搭建兩個服務:spring
服務名 | 端口 | 備註 |
---|---|---|
auth-server | 8080 | 受權服務器 |
user-server | 8081 | 資源服務器 |
我稍微解釋一下:數據庫
OK,這是咱們項目的一個大體規劃。api
接下來咱們來搭建 OAuth2 測試環境。跨域
首先咱們搭建一個名爲 auth-server 的受權服務,搭建的時候,選擇以下三個依賴:瀏覽器
項目建立完成後,首先提供一個 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; } }
接下來對受權服務器進行詳細配置:
@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()); } }
這段代碼有點長,我來給你們挨個解釋:
好了,如此以後,咱們的受權服務器就算是配置完成了,接下來咱們啓動受權服務器。
若是小夥伴們對於上面的配置感到迷糊,能夠在公衆號後臺回覆 OAuth2,先系統的學習一下鬆哥的 OAuth2 教程。
接下來咱們搭建一個資源服務器。你們網上看到的例子,資源服務器大多都是和受權服務器放在一塊兒的,若是項目比較小的話,這樣作是沒問題的,可是若是是一個大項目,這種作法就不合適了。
資源服務器就是用來存放用戶的資源,例如你在微信上的圖像、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(); } }
這段配置代碼很簡單,我簡單的說一下:
接下來咱們再來配置兩個測試接口:
@RestController public class HelloController { @GetMapping("/hello") public String hello() { return "hello"; } @GetMapping("/admin/hello") public String admin() { return "admin"; } }
如此以後,咱們的資源服務器就算配置成功了。
分別啓動受權服務器和資源服務器,先訪問受權服務器獲取 access_token:
再利用拿到的 access_token 去訪問資源服務器:
OK,測試沒問題。
接下來,咱們在 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 用來作數據展現。
請求頭加參數,這裏給你們介紹兩種,先來看第一種。
先配置一個 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)); } }
這裏的配置稍微有點長,我來給你們解釋下:
配置完成後,咱們還須要給 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,有的人會以爲這樣有點麻煩,那麼有沒有更好的辦法呢?請看方式二。
認證方式二就是直接在 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 模式。
好了,今天就和小夥伴們介紹了在 Swagger 請求中,如何修改請求頭的問題,感興趣的小夥伴能夠下來試試哦~
本文案例下載地址:https://github.com/lenve/spring-security-samples
好啦,小夥伴們若是以爲有收穫,記得點個在看鼓勵下鬆哥哦~