来自一个小伙伴在微信上的提问: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
好啦,小伙伴们若是以为有收获,记得点个在看鼓励下松哥哦~