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.

The last article is probably the most detailed single sign-on tutorial on the web. In fact, it only introduces the use of password mode for authentication and single sign-on.

This article will continue to talk about another authorization mode of Spring Cloud OAuth2, that is the authorization code mode, if you have integrated wechat, Weibo log function, it must have a certain understanding of this mode. Next, follow this tutorial to make sure you have a better understanding of how the license code pattern works and how it can be applied to your projects.

Authentication process in authorization code mode

The authentication process in authorization code mode is as follows:

1. The user client requests the authentication interface of the authentication server and attaches the callback address.

2. The authentication service interface adjusts to its login interface after receiving the authentication request.

3. Enter the user name and password and click OK to jump to the authorization and rejection prompt page (you can also omit it).

4. After clicking authorization or default authorization, the user will jump to the callback address of the micro-service client and pass in the parameter code;

5. Generally, the callback address is a RESTful interface. After receiving the code parameter, the interface requests the token of the authentication server to obtain information such as access_token.

6. After obtaining the access_token, request the interface of each micro-service client with the token.

Note the user client mentioned above can be understood as a browser, app, micro service client is such as orders in our system, customer service, such as micro service, authentication server is used for certificate authority service, relative to the authentication server, micro various business services is also called the client.

Authentication server configuration

The authentication server continues the configuration described in the previous article, and the code does not need to change, except to add a record to the database to support the authentication of the newly added microservice client

The client-ID of the client we want to create is code-client, and the client-secret is code-secret-8888, but it also needs to be encrypted, which can be obtained with the following code:

System.out.println(new BCryptPasswordEncoder().encode("code-secret-8888"));

Copy the code

In addition to these two parameters, set authorized_grant_types to authorization_code,refresh_token, and web_server_redirect_URI to the callback address. This interface is later created by the microservice client.

This record is then organized and inserted into the database.

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

('code-client', '$2a$10$jENDQZRtqqdr6sXGQK.L0OBADGIpyhtaRfaRDTeLKI76I/Ir1FDn6', 'all',

'authorization_code,refresh_token', 'http://localhost:6102/client-authcode/login', null, 3600, 36000, null, true);

Copy the code

Create a microservice in authorization mode

Introduction of maven packages

<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>io.jsonwebtoken</groupId>

<artifactId>jjwt</artifactId>

<version>0.9.1</version>

</dependency>

<dependency>

<groupId>com.squareup.okhttp3</groupId>

<artifactId>okhttp</artifactId>

<version>3.14.2</version>

</dependency>

<dependency>

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

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

</dependency>

Copy the code

Okhttp and Thymeleaf were introduced to make a simple page and simulate a normal authentication process.

Configuration file application.yml

spring:

application:

name: client-authcode

server:

port: 6102

servlet:

context-path: /client-authcode





security:

oauth2:

client:

client-id: code-client

client-secret: code-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

authorization:

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

Copy the code

** Create resourceConfig **

@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);

}



@Override

public void configure(HttpSecurity http) throws Exception {

http.authorizeRequests().antMatchers("/login").permitAll();

}

}

Copy the code

Use JWT as the token store. Note that unauthorized access is allowed to the /login interface. This address is the authentication callback address that returns the code parameter.

Create the Application.java startup class

@SpringBootApplication

public class Application {



public static void main(String[] args) {

SpringApplication.run(Application.class, args);

}

}

Copy the code

You can stop at this point. Let’s start up the authentication server and the authentication client we just created to test it manually. The callback interface hasn’t been created yet, it doesn’t matter, let’s just say that address is now just to receive the code parameter. ** authorize /oauth/authorize interface in the browser. The interface address is:

http://localhost:6001/oauth/authorize?client_id=code-client&response_type=code&redirect_uri=http://localhost:6102/client -authcode/login

Copy the code

Note that the response_type parameter is set to code and redirect_uri is set to the callback address inserted into the database.

**2. ** the login page of the authentication server is displayed. In this case, enter the user name admin and the password 123456


**3. ** Click “CONFIRM” to come to the authorization confirmation page, on which there are two buttons Authorize and Deny (Authorize and Deny). You can cancel the display of this page by setting the AutoApprove field to 0, and by default grant authorization directly.


After 4, * * * * click on authorize, jump to the callback address, although it is 404, but we just to get code parameter, pay attention to the address at the back of the code parameter.


** * get this code parameter to request access_token from the authentication server /oauth/token interface. Continue to send the request using REST Client.

Note that grant_type is set to authorization_code. Code is the address of the previous callback. Redirect_uri must still be carried as a validation condition.

Request header Authorization, again Basic + space + base64(client_id:client_secret), Can do it through https://www.sojson.com/base64.html online base64 encoding.

Code – client: code – secret – 8888 through the results for Y29kZS1jbGllbnQ6Y29kZS1zZWNyZXQtODg4OA = = after base64 encoding

POST http://localhost:6001/oauth/token? grant_type=authorization_code&client=code-client&code=BbCE34&redirect_uri=http://localhost:6102/client-authcode/login

Accept: */*

Cache-Control: no-cache

Authorization: Basic Y29kZS1jbGllbnQ6Y29kZS1zZWNyZXQtODg4OA==

Copy the code

After the request is sent, the following JSON content is returned:

{

"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYW xsIl0sImV4cCI6MTU3MjYwMTMzMiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiI2OWRmY2M4Yy1iZmZiLTRiNDItYTZhZi1hN2IzZWUyZj I1ZTMiLCJjbGllbnRfaWQiOiJjb2RlLWNsaWVudCJ9.WlgGnBkNdg2PwKqjbZWo6QmUmq0QluZLgIWJXaZahSU".

"token_type": "bearer".

"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYW xsIl0sImF0aSI6IjY5ZGZjYzhjLWJmZmItNGI0Mi1hNmFmLWE3YjNlZTJmMjVlMyIsImV4cCI6MTU3MjYzMzczMiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE 1JTiJdLCJqdGkiOiJkNzk2OWRhMS04NTg4LTQ2YzMtYjdlNS1jMGM5NzcxNTM5Y2YiLCJjbGllbnRfaWQiOiJjb2RlLWNsaWVudCJ9.TEz0pQOhST9-ozdoJ Wm6cf1SoWvPC6W-5JW9yjZJXek".

"expires_in": 3599.

"scope": "all".

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

"jti": "69dfcc8c-bffb-4b42-a6af-a7b3ee2f25e3"

}

Copy the code

This is consistent with the password mode in the previous article, and all subsequent requests will require an access_token.

Access_token = access_token = access_token = access_token = access_token = access_token

GET http://localhost:6102/client-authcode/get

Accept: */*

Cache-Control: no-cache

Authorization: bearer ${access_token}

Copy the code

The interface content is as follows:

@org.springframework.web.bind.annotation.ResponseBody

@GetMapping(value = "get")

@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

After the above manual tests, this process is proved to be common, but not yet automated. If you have integrated wechat login, then you must know what we did in the callback address, get the code parameter returned to the token interface to exchange for access_token, is that right? Yes, the idea is the same, our callback interface also needs to exchange for access_token with code.

To do this, I made a simple page and requested the token interface in the callback interface.

Create a simple login page

Create a templates directory in the Resources directory to store thymeleaf’s templates. No styling, just a simple demonstration. Create the index.html template as follows:


      

<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>

<meta charset="UTF-8">

<title>Ancient Kite -OAuth2 Client</title>

</head>

<body>

<div>

<a href="http://localhost:6001/oauth/authorize? client_id=code-client&response_type=code&redirect_uri=http://localhost:6102/client-authcode/login">The login</a>

<span th:text=${username} = ${username}></span>

<span th:text="${accessToken}"></span>

</div>

</body>

</html>

Copy the code

Callback interface and other interfaces

@Slf4j

@Controller

public class CodeClientController {



/ * *

* Used to display the index.html template

* @return

* /


@GetMapping(value = "index")

public String index(a){

return "index";

}



@GetMapping(value = "login")

public Object login(String code,Model model) {

String tokenUrl = "http://localhost:6001/oauth/token";

OkHttpClient httpClient = new OkHttpClient();

RequestBody body = new FormBody.Builder()

.add("grant_type"."authorization_code")

.add("client"."code-client")

.add("redirect_uri"."http://localhost:6102/client-authcode/login")

.add("code", code)

.build();



Request request = new Request.Builder()

.url(tokenUrl)

.post(body)

.addHeader("Authorization"."Basic Y29kZS1jbGllbnQ6Y29kZS1zZWNyZXQtODg4OA==")

.build();

try {

Response response = httpClient.newCall(request).execute();

String result = response.body().string();

ObjectMapper objectMapper = new ObjectMapper();

Map tokenMap = objectMapper.readValue(result,Map.class);

String accessToken = tokenMap.get("access_token").toString();

Claims claims = Jwts.parser()

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

.parseClaimsJws(accessToken)

.getBody();

String userName = claims.get("user_name").toString();

model.addAttribute("username", userName);

model.addAttribute("accessToken", result);

return "index";

} catch (Exception e) {

e.printStackTrace();

}

return null;

}





@org.springframework.web.bind.annotation.ResponseBody

@GetMapping(value = "get")

@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

The index() method is used to display the Thymeleaf template, and the login method is the callback interface. Okhttp3 is used as the interface request to request the authentication server /oauth/token interface in exchange for access_token. It just automates the steps that we do manually.

Visit the index.html page

We assume that this page is the front page of a web site, not login user will see the login button on the website, we visit this page: http://localhost:6102/client-authcode/index, see page is like this


Next, click the login button. It can be seen from the template code above that clicking on it actually means jumping to the address we visited in the first step of manual test. The following operations are consistent with the above manual test: enter the user name and password and click Agree authorization.

Next, page jump and adjustable address < http://localhost:6102/client-authcode/login? When code= XXX, the login method takes the code parameter, constructs the POST request body, adds Authorization to the request header, and then requests the OAUth/Token interface. Finally, the token and username resolved by the token are returned to the front end, and the final result is as follows:


Finally, the client with the token can add the token to the request header to access the interface that requires authorization.

Combined with the previous article, we have implemented oAUTH2 authentication in two modes: password and authorization code.

added

When I first published this article on my official account, some students asked the following questions after reading it.

Excuse me, I am on the authentication server, how can I know which user code corresponds to? Which step makes the connection?

The explanation is as follows:


Some students may not be clear which is the authentication server, which is the client, also blame my project name is a bit ambiguous, the figure shows the source of the server and client, we can look at the code.


The above step is the process of entering the username and password. This step generates the code, and the corresponding relationship between the code and the user should be saved on the authentication server.

After that, it jumps to the callback address, which takes the code to authenticate the server to request the token, queries the user for the code (note that there is an expiration time, such as 5 minutes), and returns the token with it.

The following is the JSON string that returns the token. This structure can have extended attributes, and can add user ID or user name information. For example, wechat login will return openId and unionId.

{

"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYW xsIl0sImV4cCI6MTU3MjYwMTMzMiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiI2OWRmY2M4Yy1iZmZiLTRiNDItYTZhZi1hN2IzZWUyZj I1ZTMiLCJjbGllbnRfaWQiOiJjb2RlLWNsaWVudCJ9.WlgGnBkNdg2PwKqjbZWo6QmUmq0QluZLgIWJXaZahSU".

"token_type": "bearer".

"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYW xsIl0sImF0aSI6IjY5ZGZjYzhjLWJmZmItNGI0Mi1hNmFmLWE3YjNlZTJmMjVlMyIsImV4cCI6MTU3MjYzMzczMiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE 1JTiJdLCJqdGkiOiJkNzk2OWRhMS04NTg4LTQ2YzMtYjdlNS1jMGM5NzcxNTM5Y2YiLCJjbGllbnRfaWQiOiJjb2RlLWNsaWVudCJ9.TEz0pQOhST9-ozdoJ Wm6cf1SoWvPC6W-5JW9yjZJXek".

"expires_in": 3599.

"scope": "all".

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

"jti": "69dfcc8c-bffb-4b42-a6af-a7b3ee2f25e3"

}

Copy the code

After the above steps, the association between the user and code is established.

Creation is not easy, please give me a thumbs-up, small thumbs-up, big warm, can give me the motivation to continue to create. Don’t mention it. 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.

Yesterday, many students saw the article to pay attention to the public account, and into the exchange group, the group family has expanded, ha ha.

Click here to view the source code for this article