In “Spring Security oAuth2(a)” is mainly oAuth some basic concepts and four kinds of authentication, now officially started to complete several main examples, quickly start the Spring Security oAuth2 provided by Spring. Many online tutorials use JWT(JSON Web Tokens) to manage Tokens. Therefore, many people believe that oAuth2 should use JWT to manage Tokens. In fact, this is a very wrong idea. Take a look at the best use scenario for JWT.

Create the oAuth2 sample project

Create Maven project named spring-security-oauth2, pom. XML (spring-security-oauth2) as follows:

<? The XML version = "1.0" encoding = "utf-8"? > < project XMLNS = "http://maven.apache.org/POM/4.0.0" XMLNS: xsi = "http://www.w3.org/2001/XMLSchema-instance" Xsi: schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > < modelVersion > 4.0.0 < / modelVersion > < the parent > < groupId > org. Springframework. Boot < / groupId > The < artifactId > spring - the boot - starter - parent < / artifactId > < version > 2.3.10. RELEASE < / version > < relativePath / > <! -- lookup parent from repository --> </parent> <groupId>cn.tim</groupId> <artifactId>spring-security-oauth2</artifactId> < packaging > pom < / packaging > < version > 1.0 - the SNAPSHOT < / version > < modules > < module > spring ws-security - oauth2 - server < / module > <module>spring-security-oauth2-dependencies</module> </modules> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>cn.tim</groupId> < artifactId > spring ws-security - oauth2 - dependencies < / artifactId > < version > 1.0.0 - the SNAPSHOT < / version > < type > pom < type > <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>Copy the code

Spring-security-oauth2-server dependencies: Spring-security-OAuth2-server < span style = “box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; font-size: 14px! Important; word-break: inherit! Important;”

<? The XML version = "1.0" encoding = "utf-8"? > < project XMLNS = "http://maven.apache.org/POM/4.0.0" XMLNS: xsi = "http://www.w3.org/2001/XMLSchema-instance" Xsi: schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > The < modelVersion > 4.0.0 < / modelVersion > < groupId > cn. Tim < / groupId > < artifactId > spring ws-security - oauth2 - dependencies < / artifactId > < version > 1.0.0 - the SNAPSHOT < / version > <packaging>pom</packaging> <url>https://zouchanglin.cn</url> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <pluginRepositories> <pluginRepository> <id>spring-milestone</id> <name>Spring Milestone</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-snapshot</id> <name>Spring Snapshot</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> </pluginRepositories> </project>Copy the code

Next, create another child Module, spring-security-OAuth2-server, which acts as an authentication/authorization server Module. The corresponding POM.xml is as follows:

<? The XML version = "1.0" encoding = "utf-8"? > < project XMLNS = "http://maven.apache.org/POM/4.0.0" XMLNS: xsi = "http://www.w3.org/2001/XMLSchema-instance" Xsi: schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > < the parent > < artifactId > spring ws-security - oauth2 < / artifactId > < groupId > cn. Tim < / groupId > < version > 1.0 - the SNAPSHOT < / version > < / parent > < modelVersion > 4.0.0 < / modelVersion > < artifactId > spring ws-security - oauth2 - server < / artifactId > < properties > <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId>  </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <mainClass>cn.tim.security.server.OAuth2ServerApplication</mainClass> </configuration> </plugin> </plugins> </build> </project>Copy the code

That’s it. All that’s left is to write the code.

Store tokens based on memory

The memory-based token-based mode is used to demonstrate the most basic operations, so that you can quickly understand the basic concepts of “authentication”, “authorization”, “access token” in oAuth2 authentication server, as shown in the following figure:Step1: Request the authentication service to obtain the authorization code, and request the address:

GET http://localhost:8080/oauth/authorize? client_id=client&response_type=codeCopy the code

It carries client_id and response_type, the authentication type.

Step2: if the authentication passes, call back the registered URL with the parameter code

GET http://emaple.com/xxx?code=xxxxx
Copy the code

Step3: request the authentication server to obtain the token

POST http://yourClientId:yourSecret@localhost:8080/oauth/token
Copy the code

The parameters are grant_type:authorization_code, code: XXXXX (code is code in step2).

Step4: the authentication server returns the token

{
    "access_token": "5cb673e1-d5c1-4887-b766-0975b29ab74b",
    "token_type": "bearer",
    "expires_in": 43199,
    "scope": "app"
}
Copy the code

Step is the above step, now to write the authentication server configuration code. Create a class inherits AuthorizationServerConfigurerAdapter and add related annotation

package cn.tim.security.server.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; @Configuration @EnableAuthorizationServer public class AuthenticationServerConfiguration extends AuthorizationServerConfigurerAdapter { @Autowired private BCryptPasswordEncoder passwordEncoder; / / 1, based on the memory storage token @ Override public void the configure (ClientDetailsServiceConfigurer clients) throws the Exception {/ / configure the client Clients // use memory set.inmemory () // client_id. WithClient ("client") // client_secret The default encoder is used here. Secret (passwordencoder.encode ("secret")) // authorizedGrantTypes("authorization_code") // Authorization scope .scopes("app") // Register the callback address. RedirectUris ("https://example.com"); }}Copy the code

Then the server security configuration, create a class inherits WebSecurityConfigurerAdapter and add comments:

package cn.tim.security.server.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Bean public BCryptPasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication()  .withUser("admin").password(passwordEncoder().encode("123456")).roles("ADMIN") .and() .withUser("user").password(passwordEncoder().encode("123456")).roles("USER"); }}Copy the code

The overall project structure is as follows:Now access to obtain the authorization code, open a browser, enter the address:

http://localhost:8080/oauth/authorize?client_id=client&response_type=code
Copy the code

The login page is redirected:After successful authentication, the user is asked whether to authorize the clientThe browser address also contains an authorization code (CODE =j4jmTM). The browser address bar will display the following address:

https://example.com/?code=j4jmTM
Copy the code

Curl curl curl curl curl curl curl curl curl curl curl curl curl curl curl curl curl curl curl

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

The results are as follows:

{    "access_token":"b15c7d2d-b8ea-41e7-ac09-c7fd1517114b",    "token_type":"bearer",    "expires_in":43199,    "scope":"app"}
Copy the code

Now try using a PostMan request:PostMan access_token request PostMan access_token request PostMan access_token request PostMan access_token

Access the authentication server through a GET request to obtain the authorization code -> endpoint: /oauth/authorize

Access the authentication server with the authorization code via a POST request to obtain the token -> Endpoint: /oauth/token

The default endpoint URL is as follows:

/oauth/authorize: Authorization endpoint /oauth/token: token endpoint /oauth/ confirM_access: user confirms authorization submission endpoint /oauth/error: Authorization service error message endpoint /oauth/check_token: Token-resolution endpoint /oauth/token_key for resource service access: endpoint that provides the public key, if you use the JWT tokenCopy the code

Store tokens based on JDBC

Storing tokens based on JDBC is similar to memory, except this time the client information is not written in code, but in the database. Configure the authentication server – configure DataSource: DataSource – configure the token storage mode: TokenStore -> JdbcTokenStore – configure the client read mode: ClientDetailsService – > JdbcClientDetailsService – configure the service endpoint information: AuthorizationServerEndpointsConfigurer – tokenStore: Set the token storage mode – configuration client information: ClientDetailsServiceConfigurer – withClientDetails: set the client configuration read mode 4, configuration, Web security – configuration password encryption mode: BCryptPasswordEncoder – configure authentication information: AuthenticationManagerBuilder 5, through the GET request access authentication server for authorization code – endpoint: /oauth/authorize 6. Access the authentication server using the authorization code through POST requests to obtain the token. -Endpoint: /oauth/token

Initialize oAuth2 tables with the following address:

https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sq lCopy the code

MySQL > create a table with VARCHAR(256) as the primary key, which exceeds the maximum primary key length (128). Replace LONGVARBINARY with BLOB.

create database oauth2 charset utf8; use oauth2; -- used in tests that use HSQLcreate table oauth_client_details( client_id VARCHAR(256) PRIMARY KEY, resource_ids VARCHAR(256), client_secret VARCHAR(256), scope VARCHAR(256), authorized_grant_types VARCHAR(256), web_server_redirect_uri VARCHAR(256), authorities VARCHAR(256), access_token_validity INTEGER, refresh_token_validity INTEGER, additional_information VARCHAR(4096), autoapprove VARCHAR(256)); create table oauth_client_token( token_id VARCHAR(256), token BLOB, authentication_id VARCHAR(256) PRIMARY KEY, user_name VARCHAR(256), client_id VARCHAR(256)); create table oauth_access_token( token_id VARCHAR(256), token BLOB, authentication_id VARCHAR(256) PRIMARY KEY, user_name VARCHAR(256), client_id VARCHAR(256), authentication BLOB, refresh_token VARCHAR(256)); create table oauth_refresh_token( token_id VARCHAR(256), token BLOB, authentication BLOB); create table oauth_code( code VARCHAR(256), authentication BLOB); create table oauth_approvals( userId VARCHAR(256), clientId VARCHAR(256), scope VARCHAR(256), status VARCHAR(10), expiresAt TIMESTAMP, lastModifiedAt TIMESTAMP); -- customized oauth_client_details tablecreate table ClientDetails( appId VARCHAR(256) PRIMARY KEY, resourceIds VARCHAR(256), appSecret VARCHAR(256), scope VARCHAR(256), grantTypes VARCHAR(256), redirectUrl VARCHAR(256), authorities VARCHAR(256), access_token_validity INTEGER, refresh_token_validity INTEGER, additionalInformation VARCHAR(4096), autoApproveScopes VARCHAR(256));Copy the code

Add a client configuration record to the oAUTH_client_details table. The following fields need to be set: client_id: client id client_secret: Scope: client authorization scope authorized_grant_types: client authorization type web_server_redirect_URI: Client_secret The BCryptPasswordEncoder is used to encrypt the client security code. The code is as follows:

System.out.println(new BCryptPasswordEncoder().encode("secret"));
Copy the code

Druid is the fastest connection pool in the world. HikariCP is the fastest connection pool in the world. Druid is the fastest connection pool in the world.

<dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> < version > 3.4.5 < / version > < / dependency > < the dependency > < groupId > org. Springframework. Boot < / groupId > <artifactId>spring-boot-starter-jdbc</artifactId> <exclusions> <! -- Exclude tomcat-JDBC to use HikariCP --> <groupId>org.apache.tomcat</groupId> <artifactId> tomcat-JDBC </artifactId>  </exclusion> </exclusions></dependency><dependency> <groupId>mysql</groupId> < artifactId > mysql connector - Java < / artifactId > < version > 8.0.23 < / version > < / dependency >Copy the code

Then configure the authentication server, create a class inherits AuthorizationServerConfigurerAdapter and add comments:

package cn.tim.security.server.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; import javax.sql.DataSource; @Configuration@EnableAuthorizationServerpublic class AuthenticationServerConfiguration extends AuthorizationServerConfigurerAdapter {/ / 2, based on JDBC access token @ Bean @ Primary @ ConfigurationProperties (prefix = "Spring. Datasource ") public datasource datasource (){// Configure the datasource (note that I am using the HikariCP connection pool). Return datasourceBuilder.create ().build(); } @bean public TokenStore TokenStore(){return JdbcTokenStore(dataSource()); } @bean public ClientDetailsService jdbcClientDetails(){ Return new JdbcClientDetailsService(dataSource()); } @ Override public void the configure (ClientDetailsServiceConfigurer clients) throws the Exception {/ / set the token clients.withClientDetails(jdbcClientDetails()); } @ Override public void the configure (AuthorizationServerEndpointsConfigurer endpoints) {/ / read the client configuration endpoints.tokenStore(tokenStore()); }}Copy the code

Then the server security configuration, create a class inherits WebSecurityConfigurerAdapter and add the relevant comments, and based on the memory store is the same as the token, there is no longer post code. Finally, configure the application.yml connection parameters:

spring: application: name: oauth2-server datasource: type: com.zaxxer.hikari.HikariDataSource jdbcUrl: JDBC: mysql: / / 127.0.0.1:3306 / oauth2? useUnicode=true&characterEncoding=utf-8&useSSL=false driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 12345678 hikari: minimum-idle: 5 idle-timeout: 600000 maximum-pool-size: 10 auto-commit: true pool-name: MyHikariCP max-lifetime: 1800000 connection-timeout: 30000 connection-test-query: SELECT 1server: port: 8080Copy the code

The test procedure is the same as aboveAfter the operation succeeds, a record will be added to the database oAUTH_access_token table. The effect picture is as follows:

About the applicable scenarios of JWT

Before we talk about this question, we need to understand what JWT is. For this part, please refer to Ruan Yifeng’s blog “JSON Web Token Tutorial”. Many people make the mistake of comparing cookies with JWT. The comparison is meaningless — cookies are a storage mechanism and JWT is a token mechanism for encrypting signatures. They are not opposed to each other, but can be used independently or in combination. What you should really compare is session versus JWT and cookies versus Local Storage.

Let’s go back to authentication/authorization:

  • Authentication: Verifies the identity of the target object. For example, logging in to a system with a user name and password is authentication.
  • Authorization: Grants operation rights to target objects that pass the authentication.

To put it more simply: Certification solves the question of who you are, and licensing solves the question of what you can do. HTTP is stateless, so the client and server need to figure out how to make the conversation stateful. For example, only logged-in users have the permission to invoke certain interfaces. After logging in, you need to remember that the user is logged in. The common method is to use the session mechanism.

JWT just packages the user information and expiration information into tokens and does not need to be stored on the server side. How to store JWT on the server side (such as memory and REDis) to realize renewal, logout and other scenarios is equivalent to recreating the session implemented by the Web server again. Besides the encryption and decryption resources of JWT, there is no session mechanism for security.

A clear mobile example comes to mind. If you want to use UDP to implement reliable transport, which means you need to manually implement TCP features, you might as well use TCP directly. Similarly, if you use JWT to manage sessions, you need to manually extend to handle renewals. Log out and so on. It’s better to just use sessions.

So what is the most appropriate application scenario for JWT? That is a one-time verification, such as users need to send an email to activate account, usually need to have a link in the email, this link needs to have the following features: able to identify the user, the link has timeliness (usually only allow a few hours to activate), cannot be tampered with to activate other possible accounts. This scenario is very similar to JWT, where the payload is a fixed parameter for which iss writer and EXP expiration time are prepared. JWT is suitable for simple Restful API authentication. Issue a JWT with a fixed validity period to reduce the risk of JWT exposure. Do not use JWT for server state management, so as to reflect the advantages of JWT stateless.

The resources

[Stop Using JWT for Sessions](