Wechat search public account “ancient kite”, a technical public account is not only technology. The Spring Cloud series is complete, and you can check out the full series on my Github.

In addition, there is probably the most detailed Spring Cloud OAuth2 authorization mode tutorial on the whole web, wechat login is the principle, these are the two most commonly used modes, we can learn together.

OAuth 2 has four licensing modes, They are the authorization Code mode, simplified mode, Resource owner password credentials mode, and Client credentials mode respectively. For details about OAuth2, please refer to this article. (http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html)


In this paper, we will use the authorization code mode and password mode to achieve user authentication and authorization management.

OAuth2 is actually a network standard about authorization, it develops the design ideas and operation process, using this standard we can actually achieve OAuth2 authentication process. Spring-cloud-starter-oauth2 is an implementation of Spring Cloud encapsulated in oAuth2 standard and spring-Security.

When should OAuth2 be used

First of all, we are most familiar with almost everyone has used, such as wechat login, QQ login, weibo login, with Google account login, github authorization login and so on, these are typical OAuth2 use scenarios. Suppose we make a service platform of our own, if we do not use OAuth2 login, then we need users to complete registration first, and then log in with the account password of registration number or mobile verification code. After using OAuth2, I believe that many people have used and even developed the public account web services, small programs, when we enter the web page, small program interface, the first use without registration, directly use wechat authorization login can greatly improve the use efficiency. Because everyone has a wechat account, with wechat you can immediately use third-party services, which is not a very good experience. For our service, we do not need to store the user’s password, but only the unique ID and user information returned by the authentication platform.

The above is the use of OAuth2 authorization code mode, the use of third-party authoritative platform to achieve user identity authentication. Of course, if your company has many services, you can specifically extract a certification center, this certification center will act as the role of the above authority certification platform, all services to do certification center.

It’s just a single sign-on feature. This is another usage scenario, for multi-service platforms, you can use OAuth2 to implement single sign-on of services, only do a single login, can freely travel through multiple services, of course only authorized services and interfaces.

Realize unified authentication function

This article first introduces the single sign-on (SSO) implemented by password mode, and we will continue with the authorization mode in the next article.

In micro services rampant today, who dare to say that their hands do not have a few micro services. Microservices reduce coupling between services and also increase system complexity in areas such as user authentication. Suppose we have implemented an e-commerce platform, and what users see is an APP or a Web site. In fact, behind it are multiple independent services, such as user service, order service, product service, etc. As long as the user enters the user name and password for the first time after logging in, the user can access any page for a period of time, such as product list page, my order page, my concerns page, etc.

We can imagine, naturally can think of, when requesting each service, each interface, must carry some credentials, and then each service knows which user is requesting the interface, otherwise there must be a problem, in fact, the credentials here is simply a Token, identification of the user’s identity Token.

System Architecture description

Authentication center: OAuth2-Auth-Server, oAuth2 main implementation end, Token generation, refresh, verification are completed in the authentication center.

Order service: Oauth2-client-order-server, one of the micro services, after receiving the request will be verified by the authentication center.

User service: Oauth2-client-user-server, the second micro service, after receiving the request will be verified by the authentication center.

Client: terminals such as APP and Web


The figure above depicts the request process between a client using OAuth2 and a microservice. The general process is that the client uses the user name and password to exchange the token to the authentication server, and then returns the token to the client. The client takes the token to request the data interface of each micro-service. Generally, the token is put in the header. When the micro service receives a request, it first takes the token to the authentication server to check the validity of the token. If the token is valid, it dynamically returns data according to the user’s role and permissions.

Create and configure the authentication server

The authentication server is configured most frequently. Verifying accounts and passwords, storing tokens, checking tokens, and refreshing tokens are all tasks of the authentication server.

1. Import the required Maven package

<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-data-redis</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-actuator</artifactId>

</dependency>

Copy the code

Spring-cloud-starter-oauth2 includes spring-cloud-starter-Security, so there is no need to introduce it separately. The redis package was introduced because here is a way to store tokens in Redis.

2. Configure application.yml

Set up the basic project configuration and add the redis configuration, which will be used later.

spring:

application:

name: auth-server

redis:

database: 2

host: localhost

port: 32768

password: 1qaz@WSX

jedis:

pool:

max-active: 8

max-idle: 8

min-idle: 0

timeout: 100ms



server:

port: 6001



management:

endpoint:

health:

enabled: true

Copy the code

3. Spring Security basic configuration

@EnableWebSecurity

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {



@Bean

public PasswordEncoder passwordEncoder(a) {

return new BCryptPasswordEncoder();

}



@Bean

@Override

public AuthenticationManager authenticationManagerBean(a) throws Exception {

return super.authenticationManagerBean();

}



/ * *

* Allows anonymous access to all interfaces mainly oauth interfaces

* @param http

* @throws Exception

* /


@Override

protected void configure(HttpSecurity http) throws Exception {

http.authorizeRequests()

.antMatchers("/ * *").permitAll();

}

}

Copy the code

Using the @ EnableWebSecurity annotations, and inherited from WebSecurityConfigurerAdapter class.

The main point of this class is to declare the PasswordEncoder and AuthenticationManager beans. You’ll need it later. BCryptPasswordEncoder is a password encryption tool class, which can achieve irreversible encryption, and AuthenticationManager is the authorization management Bean that must be specified in order to achieve the password mode of OAuth2.

4. Implement UserDetailsService

If you’ve used Security before, you’ll be familiar with this class, which is one of the simplest and most convenient ways to implement user authentication. There is also the way to combine the AuthenticationProvider, which will be expanded when we have a chance to talk about Security.

The core of the UserDetailsService is the loadUserByUsername method, which takes a string argument, the username passed in, and returns a UserDetails object.

@Slf4j

@Component(value = "kiteUserDetailsService")

public class KiteUserDetailsService implements UserDetailsService {





@Autowired

private PasswordEncoder passwordEncoder;



@Override

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

log.info("usernameis:" + username);

// Query database operations

if(! username.equals("admin")) {

throw new UsernameNotFoundException("the user is not found");

}else{

// The user role should also be obtained in the database

String role = "ROLE_ADMIN";

List<SimpleGrantedAuthority> authorities = new ArrayList<>();

authorities.add(new SimpleGrantedAuthority(role));

// The online environment should query the database by username to obtain the encrypted password

String password = passwordEncoder.encode("123456");

return new org.springframework.security.core.userdetails.User(username,password, authorities);

}

}

}

Copy the code

For demonstration purposes, the username, password, and role are all written in the code. In a formal environment, this would be obtained from the database or elsewhere based on the password and role encrypted by the username. Account admin and password 123456 will be used later when exchanging token. And set the role “ROLE_ADMIN” for this user.

5. OAuth2 configuration file

Create a configuration file inherits from AuthorizationServerConfigurerAdapter.

@Configuration

@EnableAuthorizationServer

public class OAuth2Config extends AuthorizationServerConfigurerAdapter {



@Autowired

public PasswordEncoder passwordEncoder;



@Autowired

public UserDetailsService kiteUserDetailsService;



@Autowired

private AuthenticationManager authenticationManager;



@Autowired

private TokenStore redisTokenStore;



@Override

public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

/ * *

* Redis token mode

* /


endpoints.authenticationManager(authenticationManager)

.userDetailsService(kiteUserDetailsService)

.tokenStore(redisTokenStore);



}



@Override

public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

clients.inMemory()

.withClient("order-client")

.secret(passwordEncoder.encode("order-secret-8888"))

.authorizedGrantTypes("refresh_token"."authorization_code"."password")

.accessTokenValiditySeconds(3600)

.scopes("all")

.and()

.withClient("user-client")

.secret(passwordEncoder.encode("user-secret-8888"))

.authorizedGrantTypes("refresh_token"."authorization_code"."password")

.accessTokenValiditySeconds(3600)

.scopes("all");

}



@Override

public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {

security.allowFormAuthenticationForClients();

security.checkTokenAccess("isAuthenticated()");

security.tokenKeyAccess("isAuthenticated()");

}

}

Copy the code

There are three overrides of the configure method.

The rewriting of AuthorizationServerEndpointsConfigurer parameters

endpoints.authenticationManager(authenticationManager)

.userDetailsService(kiteUserDetailsService)

.tokenStore(redisTokenStore);

Copy the code

AuthenticationManage () calls this method to support password mode.

UserDetailsService () Sets the user authentication service.

TokenStore () Specifies the token storage mode.

RedisTokenStore beans are defined as follows:

@Configuration

public class RedisTokenStoreConfig {



@Autowired

private RedisConnectionFactory redisConnectionFactory;



@Bean

public TokenStore redisTokenStore (a){

return new RedisTokenStore(redisConnectionFactory);

}

}

Copy the code

Rewriting ClientDetailsServiceConfigurer parameters, define here all the constraint conditions. including

ClientId and client-secret: These two parameters correspond to cleint-id and client-secret defined on the requester side

AuthorizedGrantTypes can include one or more of the following Settings:

  • Authorization_code: indicates the type of the authorization code.
  • Implicit: indicates the implicit authorization type.
  • Password: indicates the password of the resource owner (user).
  • Client_credentials: Type of client credentials (client IDS and keys).
  • Refresh_token: Obtain a new token by refreshing the token obtained by the above authorization.

AccessTokenValiditySeconds: the validity of the token

Scopes: Used to limit client access to scopes. The scope parameter will be added to the tokens exchanged for scopes. Only tokens that are within the scopes definition will be exchanged for scopes.

The above code is stored in inMemory, which hardcodes the configuration to memory. The way to do this in a formal environment is to persist to a database, such as mysql.

The specific approach is as follows:

  1. Add tables to the database and insert data
create 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)

);

INSERT INTO oauth_client_details

(client_id, client_secret, scope, authorized_grant_types,

web_server_redirect_uri, authorities, access_token_validity,

refresh_token_validity, additional_information, autoapprove)

VALUES

('user-client', '$2a$10$o2l5kA7z.Caekp72h5kU7uqdTDrlamLq.57M1F6ulJln9tRtOJufq', 'all',

'authorization_code,refresh_token,password', null, null, 3600, 36000, null, true);



INSERT INTO oauth_client_details

(client_id, client_secret, scope, authorized_grant_types,

web_server_redirect_uri, authorities, access_token_validity,

refresh_token_validity, additional_information, autoapprove)

VALUES

('order-client', '$2a$10$GoIOhjqFKVyrabUNcie8d.ADX.qZSxpYbO6YK4L2gsNzlCIxEUDlW', 'all',

'authorization_code,refresh_token,password', null, null, 3600, 36000, null, true);

Copy the code

Note: The client_secret field cannot be the original secret value directly and needs to be encrypted. Because is to use BCryptPasswordEncoder, so should be after BCryptPasswordEncoder. Insert the value of the encode () value.

  1. Then add the configuration for the database in the configuration file application.yml
spring:

datasource:

url: jdbc:mysql://localhost:3306/spring_cloud? characterEncoding=UTF-8&useSSL=false

username: root

password: password

hikari:

connection-timeout: 30000

idle-timeout: 600000

max-lifetime: 1800000

maximum-pool-size: 9

Copy the code

Hikari is used as the database connection pool by default after Spring Boot 2.0. If you use other connection pools, import related packages and add configurations accordingly.

  1. Add DataSource injection to OAuth2 configuration class (OAuth2Config)
@Autowired

private DataSource dataSource;

Copy the code
  1. willpublic void configure(ClientDetailsServiceConfigurer clients)Rewrite method changed to the following:
@Override

public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

JdbcClientDetailsServiceBuilder jcsb = clients.jdbc(dataSource);

jcsb.passwordEncoder(passwordEncoder);

}

Copy the code

There is also a method of rewriting a public void the configure (AuthorizationServerSecurityConfigurer security), the certification of the interface methods limit the client access.

security.allowFormAuthenticationForClients();

security.checkTokenAccess("isAuthenticated()");

security.tokenKeyAccess("isAuthenticated()");

Copy the code

The first line of code allows the client to access the OAuth2 authorization interface, otherwise the request token returns 401.

The second and third lines allow authorized users to access the checkToken interface and obtain the token interface, respectively.

Once you’re done, start the project and if you’re using IDEA you’ll see the RESTful interface associated with OAuth2 in the Mapping window below.


The main ones are as follows:

POST/OAuth/Authorize code mode Authenticates the authorization interface

GET/POST /oauth/ Token Interface for obtaining a token

POST /oauth/check_token Interface for checking token validity

Copy the code

Create a user client project

Having created the authentication server above, let’s start creating a client corresponding to the business related microservices in our system. Let’s assume that this microservice project manages user-related data, so it’s called a user client.

1. Reference the relevant Maven package

<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-data-redis</artifactId>

</dependency>

Copy the code

2. Application. Yml configuration file

spring:

application:

name: client-user

redis:

database: 2

host: localhost

port: 32768

password: 1qaz@WSX

jedis:

pool:

max-active: 8

max-idle: 8

min-idle: 0

timeout: 100ms

server:

port: 6101

servlet:

context-path: /client-user



security:

oauth2:

client:

client-id: user-client

client-secret: user-secret-8888

user-authorization-uri: http://localhost:6001/oauth/authorize

access-token-uri: http://localhost:6001/oauth/token

resource:

id: user-client

user-info-uri: user-info

authorization:

check-token-access: http://localhost:6001/oauth/check_token

Copy the code

Above is the general configuration information and redis configuration, the emphasis is on the following security configuration, the configuration here is a little careless will lead to 401 or other problems.

Client-id and client-secret must be the same as those configured in the authentication service, if inMemory or JDBC mode is used.

User-authorization-uri is required for authorization code authentication, which will be covered in the next article.

Access-token-uri is the interface used to obtain tokens in password mode.

Authorization. check-token-access is also key information. When the server receives the request from the client, it needs to take the token in the request to the authentication server for token authentication, which is the requested interface

3. Resource configuration files

In the OAuth2 concept, all interfaces are called resources, and the permissions of the interface are also the permissions of the resource, so Spring Security OAuth2 provides a resource comment @enableresourceserver, Similar to @enableWebSecurity.

@Configuration

@EnableResourceServer

@EnableGlobalMethodSecurity(prePostEnabled = true)

public class ResourceServerConfig extends ResourceServerConfigurerAdapter {



@Value("${security.oauth2.client.client-id}")

private String clientId;



@Value("${security.oauth2.client.client-secret}")

private String secret;



@Value("${security.oauth2.authorization.check-token-access}")

private String checkTokenEndpointUrl;



@Autowired

private RedisConnectionFactory redisConnectionFactory;



@Bean

public TokenStore redisTokenStore (a){

return new RedisTokenStore(redisConnectionFactory);

}



@Bean

public RemoteTokenServices tokenService(a) {

RemoteTokenServices tokenService = new RemoteTokenServices();

tokenService.setClientId(clientId);

tokenService.setClientSecret(secret);

tokenService.setCheckTokenEndpointUrl(checkTokenEndpointUrl);

return tokenService;

}



@Override

public void configure(ResourceServerSecurityConfigurer resources) throws Exception {

resources.tokenServices(tokenService());

}

}

Copy the code

Since redis is used as the token store, a Bean called tokenService needs to be specially configured to validate the token.

4. Finally, add a RESTful interface

@Slf4j

@RestController

public class UserController {



@GetMapping(value = "get")

//@PreAuthorize("hasAuthority('ROLE_ADMIN')")

@PreAuthorize("hasAnyRole('ROLE_ADMIN')")

public Object get(Authentication authentication){

//Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

authentication.getCredentials();

OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)authentication.getDetails();

String token = details.getTokenValue();

return token;

}

}

Copy the code

A RESTful method that can be accessed only when the user has the ROLE_ADMIN permission. Otherwise, 401 unauthorized is returned.

Through Authentication parameters or SecurityContextHolder. GetContext () getAuthentication () can get authorization information to look at it.

Test authentication function

1. Start the authentication server on port 6001

2. Start the user service client on port 6101

3. Request the authentication server to obtain the token

I use REST Client to do the access request, the request format is as follows:

POST http://localhost:6001/oauth/token? grant_type=password&username=admin&password=123456&scope=all

Accept: */*

Cache-Control: no-cache

Authorization: Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==

Copy the code

Suppose we use it on a Web side, where grant_type is password, indicating that this is the password mode using OAuth2.

Username =admin and password=123456 are equivalent to the username and password entered on the web login interface. The username and password are fixed as admin and 123456 in the authentication server configuration, but should be obtained by querying the database in the online environment.

Scope =all is permission-dependent; scope is specified as all in the OAuthConfig of the authentication service.

Authorization is added to the request header in the format of Basic blank base64(clientId:clientSecret). The client-ID of this micro-service client is user-client. Client-secret is user-secret-8888, connect these two values by a colon, And use base64 encoding (user – client: user – secret – 8888) after the value of dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA = =, Can be obtained via https://www.sojson.com/base64.html online code.


After running the request, if the parameters are correct, you get the following return in JSON format

{

"access_token": "9f958300-5005-46ea-9061-323c9e6c7a4d".

"token_type": "bearer".

"refresh_token": "0f5871f5-98f1-405e-848e-80f641bab72e".

"expires_in": 3599.

"scope": "all"

}

Copy the code

access_token : Token_type: bearer, which is the most commonly used form of access token refresh_token: This value can then be exchanged for a new token without entering the account password expires_in: token expiration time in seconds

4. Request the resource interface with the obtained token

We defined an interface in the user client http://localhost:6101/client-user/get, now take the last step for the token to the request of the interface.

GET http://localhost:6101/client-user/get

Accept: */*

Cache-Control: no-cache

Authorization: bearer ce334918-e666-455a-8ecd-8bd680415d84

Copy the code

Request header Authorization is also required in bearer + space + token format. Under normal circumstances, token will be returned as is according to the logic of the interface.

5. After the token expires, refresh_token is exchanged for access_token

Generally, the expiration time of the Access_Token is set to be smaller than the expiration time of the refresh_token, so that the user can obtain a new Access_token without logging in again.

# # # for access_token

POST http://localhost:6001/oauth/token? grant_type=refresh_token&refresh_token=706dac10-d48e-4795-8379-efe8307a2282

Accept: */*

Cache-Control: no-cache

Authorization: Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==

Copy the code

Grant_type is set to refresh_token.

Refresh_token is set to the value of refresh_token returned when the token is requested.

Add Authorization to the request header. The format is Basic + space + base64(client-id:client-secret).

A successful request returns the same data format as the requested token.

Replace redisToken with JWT

Spring Security OAuth2 also provides JDBC and JWT support for token storage. Spring Security OAuth2 also provides JDBC and JWT support for token storage.

With JWT, there is no need to store token to the server. JWT has its own special encryption method, which can effectively prevent data tampering. As long as the user password and other key information are not put into JWT, security can be ensured.

Authentication server transformation

Remove the redis configuration first.

Add the JwtConfig configuration class
@Configuration

public class JwtTokenConfig {



@Bean

public TokenStore jwtTokenStore(a) {

return new JwtTokenStore(jwtAccessTokenConverter());

}



@Bean

public JwtAccessTokenConverter jwtAccessTokenConverter(a) {

JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();

accessTokenConverter.setSigningKey("dev");

return accessTokenConverter;

}

}

Copy the code

JwtAccessTokenConverter is intended to do JWT data conversion because JWT has its own unique data format. If you’re not familiar with JWT, do a search to find out.

Change the OAuthConfig configuration class
@Autowired

private TokenStore jwtTokenStore;



@Autowired

private JwtAccessTokenConverter jwtAccessTokenConverter;



@Override

public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

/ * *

* Normal JWT mode

* /


endpoints.tokenStore(jwtTokenStore)

.accessTokenConverter(jwtAccessTokenConverter)

.userDetailsService(kiteUserDetailsService)

/ * *

* Support password mode

* /


.authenticationManager(authenticationManager);

}

Copy the code

Injection of JWT related Bean, then modify the configure (final AuthorizationServerEndpointsConfigurer endpoints) method for JWT storage mode.

Modify the user client

Modify the application.yml configuration file
security:

oauth2:

client:

client-id: user-client

client-secret: user-secret-8888

user-authorization-uri: http://localhost:6001/oauth/authorize

access-token-uri: http://localhost:6001/oauth/token

resource:

jwt:

key-uri: http://localhost:6001/oauth/token_key

key-value: dev

Copy the code

Note The SigningKey set on the authentication server JwtAccessTokenConverter must be the same as the key-value in the configuration file; otherwise, the JWT cannot be decoded normally and the authentication fails.

Configuration of the ResourceServerConfig class
@Configuration

@EnableResourceServer

@EnableGlobalMethodSecurity(prePostEnabled = true)

public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

@Bean

public TokenStore jwtTokenStore(a) {

return new JwtTokenStore(jwtAccessTokenConverter());

}



@Bean

public JwtAccessTokenConverter jwtAccessTokenConverter(a) {

JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();



accessTokenConverter.setSigningKey("dev");

accessTokenConverter.setVerifierKey("dev");

return accessTokenConverter;

}



@Autowired

private TokenStore jwtTokenStore;



@Override

public void configure(ResourceServerSecurityConfigurer resources) throws Exception {

resources.tokenStore(jwtTokenStore);

}

}

Copy the code
Run the request requesting the Token interface
POST http://localhost:6001/oauth/token? grant_type=password&username=admin&password=123456&scope=all

Accept: */*

Cache-Control: no-cache

Authorization: Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==

Copy the code

The result is as follows:

{

"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzE3NDM0OTQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE 1JTiJdLCJqdGkiOiI4Y2NhMjlhZi1lYTc3LTRmZTYtOWZlMS0zMjc0MTVkY2QyMWQiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCIsInNjb3BlIjpbImFsbC JdfQ.0Ik3UwB1xjX2le5luEdtVAI_MEyu_OloRRYtPOvtvwM".

"token_type": "bearer".

"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI4Y2NhMjlhZi1lYTc3LTRmZT YtOWZlMS0zMjc0MTVkY2QyMWQiLCJleHAiOjE1NzE3NzU4OTQsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iXSwianRpIjoiZjdkMjg4NDUtMmU2ZC00Zm RjLTg1OGYtMWNiY2RlNzI1ZmMyIiwiY2xpZW50X2lkIjoidXNlci1jbGllbnQifQ.vk_msYtbrAr93h5sK4wy6EC2_wRD_cD_UBS8O6eRziw".

"expires_in": 3599.

"scope": "all".

"jti": "8cca29af-ea77-4fe6-9fe1-327415dcd21d"

}

Copy the code

IO/jwt. IO/or jwt.calebb.net/ to decode the token


See, user_name, client_id, etc.

Request the user client interface with the returned token
GET http://localhost:6101/client-user/get

Accept: */*

Cache-Control: no-cache

Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzE3NDM0OTQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1 JTiJdLCJqdGkiOiI4Y2NhMjlhZi1lYTc3LTRmZTYtOWZlMS0zMjc0MTVkY2QyMWQiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCIsInNjb3BlIjpbImFsbCJ dfQ.0Ik3UwB1xjX2le5luEdtVAI_MEyu_OloRRYtPOvtvwM

Copy the code

Strengthen the JWT

What if I want to add additional fields (such as other user information) to the JWT? Sure. Spring Security OAuth2 provides the TokenEnhancer enhancer. In fact, not only JWT, RedisToken’s way can also be.

Declare an enhancer
public class JWTokenEnhancer implements TokenEnhancer {



@Override

public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {

Map<String, Object> info = new HashMap<>();

info.put("jwt-ext"."JWT Extended Information");

((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info);

return oAuth2AccessToken;

}

}

Copy the code

OAuth2Authentication can get the user name and other information, through which we can query the database or cache to get more information, and this information can be added as JWT extended information.

OAuthConfig configuration class modified

Injection enhancer

@Autowired

private TokenEnhancer jwtTokenEnhancer;



@Bean

public TokenEnhancer jwtTokenEnhancer(a){

return new JWTokenEnhancer();

}

Copy the code

Modify the configure (final AuthorizationServerEndpointsConfigurer endpoints) method

@Override

public void configure( final AuthorizationServerEndpointsConfigurer endpoints ) throws Exception{

/ * *

* JWT enhanced mode

* /


TokenEnhancerChain enhancerChain = new TokenEnhancerChain();

List<TokenEnhancer> enhancerList = new ArrayList<>();

enhancerList.add( jwtTokenEnhancer );

enhancerList.add( jwtAccessTokenConverter );

enhancerChain.setTokenEnhancers( enhancerList );

endpoints.tokenStore( jwtTokenStore )

.userDetailsService( kiteUserDetailsService )

/ * *

* Support password mode

* /


.authenticationManager( authenticationManager )

.tokenEnhancer( enhancerChain )

.accessTokenConverter( jwtAccessTokenConverter );

}

Copy the code
Request token again, return a jWT-ext field
{

"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYW xsIl0sImV4cCI6MTU3MTc0NTE3OCwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJhNDU1MWQ5ZS1iN2VkLTQ3NTktYjJmMS1mMGI5YjIxY2 E0MmMiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCJ9. 5 j4hnsvpktg2ikxnqr - q1rfcnhlyV3M6HUBx5cd6PiQ".

"token_type": "bearer".

"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYW xsIl0sImF0aSI6ImE0NTUxZDllLWI3ZWQtNDc1OS1iMmYxLWYwYjliMjFjYTQyYyIsImV4cCI6MTU3MTc3NzU3OCwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE 1JTiJdLCJqdGkiOiJmNTI3ODJlOS0wOGRjLTQ2NGUtYmJhYy03OTMwNzYwYmZiZjciLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCJ9.UQMf140CG8U0eWh08 nGlctpIye9iJ7p2i6NYHkGAwhY".

"expires_in": 3599.

"scope": "all".

"jwt-ext": "JWT Extended Information".

"jti": "a4551d9e-b7ed-4759-b2f1-f0b9b21ca42c"

}

Copy the code

The user client parses JWT data

If we add additional information to the JWT, we may need it, and after receiving the TOKEN in JWT format, the user client has to parse the JWT.

The introduction of JWT package
<dependency>

<groupId>io.jsonwebtoken</groupId>

<artifactId>jjwt</artifactId>

<version>0.9.1</version>

</dependency>

Copy the code
Add a RESTful interface in which to parse JWT
@GetMapping(value = "jwt")

@PreAuthorize("hasAnyRole('ROLE_ADMIN')")

public Object jwtParser(Authentication authentication){

authentication.getCredentials();

OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)authentication.getDetails();

String jwtToken = details.getTokenValue();

Claims claims = Jwts.parser()

.setSigningKey("dev".getBytes(StandardCharsets.UTF_8))

.parseClaimsJws(jwtToken)

.getBody();

return claims;

}

Copy the code

Note that the signature Settings must be the same as those on the authentication server.

The token from the previous step requests the interface above
# # # parse JWT

GET http://localhost:6101/client-user/jwt

Accept: */*

Cache-Control: no-cache

Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWx sIl0sImV4cCI6MTU3MTc0NTE3OCwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJhNDU1MWQ5ZS1iN2VkLTQ3NTktYjJmMS1mMGI5YjIxY2E 0 mmmilcjjbgllbnrfawqioij1c2vylwnsawvudcj9. 5 j4hnsvpktg2ikxnqr - q1rfcnhlyV3M6HUBx5cd6PiQ

Copy the code

The following content is returned:

{

"user_name": "admin".

"jwt-ext": "JWT Extended Information".

"scope": [

"all"

].

"exp": 1571745178.

"authorities": [

"ROLE_ADMIN"

].

"jti": "a4551d9e-b7ed-4759-b2f1-f0b9b21ca42c".

"client_id": "user-client"

}

Copy the code

The above is the complete process of password mode, the source code on Github, there is a need to go to see.

“Like” is a virtue, and it can also give me the motivation to create. You’re welcome. Like me!

If you search the official wechat account “Ancient Kites”, you can also scan the qr code below. After attention can be added wechat, and the group of small partners exchange learning, and Ali and other large factory students can directly push.

Click here to view the source code for this article