An OAuth2.0 authorization versus single sign-on (SSO)

In the first two articles I introduces the basic concept of OAuth2.0 agreement (www.zifangsky.cn/1309.html) and OAuth2.0 authorization from design to implementation of a service (www.zifangsky.cn/1313.html). In this article I will introduce you to the difference between OAuth2.0 authorization and single sign-on. Although the two concepts seem similar, they are actually very different, and many people tend to confuse them.

What is single sign-on?

The English name for Single sign-on is Single Sign On, so it is commonly referred to as SSO for short. Its purpose is that no matter how complex the application cluster, as long as the user within the scope of authority, then can be done, users only need to log in once to access all the application subsystems within the scope of authority. Users only need to log in once to access multiple application subsystems and log out only once. For example, after you successfully log in to baidu home page, you will also be logged in to Baidu Baike, Baidu Know, Baidu Tieba and other websites. This is a real case of single sign-on.

OAuth2.0 difference between authorization and single sign-on

Based on the concept of OAuth2.0 authorization and single sign-on, we can see at least the following differences:

  1. From a trust point of view. OAuth2.0 authorization server and third-party clients do not belong to a group of trusted applications (usually not the same company to provide services). Users of third-party clients do not belong to official users of OAuth2.0 authorization server. The sso server and the client belong to a trusted application group (usually the service provided by the same company), and the users of each subsystem belong to the official users of the SSO server.
  2. From a resource point of view. OAuth2.0 authorization mainly allows users to decide for themselves — whether “I” in OAuth2.0 service provider’s personal resources allow third-party applications to access; The resources of single sign-on are all on the client side, and the server side of single sign-on is mainly used for login and management of user permission information in each subsystem.
  3. From a process perspective. When OAuth2.0 authorization, third-party clients need to take a “negotiated” password to obtain Access Token; Single sign-on does not.

Design of single sign-on server

For a single sign-on subsystem, the following two steps are required:

  1. The client requests the sso server to obtain the Access Token.
  2. Because the client cannot determine whether the Access Token given to it is returned by the SSO server or forged by the user, it needs to request the SSO server again to verify whether the Access Token is valid. If yes, the client returns basic information about the user and the role and permission of the user on the client.

Therefore, the design of single sign-on server mainly revolves around these two interfaces, and its main flow is as follows:

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) SSO_client_details

Access subsystem details for single sign-on. Similar to Baidu’s application subsystems such as Baidu Baike, Baidu Know and Baidu Tieba, each subsystem that wants to access single sign-on needs to be “put on record” at the server in advance. On the one hand, the Access Token generated by a user’s successful login on the server is redirected to an illegal website, which leads to the theft of the user’s Access Token. On the other hand, it records the logout URL of the subsystem to facilitate the development of single sign-off function. Therefore, the following fields are required:

  • client_name: Specifies the name of the subsystem
  • redirect_url: getAccess TokenThe successful callback URL
  • logout_url: User logout URL in subsystem (User login status can be classified as follows: Global login: login status on the SSO server; Local Login – Login status in the subsystemTo log out, you need to log out the login status of the user on the SSO server and the application subsystem.

(2) SSO_access_token

Single sign-on Access Token information table. This table mainly shows which user is logged on to which subsystem and the end date of the generated token. Therefore, the following fields are required:

  • access_token:The Access Token field
  • user_id: Indicates the login user
  • client_id: Indicates the subsystem in which you log in
  • expires_in: Expiration timestamp, indicating when this Token expires

(3) sSO_refresh_token

Refresh Token information table for sso. This table is mainly used to record Refresh tokens, and records of its corresponding SSO_ACCESS_token table need to be associated during the design of the table structure. Therefore, the following fields are required:

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

Three single sign-on server main interface code implementation

The Demo single sign-on server’s full available source code can be found at: gitee.com/zifangsky/O…

(1) Access Token:

The client requests the sso server to obtain the Access Token and is redirected to the callback URL in the request.

The interface address: http://127.0.0.1:7000/sso/token? Redirect_uri = http://192.168.197.130:6080/login

/** * Obtain Token *@author zifangsky
 * @date2018/8/30 ticket *@since 1.0.0
 * @param request HttpServletRequest
 * @return org.springframework.web.servlet.ModelAndView
 */
@RequestMapping("/token")
public ModelAndView authorize(HttpServletRequest request){
	HttpSession session = request.getSession();
	User user = (User) session.getAttribute(Constants.SESSION_USER);

	// Expiration time
	Long expiresIn = DateUtils.dayToSecond(ExpireEnum.ACCESS_TOKEN.getTime());
	/ / callback URL
	String redirectUri = request.getParameter("redirect_uri");
	// Query the access client
	SsoClientDetails ssoClientDetails = ssoService.selectByRedirectUrl(redirectUri);

	// Obtain the user IP address
	String requestIp = SpringContextUtils.getRequestIp(request);

	// Generate Access Token
	String accessTokenStr = ssoService.createAccessToken(user, expiresIn, requestIp, ssoClientDetails);
	// Query the Access Token that has been inserted into the database
	SsoAccessToken ssoAccessToken = ssoService.selectByAccessToken(accessTokenStr);
	// Generate Refresh Token
	String refreshTokenStr = ssoService.createRefreshToken(user, ssoAccessToken);
	logger.info(MessageFormat.format(Access Token: [{2}],Refresh Token: [{3}]"
			,user.getUsername(),ssoClientDetails.getClientName(),accessTokenStr,refreshTokenStr));

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

Accordingly, call the cn/zifangsky/service/impl SsoServiceImpl. The inside of the Java classes generated logic:

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

	//1. Assemble the string to be encrypted (username + channel CODE + current millisecond timestamp)
	String str = user.getUsername() + ssoClientDetails.getClientName() + String.valueOf(DateUtils.currentTimeMillis());

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

	3. Save the Access Token
   SsoAccessToken savedAccessToken = ssoAccessTokenMapper.selectByUserIdAndClientId(user.getId(), ssoClientDetails.getId());
	// If a matching record exists, the original record is updated, otherwise a new record is inserted into the database
	if(savedAccessToken ! =null){
		savedAccessToken.setAccessToken(accessTokenStr);
		savedAccessToken.setExpiresIn(expiresAt);
		savedAccessToken.setUpdateUser(user.getId());
		savedAccessToken.setUpdateTime(current);
		ssoAccessTokenMapper.updateByPrimaryKeySelective(savedAccessToken);
	}else{
		savedAccessToken = new SsoAccessToken();
		savedAccessToken.setAccessToken(accessTokenStr);
		savedAccessToken.setUserId(user.getId());
		savedAccessToken.setUserName(user.getUsername());
		savedAccessToken.setIp(requestIP);
		savedAccessToken.setClientId(ssoClientDetails.getId());
		savedAccessToken.setChannel(ssoClientDetails.getClientName());
		savedAccessToken.setExpiresIn(expiresAt);
		savedAccessToken.setCreateUser(user.getId());
		savedAccessToken.setUpdateUser(user.getId());
		savedAccessToken.setCreateTime(current);
		savedAccessToken.setUpdateTime(current);
		ssoAccessTokenMapper.insertSelective(savedAccessToken);
	}

	//4. Return Access Token
	return accessTokenStr;
}

@Override
public String createRefreshToken(User user, SsoAccessToken ssoAccessToken) {
	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() + ssoAccessToken.getAccessToken() + String.valueOf(DateUtils.currentTimeMillis());

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

	3. Save the Refresh Token
	SsoRefreshToken savedRefreshToken = ssoRefreshTokenMapper.selectByTokenId(ssoAccessToken.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);
		ssoRefreshTokenMapper.updateByPrimaryKeySelective(savedRefreshToken);
	}else{
		savedRefreshToken = new SsoRefreshToken();
		savedRefreshToken.setTokenId(ssoAccessToken.getId());
		savedRefreshToken.setRefreshToken(refreshTokenStr);
		savedRefreshToken.setExpiresIn(expiresAt);
		savedRefreshToken.setCreateUser(user.getId());
		savedRefreshToken.setUpdateUser(user.getId());
		savedRefreshToken.setCreateTime(current);
		savedRefreshToken.setUpdateTime(current);
		ssoRefreshTokenMapper.insertSelective(savedRefreshToken);
	}

	// Refresh Tokens
	return refreshTokenStr;
}
Copy the code

(2) Verify Access Token and return user information:

After obtaining the Access Token, the client invokes the sso server interface again to verify the Access Token and return user information.

The interface address: http://127.0.0.1:7000/sso/verify? Access_token = 11. Ad51132688b5be3f476592356c78aef71d235f07. 2592000.1539143183

Returns the following:

{
	"access_token": "2592000.1539143183 11. Ad51132688b5be3f476592356c78aef71d235f07."."refresh_token": "12. C10cb9001bf0e2c7f808580318715fc089673279. 31536000.1568087183"."expires_in": 2592000."user_info": {
		"id": 2."username": "zifangsky"."password": "$5$toOBSeX2$hSnSDyhJmVVRpbmKuIY4SxDgyeGRGacQaBYGrtEBnZA"."mobile": "110"."email": "[email protected]"."createTime": "The T16 2017-12-31:00:00. 000 + 0000"."updateTime": "The T16 2017-12-31:00:00. 000 + 0000"."status": 1."roles": [{
				"id": 2."roleName": "user"."description": "Ordinary user"."funcs": null}}}]Copy the code

First check whether the Access Token is valid in an interceptor:

package cn.zifangsky.interceptor;

import cn.zifangsky.enums.ErrorCodeEnum;
import cn.zifangsky.model.SsoAccessToken;
import cn.zifangsky.service.SsoService;
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/30
 * @since1.0.0 * /
public class SsoAccessTokenInterceptor extends HandlerInterceptorAdapter{
    @Resource(name = "ssoServiceImpl")
    private SsoService ssoService;

    /** * 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
            SsoAccessToken ssoAccessToken = ssoService.selectByAccessToken(accessToken);

            if(ssoAccessToken ! =null){
                Long savedExpiresAt = ssoAccessToken.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 user information:

/** * Validates Access Token and returns user information *@author zifangsky
 * @date 2018/8/30 16:07
 * @since 1.0.0
 * @param request HttpServletRequest
 * @return java.util.Map<java.lang.String,java.lang.Object>
 */
@RequestMapping(value = "/verify", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public Map<String,Object> verify(HttpServletRequest request) {
	Map<String, Object> result = new HashMap<>(8);

	// Obtain the Access Token
	String accessToken = request.getParameter("access_token");

	try {
		// Expiration time
		Long expiresIn = DateUtils.dayToSecond(ExpireEnum.ACCESS_TOKEN.getTime());
		// Query the Access Token
		SsoAccessToken ssoAccessToken = ssoService.selectByAccessToken(accessToken);
		// Query Refresh Token
		SsoRefreshToken ssoRefreshToken = ssoService.selectByTokenId(ssoAccessToken.getId());
		// Query user information
		UserBo userBo = userService.selectUserBoByUserId(ssoAccessToken.getUserId());

		// The assembly returns information
		result.put("access_token", ssoAccessToken.getAccessToken());
		result.put("refresh_token", ssoRefreshToken.getRefreshToken());
		result.put("expires_in", expiresIn);
		result.put("user_info", userBo);
		return result;
	}catch (Exception e){
		logger.error(e.getMessage());
		this.generateErrorResponse(result, ErrorCodeEnum.UNKNOWN_ERROR);
		returnresult; }}Copy the code

(3) Refresh the Access Token interface through Refresh Token:

If the client’s Access Token expires, the Access Token can be refreshed through this interface.

The interface address: http://127.0.0.1:7000/sso/refreshToken? Refresh_token = 12. C10cb9001bf0e2c7f808580318715fc089673279. 31536000.1568087183

Returns the following:

{
	"access_token": "11.40 f0270697c37db4570e41e0f6f335bf6c2f8902. 2592000.1539164947"."refresh_token": "12. C10cb9001bf0e2c7f808580318715fc089673279. 31536000.1568087183"."expires_in": 2592000."user_info": {
		"id": 2."username": "zifangsky"."password": "$5$toOBSeX2$hSnSDyhJmVVRpbmKuIY4SxDgyeGRGacQaBYGrtEBnZA"."mobile": "110"."email": "[email protected]"."createTime": "The T16 2017-12-31:00:00. 000 + 0000"."updateTime": "The T16 2017-12-31:00:00. 000 + 0000"."status": 1."roles": [{
				"id": 2."roleName": "user"."description": "Ordinary user"."funcs": null}}}]Copy the code
/** * Refresh Access Token * by Refresh Token *@author zifangsky
 * @date 2018/8/30 16:07
 * @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");
	// Obtain the user IP address
	String requestIp = SpringContextUtils.getRequestIp(request);

	try {
		SsoRefreshToken ssoRefreshToken = ssoService.selectByRefreshToken(refreshTokenStr);

		if(ssoRefreshToken ! =null) {
			Long savedExpiresAt = ssoRefreshToken.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
				SsoAccessToken ssoAccessToken = ssoService.selectByAccessId(ssoRefreshToken.getTokenId());
				// Query the access client
				SsoClientDetails ssoClientDetails = ssoService.selectByPrimaryKey(ssoAccessToken.getClientId());
				// Obtain the corresponding user information
				User user = userService.selectByUserId(ssoAccessToken.getUserId());

				// New expiration time
				Long expiresIn = DateUtils.dayToSecond(ExpireEnum.ACCESS_TOKEN.getTime());
				// Generate a new Access Token
				String newAccessTokenStr = ssoService.createAccessToken(user, expiresIn, requestIp, ssoClientDetails);
				// Query user information
				UserBo userBo = userService.selectUserBoByUserId(ssoAccessToken.getUserId());

				logger.info(MessageFormat.format(Token: username: [{0}],requestIp: [{1}],old Token: [{2}],new Token: [{3}]
						,user.getUsername(),requestIp,ssoAccessToken.getAccessToken(),newAccessTokenStr));

				// The assembly returns information
				result.put("access_token", newAccessTokenStr);
				result.put("refresh_token", ssoRefreshToken.getRefreshToken());
				result.put("expires_in", expiresIn);
				result.put("user_info", userBo);
				returnresult; }}else {
			this.generateErrorResponse(result, ErrorCodeEnum.INVALID_GRANT);
			returnresult; }}catch (Exception e){
		e.printStackTrace();
		this.generateErrorResponse(result, ErrorCodeEnum.UNKNOWN_ERROR);
		returnresult; }}/** * Assembles the error request's return */
private void generateErrorResponse(Map<String,Object> result, ErrorCodeEnum errorCodeEnum) {
	result.put("error", errorCodeEnum.getError());
	result.put("error_description",errorCodeEnum.getErrorDescription());
}
Copy the code

(4) Single sign-off interface:

In this Demo project, I do not provide sample code for single sign-off, but I can briefly describe the main process of single sign-off. If you need this function, you can use the code to achieve it:

  1. The user requests logout in the application subsystem.
  2. After the user logs out of the application subsystem, the application subsystem requests the logout interface of the single sign-on server.
  3. The logout interface of the SINGLE sign-on (SSO) server queries the logout interfaces of all application subsystems that the current user logs in to based on the user’s Token in the database and invokes the logout interface one by one.

Four access single sign-on subsystem key code

The full source code for the Demo’s single sign-on subsystem is available at: gitee.com/zifangsky/O…

In fact, for the subsystem that accesses single sign-on, the login module simply calls the interface provided by the single sign-on server.

Login verification filter:

package cn.zifangsky.interceptor;

import cn.zifangsky.common.Constants;
import cn.zifangsky.common.SpringContextUtils;
import cn.zifangsky.model.bo.UserBo;
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;

/** * define some pages that require login checks **@author zifangsky
 * @date 2018/7/26
 * @since1.0.0 * /
public class LoginInterceptor extends HandlerInterceptorAdapter{

    /** * Check whether you have logged in */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();

        // Get the user information stored in the session
        UserBo user = (UserBo) session.getAttribute(Constants.SESSION_USER);

        if(user ! =null) {return true;
        }else{
            // If the token does not exist, the login page is redirected
            response.sendRedirect(request.getContextPath() + "/login? redirectUrl=" + SpringContextUtils.getRequestUrl(request));

            return false; }}}Copy the code

Login-related code logic:

package cn.zifangsky.controller;

import cn.zifangsky.common.Constants;
import cn.zifangsky.model.SsoResponse;
import cn.zifangsky.model.User;
import cn.zifangsky.utils.CookieUtils;
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.HttpServletResponse;
import javax.servlet.http.HttpSession;

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

    @Autowired
    private RestTemplate restTemplate;

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

    @Value("${own.sso.verify-uri}")
    private String verifyUri;

    /** * Login authentication (the actual login calls the authentication server) *@author zifangsky
     * @date 2018/8/30 18:02
     * @since 1.0.0
     * @param request HttpServletRequest
     * @return org.springframework.web.servlet.ModelAndView
     */
    @RequestMapping("/login")
    public ModelAndView login(HttpServletRequest request, HttpServletResponse response){
        // Callback URL after successful login to the current system
        String redirectUrl = request.getParameter("redirectUrl");
        // Token returned by the current system after the authentication server successfully requests the Token
        String code = request.getParameter("code");

        // The last redirected URL
        String resultUrl = "redirect:";
        HttpSession session = request.getSession();

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

            // Assemble the request Token address
            resultUrl += accessTokenUri;
        }else{
            //2. Verify the Token and return the basic user information, role, and access permission
            SsoResponse verifyResponse = restTemplate.getForObject(verifyUri, SsoResponse.class
                    ,code);

            // Return if normal
            if(StringUtils.isNoneBlank(verifyResponse.getAccess_token())){
                2.1 Save user information to session
                session.setAttribute(Constants.SESSION_USER,verifyResponse.getUser_info());

                //2.2 Write the Access Token and Refresh Token to cookiesCookieUtils.addCookie(response,Constants.COOKIE_ACCESS_TOKEN, verifyResponse.getAccess_token(),request.getServerName());  CookieUtils.addCookie(response,Constants.COOKIE_REFRESH_TOKEN, verifyResponse.getRefresh_token(),request.getServerName()); }//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

Of course, some of the configuration used in the above code is the address of our single sign-on server interface:

Own. Sso. The access token - uri = http://10.0.5.22:7000/sso/token? Redirect_uri = http://192.168.197.130:6080/login own. Sso. Verify - uri = http://10.0.5.22:7000/sso/verify? access_token={1}Copy the code

Testing:

  1. willSsoClientDemo projectDeployed in withServerDemo projectDifferent servers;
  2. First startupSsoClientDemo projectAnd access the page you need to log in to, for example:http://192.168.197.130:6080/user/userIndex;
  3. You can see the jump toServerDemo projectAfter the server login is successful, jump toSsoClientDemo projectthe/user/userIndexIs displayed, indicating that the client is also successfully logged in.
  4. restartSsoClientDemo projectAnd visit againhttp://192.168.197.130:6080/user/userIndex, you can find that this is a direct login (of course you can also putSsoClientDemo projectDeploy to multiple servers and log in successively to check the effect), which indicates that the single sign-on function has been implemented.

That’s the end of this article. Thank you for reading.

Reference:

  • OAuth authentication mechanism and single sign-on principle