An OAuth2.0 authorization server design

In the last article, I introduced the basic concepts of the OAuth2.0 protocol and what you need to do as a third-party application when requesting authorization from the server. Through the example of invoking Baidu OAuth service in the previous article, we can know that the following three steps are needed to complete the process of OAuth2.0 authorization using the authorization code mode:

  1. The client requests the Authorization server to obtain the Authorization Code.
  2. The client requests the Authorization server again through Authorization Code to obtain the Access Token.
  3. The client obtains basic user information by using the Access Token returned by the server.

Therefore, the design of OAuth2.0 authorization server is mainly based on these interfaces, and its main process is as follows:

Once you understand the process, the rest will take care of itself. What we need to do next is to design the table structure of the database.

Table structure design of database

Tip: I only cover some of the main table fields below. The full table structure used in this Demo can be found at: gitee.com/zifangsky/O…

(1) Auth_client_details:

View the information table of the third-party client. This is the same as if we want to use Baidu OAuth service, we need to build a new application in Baidu developer Center in advance. Each third-party client that wants to access OAuth2.0 authorization service needs to “record” here in advance, so it mainly needs the following fields:

  • client_id: Each clientclient_idIs unique and is usually a randomly generated string
  • client_name: Indicates the client name
  • client_secret: This key is held jointly by the client and the OAuth2.0 server to authenticate requests, and is usually a randomly generated string

(2) auth_scope:

User information range table. When the OAuth2.0 server authorizes the third-party client to access the user’s information, it usually divides the user’s information into several levels, such as the user’s basic information, user password, shopping record and other high confidentiality information. This division is mainly to allow users to independently choose which kind of information to authorize third-party clients to access, so the following fields are required:

  • scope_name: Scope name

(3) Auth_access_token

Access Token information table. This table mainly shows which user granted which client which token of which access scope, and the end date of the token. Therefore, the following fields are required:

  • access_token:The Access Token field
  • user_id: Indicates the user who grants the permission
  • client_id: Indicates which client is granted
  • expires_in: Expiration timestamp, indicating when this Token expires
  • scope: Indicates what ranges can be accessed

(4) auth_refresh_token

Refresh Token information table. This table is mainly used to record Refresh tokens, and the corresponding auth_ACCESS_token table records need to be associated when designing the table structure. Therefore, the following fields are required:

  • refresh_token:The Refresh Token field
  • token_id: Its correspondingauth_access_tokenTable records
  • expires_in: Indicates the expiration timestamp

(5) Auth_client_user:

User authorization information table of an access client. This table is used to record the association between client, scope and user. Therefore, the following fields are required:

  • Auth_client_id: corresponding to authorizationauth_client_detailsTable records
  • User_id: indicates the entitlementuserTable records
  • Auth_scope_id: indicates the value of authorizationauth_scopeTable records

Understand the entire process of authorization, as well as the design of the table structure that needs to be used later, so we finally left the specific code implementation.

Two OAuth2.0 authorization server main interface code implementation

The Demo authorization server’s full available source code can be found at gitee.com/zifangsky/O…

(1) Client registration interface:

A third-party client needs to register with the server in advance. In this Demo I didn’t write a specific page, just a registration interface where client_id and client_secret are randomly generated strings.

The interface address: http://127.0.0.1:7000/oauth2.0/clientRegister

Parameters:

{"clientName":"Test client"."redirectUri":"http://localhost:7080/login"."description":"This is a test client service"}
Copy the code

(2) Authorization Page:

If the user has not authorized the client before, the Authorization page will be opened when the user requests the Authorization Code for the first time, and then the user manually selects whether to authorize:

The implementation code is simple: after the user selects “Authorize,” insert a record into the table auth_client_user. Here is no more to say, you can refer to the sample source code.

(3) Obtain Authorization Code:

Generate a string — Authorization Code according to the client_id and scope of the request, and save the Authorization scope and user information of the request into Redis. It is a new session, so you need to save the user information in advance.

The interface address: http://127.0.0.1:7000/oauth2.0/authorize? Client_id = 7 ugj6xwmtdpyyp8m8njg3hqx & scope = basic&response _type = code&state = AB1357 & redirect_uri = http://192.168.197.130:7080/ login

/** * Get Authorization Code *@author zifangsky
 * @date2018/8/6 summer *@since 1.0.0
 * @param request HttpServletRequest
 * @return org.springframework.web.servlet.ModelAndView
 */
@RequestMapping("/authorize")
public ModelAndView authorize(HttpServletRequest request){
	HttpSession session = request.getSession();
	User user = (User) session.getAttribute(Constants.SESSION_USER);

	// Client ID
	String clientIdStr = request.getParameter("client_id");
	// Scope of permission
	String scopeStr = request.getParameter("scope");
	/ / callback URL
	String redirectUri = request.getParameter("redirect_uri");
	//status, used to prevent CSRF attacks (optional)
	String status = request.getParameter("status");

	// Generate the Authorization Code
	String authorizationCode = authorizationService.createAuthorizationCode(clientIdStr, scopeStr, user);

	String params = "? code=" + authorizationCode;
	if(StringUtils.isNoneBlank(status)){
		params = params + "&status=" + status;
	}

	return new ModelAndView("redirect:" + redirectUri + params);
}
Copy the code

Call the cn/zifangsky/service/impl AuthorizationServiceImpl. The inside of the Java classes generated logic:

@Override
public String createAuthorizationCode(String clientIdStr, String scopeStr, User user) {
	//1. Assemble string to be encrypted (clientId + scope + current millisecond timestamp)
	String str = clientIdStr + scopeStr + String.valueOf(DateUtils.currentTimeMillis());

	/ / 2. SHA1 encryption
	String encryptedStr = EncryptUtils.sha1Hex(str);

	//3.1 Save the authorization scope of this request
	redisService.setWithExpire(encryptedStr + ":scope", scopeStr, (ExpireEnum.AUTHORIZATION_CODE.getTime()), ExpireEnum.AUTHORIZATION_CODE.getTimeUnit());
	//3.2 Save the information about the user to which the request belongs
	redisService.setWithExpire(encryptedStr + ":user", user, (ExpireEnum.AUTHORIZATION_CODE.getTime()), ExpireEnum.AUTHORIZATION_CODE.getTimeUnit());

	//4. Return Authorization Code
	return encryptedStr;
}
Copy the code

(4) Obtain Access Token through Authorization Code:

After the third-party client receives the Authorization Code, it can call the Token generating interface in the background to generate Access tokens and Refresh tokens:

The interface address: http://127.0.0.1:7000/oauth2.0/token? grant_type=authorization_code&code=82ce2bf34f5028d7e8a517ef381f5c87f0139b26&client_id=7Ugj6XWmTDpyYp8M8njG3hqx&client_se Cret = tur2rlFfywR9OOP3fB5ZbsLTnNuNabI3 & redirect_uri = http://192.168.197.130:7080/login

Returns the following:

{
	"access_token": "1.6659 c9d38f5943f97db334874e5229284cdd1523. 2592000.1537600367"."refresh_token": "2. B19923a01cf35ccab48ddbd687750408bd1cb763. 31536000.1566544316"."expires_in": 2592000."scope": "basic"
}
Copy the code
/** * Obtain Access Token through Authorization Code *@author zifangsky
 * @date2018/8/18 * 3@since 1.0.0
 * @param request HttpServletRequest
 * @return java.util.Map<java.lang.String,java.lang.Object>
 */
@RequestMapping(value = "/token", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public Map<String,Object> token(HttpServletRequest request){
	Map<String,Object> result = new HashMap<>(8);

	// Authorization mode
	String grantType = request.getParameter("grant_type");
	// The previously obtained Authorization Code
	String code = request.getParameter("code");
	// Client ID
	String clientIdStr = request.getParameter("client_id");
	// Access client key
	String clientSecret = request.getParameter("client_secret");
	/ / callback URL
	String redirectUri = request.getParameter("redirect_uri");

	// Verify authorization mode
	if(! GrantTypeEnum.AUTHORIZATION_CODE.getType().equals(grantType)){this.generateErrorResponse(result, ErrorCodeEnum.UNSUPPORTED_GRANT_TYPE);
		return result;
	}

	try {
		AuthClientDetails savedClientDetails = authorizationService.selectClientDetailsByClientId(clientIdStr);
		// Verify that the requested client key matches the saved key
		if(! (savedClientDetails ! =null && savedClientDetails.getClientSecret().equals(clientSecret))){
			this.generateErrorResponse(result, ErrorCodeEnum.INVALID_CLIENT);
			return result;
		}

		// Verify the callback URL
		if(! savedClientDetails.getRedirectUri().equals(redirectUri)){this.generateErrorResponse(result, ErrorCodeEnum.REDIRECT_URI_MISMATCH);
			return result;
		}

		// Get the range of user permissions allowed from Redis
		String scope = redisService.get(code + ":scope");
		// Get the corresponding user information from Redis
		User user = redisService.get(code + ":user");

		// If the user information can be obtained through the Authorization Code, the Authorization Code is valid
		if(StringUtils.isNoneBlank(scope) && user ! =null) {// Expiration time
			Long expiresIn = DateUtils.dayToSecond(ExpireEnum.ACCESS_TOKEN.getTime());

			// Generate Access Token
			String accessTokenStr = authorizationService.createAccessToken(user, savedClientDetails, grantType, scope, expiresIn);
			// Query the Access Token that has been inserted into the database
			AuthAccessToken authAccessToken = authorizationService.selectByAccessToken(accessTokenStr);
			// Generate Refresh Token
			String refreshTokenStr = authorizationService.createRefreshToken(user, authAccessToken);

			// Return data
			result.put("access_token", authAccessToken.getAccessToken());
			result.put("refresh_token", refreshTokenStr);
			result.put("expires_in", expiresIn);
			result.put("scope", scope);
			return result;
		}else{
			this.generateErrorResponse(result, ErrorCodeEnum.INVALID_GRANT);
			returnresult; }}catch (Exception e){
		this.generateErrorResponse(result, ErrorCodeEnum.UNKNOWN_ERROR);
		returnresult; }}Copy the code

Generation logic in cn/same zifangsky/service/impl AuthorizationServiceImpl. Java inside the class, specific as follows:

@Override
public String createAccessToken(User user, AuthClientDetails savedClientDetails, String grantType, String scope, Long expiresIn) {
	Date current = new Date();
	// An expired timestamp
	Long expiresAt = DateUtils.nextDaysSecond(ExpireEnum.ACCESS_TOKEN.getTime(), null);

	//1. Assemble the string to be encrypted (username + clientId + current millisecond timestamp)
	String str = user.getUsername() + savedClientDetails.getClientId() + String.valueOf(DateUtils.currentTimeMillis());

	/ / 2. SHA1 encryption
	String accessTokenStr = "1." + EncryptUtils.sha1Hex(str) + "." + expiresIn + "." + expiresAt;

	3. Save the Access Token
	AuthAccessToken savedAccessToken = authAccessTokenMapper.selectByUserIdClientIdScope(user.getId()
			, savedClientDetails.getId(), scope);
	// If there is a userId + clientId + scope matching record, update the original record, otherwise insert a new record into the database
	if(savedAccessToken ! =null){
		savedAccessToken.setAccessToken(accessTokenStr);
		savedAccessToken.setExpiresIn(expiresAt);
		savedAccessToken.setUpdateUser(user.getId());
		savedAccessToken.setUpdateTime(current);
		authAccessTokenMapper.updateByPrimaryKeySelective(savedAccessToken);
	}else{
		savedAccessToken = new AuthAccessToken();
		savedAccessToken.setAccessToken(accessTokenStr);
		savedAccessToken.setUserId(user.getId());
		savedAccessToken.setUserName(user.getUsername());
		savedAccessToken.setClientId(savedClientDetails.getId());
		savedAccessToken.setExpiresIn(expiresAt);
		savedAccessToken.setScope(scope);
		savedAccessToken.setGrantType(grantType);
		savedAccessToken.setCreateUser(user.getId());
		savedAccessToken.setUpdateUser(user.getId());
		savedAccessToken.setCreateTime(current);
		savedAccessToken.setUpdateTime(current);
		authAccessTokenMapper.insertSelective(savedAccessToken);
	}

	//4. Return Access Token
	return accessTokenStr;
}

@Override
public String createRefreshToken(User user, AuthAccessToken authAccessToken) {
	Date current = new Date();
	// Expiration time
	Long expiresIn = DateUtils.dayToSecond(ExpireEnum.REFRESH_TOKEN.getTime());
	// An expired timestamp
	Long expiresAt = DateUtils.nextDaysSecond(ExpireEnum.REFRESH_TOKEN.getTime(), null);

	//1. Assemble the string to be encrypted (username + accessToken + current timestamp to milliseconds)
	String str = user.getUsername() + authAccessToken.getAccessToken() + String.valueOf(DateUtils.currentTimeMillis());

	/ / 2. SHA1 encryption
	String refreshTokenStr = "2." + EncryptUtils.sha1Hex(str) + "." + expiresIn + "." + expiresAt;

	3. Save the Refresh Token
	AuthRefreshToken savedRefreshToken = authRefreshTokenMapper.selectByTokenId(authAccessToken.getId());
	// If there is a tokenId matching record, update the original record, otherwise insert a new record into the database
	if(savedRefreshToken ! =null){
		savedRefreshToken.setRefreshToken(refreshTokenStr);
		savedRefreshToken.setExpiresIn(expiresAt);
		savedRefreshToken.setUpdateUser(user.getId());
		savedRefreshToken.setUpdateTime(current);
		authRefreshTokenMapper.updateByPrimaryKeySelective(savedRefreshToken);
	}else{
		savedRefreshToken = new AuthRefreshToken();
		savedRefreshToken.setTokenId(authAccessToken.getId());
		savedRefreshToken.setRefreshToken(refreshTokenStr);
		savedRefreshToken.setExpiresIn(expiresAt);
		savedRefreshToken.setCreateUser(user.getId());
		savedRefreshToken.setUpdateUser(user.getId());
		savedRefreshToken.setCreateTime(current);
		savedRefreshToken.setUpdateTime(current);
		authRefreshTokenMapper.insertSelective(savedRefreshToken);
	}

	//4. Return Refresh Token
	return refreshTokenStr;
}
Copy the code

(5) Refresh Access Token by Refresh Token:

When the Access Token of a third-party client becomes invalid, this interface can be called to generate a new Access Token:

The interface address: http://127.0.0.1:7000/oauth2.0/refreshToken? Refresh_token = 2.5 c58637a2d51e4470d3e1189978e94da8402785e. 31536000.1566283826

Returns the following:

{
	"access_token": "1. Adebb0a4522d5dae9eaf94a5af4fec070c4f3dce. 2592000.1537508734"."refresh_token": "2.5 c58637a2d51e4470d3e1189978e94da8402785e. 31536000.1566283826"."expires_in": 2592000."scope": "basic"
}
Copy the code
/** * Refresh Access Token * by Refresh Token *@author zifangsky
 * @date 2018/8/22 11:11
 * @since 1.0.0
 * @param request HttpServletRequest
 * @return java.util.Map<java.lang.String,java.lang.Object>
 */
@RequestMapping(value = "/refreshToken", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public Map<String,Object> refreshToken(HttpServletRequest request){
	Map<String,Object> result = new HashMap<>(8);

	// Obtain Refresh Token
	String refreshTokenStr = request.getParameter("refresh_token");

	try {
		AuthRefreshToken authRefreshToken = authorizationService.selectByRefreshToken(refreshTokenStr);

		if(authRefreshToken ! =null) {
			Long savedExpiresAt = authRefreshToken.getExpiresIn();
			// Expiration date
			LocalDateTime expiresDateTime = DateUtils.ofEpochSecond(savedExpiresAt, null);
			// The current date
			LocalDateTime nowDateTime = DateUtils.now();

			// If the Refresh Token is invalid, it needs to be generated again
			if (expiresDateTime.isBefore(nowDateTime)) {
				this.generateErrorResponse(result, ErrorCodeEnum.EXPIRED_TOKEN);
				return result;
			} else {
				// Get the stored Access Token
				AuthAccessToken authAccessToken = authorizationService.selectByAccessId(authRefreshToken.getTokenId());
				// Obtain the corresponding client information
				AuthClientDetails savedClientDetails = authorizationService.selectClientDetailsById(authAccessToken.getClientId());
				// Obtain the corresponding user information
				User user = userService.selectByUserId(authAccessToken.getUserId());

				// New expiration time
				Long expiresIn = DateUtils.dayToSecond(ExpireEnum.ACCESS_TOKEN.getTime());
				// Generate a new Access Token
				String newAccessTokenStr = authorizationService.createAccessToken(user, savedClientDetails
						, authAccessToken.getGrantType(), authAccessToken.getScope(), expiresIn);

				// Return data
				result.put("access_token", newAccessTokenStr);
				result.put("refresh_token", refreshTokenStr);
				result.put("expires_in", expiresIn);
				result.put("scope", authAccessToken.getScope());
				returnresult; }}else {
			this.generateErrorResponse(result, ErrorCodeEnum.INVALID_GRANT);
			returnresult; }}catch (Exception e){
		this.generateErrorResponse(result, ErrorCodeEnum.UNKNOWN_ERROR);
		returnresult; }}Copy the code

(6) Obtain user information through Access Token:

When obtaining user information through Access Token, it is necessary to verify whether the requested Token is valid in the interceptor first. The relevant code logic is as follows:

The interface address: http://127.0.0.1:7000/api/users/getInfo? Access_token = 1. Adebb0a4522d5dae9eaf94a5af4fec070c4f3dce. 2592000.1537508734

Returns the following:

{
	"mobile": "110"."id": 1."email": "[email protected]"."username": "admin"
}
Copy the code
package cn.zifangsky.interceptor;

import cn.zifangsky.enums.ErrorCodeEnum;
import cn.zifangsky.model.AuthAccessToken;
import cn.zifangsky.service.AuthorizationService;
import cn.zifangsky.utils.DateUtils;
import cn.zifangsky.utils.JsonUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

/** * verifies whether the Access Token is empty and invalid **@author zifangsky
 * @date 2018/8/22
 * @since1.0.0 * /
public class AuthAccessTokenInterceptor extends HandlerInterceptorAdapter{
    @Resource(name = "authorizationServiceImpl")
    private AuthorizationService authorizationService;

    /** * Check whether the Access Token is invalid */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String accessToken = request.getParameter("access_token");

        if(StringUtils.isNoneBlank(accessToken)){
            // Query the Access Token in the database
            AuthAccessToken authAccessToken = authorizationService.selectByAccessToken(accessToken);

            if(authAccessToken ! =null){
                Long savedExpiresAt = authAccessToken.getExpiresIn();
                // Expiration date
                LocalDateTime expiresDateTime = DateUtils.ofEpochSecond(savedExpiresAt, null);
                // The current date
                LocalDateTime nowDateTime = DateUtils.now();

                // If the Access Token is invalid, an error message is displayed
                return expiresDateTime.isAfter(nowDateTime) || this.generateErrorResponse(response, ErrorCodeEnum.EXPIRED_TOKEN);
            }else{
                return this.generateErrorResponse(response, ErrorCodeEnum.INVALID_GRANT); }}else{
            return this.generateErrorResponse(response, ErrorCodeEnum.INVALID_REQUEST); }}/** * Assembles the error request's return */
    private boolean generateErrorResponse(HttpServletResponse response,ErrorCodeEnum errorCodeEnum) throws Exception {
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-type"."application/json; charset=UTF-8");
        Map<String,String> result = new HashMap<>(2);
        result.put("error", errorCodeEnum.getError());
        result.put("error_description",errorCodeEnum.getErrorDescription());

        response.getWriter().write(JsonUtils.toJson(result));
        return false; }}Copy the code

Then return the corresponding user information based on the scope of Access granted by the Access Token:

package cn.zifangsky.controller;

import cn.zifangsky.enums.ErrorCodeEnum;
import cn.zifangsky.model.AuthAccessToken;
import cn.zifangsky.model.User;
import cn.zifangsky.service.AuthorizationService;
import cn.zifangsky.service.UserService;
import cn.zifangsky.utils.JsonUtils;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/** * API service ** that is accessed by Access Token@author zifangsky
 * @date 2018/8/22
 * @since1.0.0 * /
@RestController
@RequestMapping("/api")
public class ApiController {

    @Resource(name = "authorizationServiceImpl")
    private AuthorizationService authorizationService;

    @Resource(name = "userServiceImpl")
    private UserService userService;

    @RequestMapping(value = "/users/getInfo", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public String getUserInfo(HttpServletRequest request){
        String accessToken = request.getParameter("access_token");
        // Query the Access Token in the database
        AuthAccessToken authAccessToken = authorizationService.selectByAccessToken(accessToken);

        if(authAccessToken ! =null){
            User user = userService.selectUserInfoByScope(authAccessToken.getUserId(), authAccessToken.getScope());
            return JsonUtils.toJson(user);
        }else{
            return this.generateErrorResponse(ErrorCodeEnum.INVALID_GRANT); }}/** * Assembles the error request's return */
    private String generateErrorResponse(ErrorCodeEnum errorCodeEnum) {
        Map<String,Object> result = new HashMap<>(2);
        result.put("error", errorCodeEnum.getError());
        result.put("error_description",errorCodeEnum.getErrorDescription());

        returnJsonUtils.toJson(result); }}Copy the code

The code logic is as follows:

@Override
public User selectUserInfoByScope(Integer userId, String scope) {
	User user = userMapper.selectByPrimaryKey(userId);

	// For basic permissions, some information is not returned
	if(ScopeEnum.BASIC.getCode().equalsIgnoreCase(scope)){
		user.setPassword(null);
		user.setCreateTime(null);
		user.setUpdateTime(null);
		user.setStatus(null);
	}

	return user;
}
Copy the code

Access to OAuth2.0 authorized third-party client code logic

The full source code for the Demo third-party client is available at: gitee.com/zifangsky/O…

In fact, for access to the third-party client, the code logic of the background is similar to the code logic of baidu OAuth service in my last article. The following is an example:

package cn.zifangsky.controller;

import cn.zifangsky.common.Constants;
import cn.zifangsky.model.AuthorizationResponse;
import cn.zifangsky.model.User;
import cn.zifangsky.utils.EncryptUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.text.MessageFormat;

/** * login *@author zifangsky
 * @date 2018/7/9
 * @since1.0.0 * /
@Controller
public class LoginController {

    @Autowired
    private RestTemplate restTemplate;

    @Value("${own.oauth2.client-id}")
    private String clientId;

    @Value("${own.oauth2.scope}")
    private String scope;

    @Value("${own.oauth2.client-secret}")
    private String clientSecret;

    @Value("${own.oauth2.user-authorization-uri}")
    private String authorizationUri;

    @Value("${own.oauth2.access-token-uri}")
    private String accessTokenUri;

    @Value("${own.oauth2.resource.userInfoUri}")
    private String userInfoUri;

    /** * Login authentication (the actual login calls the authentication server) *@author zifangsky
     * @date2018/7/25 * calm@since 1.0.0
     * @param request HttpServletRequest
     * @return org.springframework.web.servlet.ModelAndView
     */
    @RequestMapping("/login")
    public ModelAndView login(HttpServletRequest request){
        // Callback URL after successful login to the current system
        String redirectUrl = request.getParameter("redirectUrl");
        // The Authorization Code returned after the system successfully requests the authentication server
        String code = request.getParameter("code");

        // The last redirected URL
        String resultUrl = "redirect:";
        HttpSession session = request.getSession();
        // The current request path
        String currentUrl = request.getRequestURL().toString();

        // If code is empty, the current request is not a callback request from the authentication server, and the URL is redirected to the authentication server for login
        if(StringUtils.isBlank(code)){
            // If there is a callback URL, add it to session
            if(StringUtils.isNoneBlank(redirectUrl)){
                session.setAttribute(Constants.SESSION_LOGIN_REDIRECT_URL,redirectUrl);
            }

            // Generate a random status code to prevent CSRF attacks
            String status = EncryptUtils.getRandomStr1(6);
            session.setAttribute(Constants.SESSION_AUTH_CODE_STATUS, status);
            // Assemble the request Authorization Code address
            resultUrl += MessageFormat.format(authorizationUri,clientId,status,currentUrl);
        }else{
            //2. Obtain the Access Token using Authorization Code
            AuthorizationResponse response = restTemplate.getForObject(accessTokenUri, AuthorizationResponse.class
                    ,clientId,clientSecret,code,currentUrl);

            // Return if normal
            if(StringUtils.isNoneBlank(response.getAccess_token())){
                System.out.println(response);

                2.1 Save the Access Token to session
                session.setAttribute(Constants.SESSION_ACCESS_TOKEN,response.getAccess_token());

                //2.2 Querying basic user information again and saving the user ID to session
                User user = restTemplate.getForObject(userInfoUri, User.class
                        ,response.getAccess_token());

                if(StringUtils.isNoneBlank(user.getUsername())){ session.setAttribute(Constants.SESSION_USER,user); }}//3. Get the callback URL from session and return it
            redirectUrl = (String) session.getAttribute(Constants.SESSION_LOGIN_REDIRECT_URL);
            session.removeAttribute("redirectUrl");
            if(StringUtils.isNoneBlank(redirectUrl)){
                resultUrl += redirectUrl;
            }else{
                resultUrl += "/user/userIndex"; }}return newModelAndView(resultUrl); }}Copy the code

Note that I have added a status code to protect against CSRF attacks during OAuth2.0 authorized logins. Therefore, a new interceptor needs to be added to validate the status Code when the Authorization Code callback is requested. The relevant codes are as follows:

package cn.zifangsky.interceptor;

import cn.zifangsky.common.Constants;
import cn.zifangsky.enums.ErrorCodeEnum;
import cn.zifangsky.utils.JsonUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;

/** * is used to verify the OAuth2.0 login status code **@author zifangsky
 * @date 2018/8/23
 * @since1.0.0 * /
public class AuthInterceptor extends HandlerInterceptorAdapter{

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();

        // The Authorization Code returned after the system successfully requests the authentication server
        String code = request.getParameter("code");
        // The status code returned as the original
        String resultStatus = request.getParameter("status");

        If code is not empty, the current request is a callback request returned from the authentication server
        if(StringUtils.isNoneBlank(code)){
            // Get the saved status code from session
            String savedStatus = (String) session.getAttribute(Constants.SESSION_AUTH_CODE_STATUS);
            //1. Check whether the status code matches
            if(savedStatus ! =null&& resultStatus ! =null && savedStatus.equals(resultStatus)){
                return true;
            }else{
                response.setCharacterEncoding("UTF-8");
                response.setHeader("Content-type"."application/json; charset=UTF-8");
                Map<String,String> result = new HashMap<>(2);
                result.put("error", ErrorCodeEnum.INVALID_STATUS.getError());
                result.put("error_description",ErrorCodeEnum.INVALID_STATUS.getErrorDescription());

                response.getWriter().write(JsonUtils.toJson(result));
                return false; }}else{
            return true; }}}Copy the code

In addition, some of the actual configuration used in the above code is the address of our OAuth2.0 server interface:

own.oauth2.client-id=7Ugj6XWmTDpyYp8M8njG3hqx own.oauth2.scope=super own.oauth2.client-secret=tur2rlFfywR9OOP3fB5ZbsLTnNuNabI3 Own. Oauth2. User authorization - uri = http://127.0.0.1:7000/oauth2.0/authorize? client_id={0}&response_type=code&scope=super&&status={1}&redirect_uri={2} Own. Oauth2. The access token - uri = http://127.0.0.1:7000/oauth2.0/token? client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}&redirect_uri={4} Own. Oauth2. Resource. UserInfoUri = http://127.0.0.1:7000/api/users/getInfo? access_token={1}Copy the code

Well, the end of this article, interested students can refer to the sample source code to try their own. Also, stay tuned for the differences and connections between OAuth2.0 and single sign-on (SSO) in my next article.