Question from a friend on wechat:
Seeing this question, Songo suddenly remembered that I had previously written about Spring Boot+Swagger:
- Integrate SpringBoot Swagger2
OAuth2 + Jwt
- Want OAuth2 and JWT to have fun together? Watch Songo’s performance
If we use Swagger to test the interface, how to carry the Token in the request header? Today Songo is here to talk about it.
1. Project planning
If you have not seen the songge before the OAuth2 series of articles, it is suggested that you must first read (public number Jiangnan little rain background reply OAuth2 obtain), and then look at the content of this article, otherwise the following content may make confused.
Here Songo set up an OAuth2+JWT environment for the demonstration. Two services have been set up:
The service name | port | note |
---|---|---|
auth-server | 8080 | Authorization server |
user-server | 8081 | Resource server |
Let me explain a little bit:
- Auth-server is my resource server for issuing JWT tokens.
- User-server is a resource server. To access resources on user-server, you need to carry a token.
- Swagger is used to document interfaces on the user-server.
OK, this is a rough plan of our project.
2. Environment construction
Next, let’s set up the OAuth2 test environment.
2.1 Setting up the Authorization Server
First of all, we set up an authorization service named auth-server. When setting up, we select the following three dependencies:
- Web
- Spring Cloud Security
- Spirng Cloud OAuth2
After the project is created, we will first provide a basic configuration for Spring Security:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder(a) {
return new BCryptPasswordEncoder();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean(a) 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(); }}Copy the code
In this code, for the sake of simplicity, I will not store the Spring Security users in the database, but in memory.
Here I create a user named Sang with a password of 123 and a role of admin. I also configured a form login.
The purpose of this configuration is to configure users. For example, if you want to use wechat to log in to a third-party website, you need to log in to wechat first, which requires your user name/password, so what we configure here is actually the user’s user name/password/role information.
Note that in the current case, I will use the password mode in OAuth2 for login, so I also need to explicitly provide an AuthenticationManager Bean.
With basic user information configured, it’s time to configure the authorization server.
Configure TokenStore first:
@Configuration
public class AccessTokenConfig {
@Bean
TokenStore tokenStore(a) {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
JwtAccessTokenConverter jwtAccessTokenConverter(a) {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("javaboy");
returnconverter; }}Copy the code
- TokenStore We use the JwtTokenStore instance. With JWT, access_token does not actually need to be stored (stateless login, server does not need to store information) because all user information is stored in JWT, so the JwtTokenStore configured here is not a store per se.
- In addition, we also provide a JwtAccessTokenConverter, which can convert user information to JWT (convert user information to JWT string or extract user information from JWT string).
- In addition, when the JWT string is generated, we need a signature, which needs to be saved by itself.
Next, configure the authorization server in detail:
@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(a) {
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()); }}Copy the code
This code is a bit long, so I’ll explain it to you one by one:
- Create AuthorizationServer class inherits from AuthorizationServerConfigurerAdapter to the license server for further detailed configuration, AuthorizationServer class remember add @ EnableAuthorizationServer annotations, said open authorization server configuration of automation.
- In the AuthorizationServer class, we essentially override three configure methods.
- AuthorizationServerSecurityConfigurer used to configure the token endpoint security constraints, namely the endpoint who can access, who can not access.
- ClientDetailsServiceConfigurer used to configure the client’s detailed information, in the previous articles, pine, elder brother and everybody said, authorization server to do two aspects of the inspection, on the one hand is to check the client, on the other hand is to check the user, check the user, we have configured the front, This is configuring the verification client. The client’s information we can exist in the database, it is also easier, and user information to the database similar, but here in order to simplify the code, I still will be the client information exists in memory, here we are configured with the client id, secret, resource id, type, the authorization scope of authorization, and redirect uri. There are four authorization types that I described in the previous article. Refresh_token is not included in the four authorization types, but in practice, refresh_token is counted as one of the authorization types.
- AuthorizationServerEndpointsConfigurer here used to configure the access token endpoint and token service.
- The tokenServices Bean is used to configure some basic information about the Token, such as whether the Token supports refreshing, the storage location of the Token, the expiration date of the Token, and the expiration date of the refreshed Token. When a Token is about to expire, we need to obtain a new Token. When we obtain a new Token, we need to have a certificate information. This certificate information is not the old Token. It’s another refresh_token, which also has an expiration date.
Ok, now that our authorization server is configured, let’s start the authorization server.
If you feel confused about the above configuration, you can reply to OAuth2 in the background of the public account, and learn the OAuth2 tutorial of Songgo first.
2.2 Resource Server Construction
Next we set up a resource server. In the examples you see on the web, most resource servers are located with authorization servers, which is fine if the project is small, but not appropriate if the project is large.
The resource server is used to store users’ resources, such as your images and OpenID information on wechat. After users get an Access_token from the authorized server, they can request data from the resource server through the Access_token.
We created a new Spring Boot project called user-Server as our resource server, adding the following dependencies when we created it:
Once the project is successfully created, copy the previous AccessTokenConfig to the resource server and add the following configuration:
@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(); }}Copy the code
This configuration code is very simple, let me briefly say:
- First, configure the resource ID and TokenStore in the configure method. After the configuration, JwtAccessTokenConverter will be automatically called to parse the JWT, which contains the user’s basic information. So you don’t need to remotely verify access_token.
- Finally, configure the resource interception rules, which are basic in Spring Security and I won’t go over them.
Let’s configure two more test interfaces:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(a) {
return "hello";
}
@GetMapping("/admin/hello")
public String admin(a) {
return "admin"; }}Copy the code
After that, our resource server is configured successfully.
2.3 test
Start the authorization server and resource server respectively and access the authorization server to obtain the access_token:
Use the access_token to access the resource server:
OK, the test is fine.
3. The integration of the Swagger
Add swagger dependency to user-server
<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>
Copy the code
There are two dependencies added here, one for generating interface data and the other for swagger-UI to display data.
3.1 Authentication Mode 1
Request header with parameters, here we introduce two kinds, let’s look at the first one.
Configure a Docket instance as follows:
@Configuration
@EnableSwagger2
public class Swagger2Config {
@Bean
Docket docket(a) {
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("Description of interface document")
.title("Micro Personnel Item Interface Document")
.contact(new Contact("javaboy"."http://www.javaboy.org"."[email protected]"))
.version("v1.0")
.license("Apache2.0")
.build());
}
private SecurityScheme securitySchemes(a) {
return new ApiKey("Authorization"."Authorization"."header");
}
private SecurityContext securityContexts(a) {
return SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.any())
.build();
}
private List<SecurityReference> defaultAuth(a) {
AuthorizationScope authorizationScope = new AuthorizationScope("xxx"."Description");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
return Arrays.asList(new SecurityReference("Authorization", authorizationScopes)); }}Copy the code
The configuration here is a bit long, so LET me explain it to you:
- Start by enabling Swagger2 with the @enablesWagger2 annotation.
- Configure a Docket Bean that configures the mapping path and the location of the interface to scan.
- In apiInfo, configure Swagger2 document site information, such as site title, site description, contact information, protocol used, etc.
- Use securitySchemes to configure global parameters, in this case a request header called Authorization (the one you need to carry with OAuth2).
- Securitycontext is used to configure which requests need to carry Token, so we configured all of them.
After the configuration is complete, we also need to allow swagger-UI, otherwise swagger-UI related static resources will be intercepted by 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/**"); }}Copy the code
After configuration is complete, restart the user – server, the browser type http://localhost:8081/swagger-ui.html, the results are as follows:
As you can see, a Authorize button is added to the page. Click on this button and enter Bearer ${token} as follows:
After the input is complete, click the Authorize button to complete the authentication. Next, the various interfaces in the User-server can call the test directly.
The above method is more general, not only for OAuth2, but also for some other custom token login methods.
However, this method requires developers to obtain the access_token through other means first. Some people may find this a bit troublesome, but is there a better way? Look at method two.
3.2 Authentication Mode 2
The second authentication mode is to directly fill in the authentication information in Swagger, so that there is no need to obtain access_token externally. The effect is as follows:
So let’s see how this is configured.
Since swagger request /oauth/token interface is cross-domain, we will first modify auth-server to support cross-domain:
The modifications are mainly in two aspects. First, CorsFilter is configured to allow cross-domain, as follows:
@Configuration
public class GlobalCorsConfiguration {
@Bean
public CorsFilter corsFilter(a) {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
urlBasedCorsConfigurationSource.registerCorsConfiguration("/ * *", corsConfiguration);
return newCorsFilter(urlBasedCorsConfigurationSource); }}Copy the code
Then enable cross-domain support in 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(); }}Copy the code
After these two steps, cross-domain support on the server is enabled.
Next we modify the definition of the Docket bean in user-server:
@Configuration
@EnableSwagger2
public class Swagger2Config {
@Bean
Docket docket(a) {
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("Description of interface document")
.title("Micro Personnel Item Interface Document")
.contact(new Contact("javaboy"."http://www.javaboy.org"."[email protected]"))
.version("v1.0")
.license("Apache2.0")
.build());
}
private AuthorizationScope[] scopes() {
return new AuthorizationScope[]{
new AuthorizationScope("all"."all scope")}; }private SecurityScheme securityScheme(a) {
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(a) {
return SecurityContext.builder()
.securityReferences(Arrays.asList(new SecurityReference("OAuth2", scopes()))) .forPaths(PathSelectors.any()) .build(); }}Copy the code
This configuration is similar to the previous one except for the SecurityScheme. OAuthBuilder is used here to build, and the access address of the configuration token is obtained when building.
Ok, configuration complete, restart auth-server and user-server to test. The result of the test is the picture given by Songo.
The biggest advantage of this method is that you do not need to obtain the access_token in other ways. You can directly enter authentication parameters in password mode on the Swagger-UI page. Very convenient, limited to OAuth2 mode.
4. Summary
Swagger request header = Swagger request header = Swagger request header = Swagger request header
Download address of this case: github.com/lenve/sprin…
Well, if you feel that you have gained something, remember to click on songge