Preface: This article intends to throw a brick to attract jade, to help everyone to build up the basic environment, the specific actual combat plan should be formulated according to their own business needs. Instead of using Spring Security OAuth2 to build the authorization service, we implemented our own service entirely based on the OAuth2 standard.

Spring Cloud Security OAuth2 is Spring’s open source implementation of OAuth2. The advantage is that it can seamlessly integrate with Spring Cloud stack. If all the default configuration is used, developers only need to add annotations to complete the construction of OAuth2 authorization services.

1. Add dependencies

The authorization service is based on Spring Security, so two dependencies need to be introduced into the project:

 <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-security</artifactId>
</dependency>

<dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-oauth2</artifactId>
 </dependency>Copy the code

The former is Security, and the latter is an OAuth2 extension of Security.

2. Add comments and configurations

In starting the class add @ EnableAuthorizationServer comments:

@SpringBootApplication @EnableAuthorizationServer public class AlanOAuthApplication { public static void main(String[] args) { SpringApplication.run(AlanOAuthApplication.class, args); }}Copy the code

With that done, the skeleton of our licensing service is in place. But to get through the process, we must assign client_id, client_secret. Spring Security OAuth2 Configuration approach is to write @ AuthorizationServerConfigurerAdapter Configuration class inheritance, Then rewrite the void the configure (ClientDetailsServiceConfigurer clients) method, such as:

@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() // Use in-memory for.withclient ("client") // client_id. Secret ("secret") // client_secret .authorizedGrantTypes("authorization_code") // The authorized types allowed by the client. Scopes ("app"); }}Copy the code

3. Authorization process

Access authorization page:

localhost:8080/oauth/authorize? client_id=client&response_type=code&redirect_uri=http://www.baidu.comCopy the code

At this point, the browser will ask you to enter a username and password because Spring Security adds Basic Auth authentication to all urls by default. The default user name is User, and the password is randomly generated, as can be seen in the console log.

The painting style is very simple, but the basic functions are available. After clicking Authorize, the browser will redirect to Baidu with the code parameter:

Once you get the code, you can call it

POST/GET http://client:secret@localhost:8080/oauth/tokenCopy the code

For access_token:

curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=Li4NZo&redirect_uri=http://www.baidu.com' "http://client:secret@localhost:8080/oauth/token"Copy the code

Note that the URL of the client as the above through ClientDetailsServiceConfigurer class specified clientId. Since client_secret is not required for authorization_code authorization, secret can be filled in with any value

Returns the following:

{"access_token": "32A1CA28-BC7A-447-88A1-C95ABCC30556 ", // Token "token_type": "bearer", "expires_in": 2591999, "scope": "app" }Copy the code

At this point, our most basic authorized services are set up. However, this is just a demo, and more work is needed if it is to be used in a production environment.

4. Use MySQL to store access_token and client information

In the above example, all the token information is stored in memory, which is obviously not usable in a production environment (all tokens are lost after the process ends and the user needs to be reauthorized), so we need to persist this information. It is not difficult to store the authorization server data in the database, because Spring Cloud Security OAuth already has a set of schemas and corresponding DAO objects designed for us. But before we can use it, we need to have some understanding of the relevant classes.

4.1 Related Interfaces

Spring Cloud Security OAuth2 uses DefaultTokenServices to complete the business logic stipulated by OAuth2 standard, such as token generation, expiration, etc. DefaultTokenServices, in turn, persists the generated data through the TokenStore interface. In the demo above, the default implementation of TokenStore is InMemoryTokenStore, or memory storage. For Client information, ClientDetailsService interface reads data from a storage warehouse, used in the demo above default is InMemoryClientDetialsService implementation class. As you can see at this point, all you need to do to use database storage is provide implementation classes for these interfaces. Fortunately, the framework already has JDBC implementations written for us, namely JdbcTokenStore and JdbcClientDetailsService.

Table 4.2 built

To use these JDBC implementations, you first need to build tables. The framework designed the schema for us in advance, on Github: github.com/spring-proj…

Before using this table structure, note that the default primary key in MySQL is vARCHAR (255). The default primary key in MySQL is vARCHar (255). So I can change this to 128. Second, there will be some fields in the statement that are of type LONGVARBINARY, which corresponds to the mysql BLOB type and also needs to be modified.

4.3 configuration

Once the database is set up, the next step is to configure the framework to use JDBC implementation. Methods or write @ AuthorizationServerConfigurerAdapter Configuration class inheritance:

@Autowired private AuthenticationManager authenticationManager; @Autowired private DataSource dataSource; Public TokenStore TokenStore() {return new JdbcTokenStore(dataSource); Public ClientDetailsService ClientDetails () {return new JdbcClientDetailsService(dataSource); } @ Override / / configuration framework that is applied to implement public void the configure (AuthorizationServerEndpointsConfigurer endpoints) throws the Exception { endpoints.authenticationManager(authenticationManager); endpoints.tokenStore(tokenStore()); DefaultTokenServices TokenServices = new DefaultTokenServices(); tokenServices.setTokenStore(endpoints.getTokenStore()); tokenServices.setSupportRefreshToken(false); tokenServices.setClientDetailsService(endpoints.getClientDetailsService()); tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer()); tokenServices.setAccessTokenValiditySeconds( (int) TimeUnit.DAYS.toSeconds(30)); 30 days / / endpoints. TokenServices (tokenServices); }Copy the code

Once this is done, the framework writes the generated data to mysql.oauth_client_detailsAdd a client to this table by adding a record to it:

4.4 Points needing attention

I have to say there is a weird aspect to Spring design. Note that the oAUTH_ACCESS_Token table holds access tokens, but does not hold tokens directly in fields. Spring uses OAuth2AccessToken to abstract all token-related attributes. When writing to the database, Spring stores the object in bytes directly into the token field of the table through the JDK’s built-in serialization mechanism. In other words, if you just look at the data table, you can’t see the value of the access_token, the expiration date, etc. This brings trouble to the implementation of resource server. Our resource provider does not use Spring Security and does not want to introduce any Spring Security dependencies. In this case, we have to copy the source code of DefaultOAuth2AccessToken into the resource provider’s project. Then read the Token field and deserialize the restore object to get the token information. Will but if you do meet the deserialization compatibility problem, specific solutions refer to my another blog post: blog.csdn.net/neosmith/ar…

5. To summarize

At this point, an authorized service is set up that can be used in a production environment. In fact, we should properly customize JdbcTokenStore or ClientDetailsService to meet the needs of the business, or even directly implement the interface from 0, completely without the implementation provided by the framework. In addition, Spring’s design to serialize DefaultOAuth2AccessToken directly into bytes and store them in the database is, IN my opinion, quite unreasonable. Perhaps the designer intended to keep the access_token secret, but it can be done by encrypting it and should not be thrown directly at all. However, by customizing the TokenStore interface, we can use our own table structure instead of sticking to the default implementation.

6. Personal opinion

Spring’s OAuth2 implementation is a bit too complicated. OAuth2 itself is a very simple protocol that can be implemented freely on the basis of SpringMVC without difficulty or complexity. I think a lot of people are worried about the robustness of the oAuth2 protocol because it is too complex to implement. If I were working on my own project, I would definitely not use any framework.


Github address: github.com/wanghongfei…