OAuth2.0协议入门(三):OAuth2.0受权与单点登陆(SSO)的区别以及单点登陆服务端从设计到实现

一 OAuth2.0受权与单点登陆(SSO)的区别

在前两篇文章中我介绍了OAuth2.0协议的基本概念(www.zifangsky.cn/1309.html)以及OAuth2.0受权服务端从设计到实现(www.zifangsky.cn/1313.html)。这篇文章中我将介绍OAuth2.0受权与单点登陆的区别,这两个概念看似很类似,实际上却有很大区别,而不少人每每把两者混为一谈。html

什么是单点登陆?

单点登陆的英文名是 Single Sign On,所以通常简称为SSO。它的用途在于,无论多么复杂的应用群,只要在用户权限范围内,那么就能够作到,用户只须要登陆一次就能够访问权限范围内的全部应用子系统。对于用户而言,访问多个应用子系统只须要登陆一次,一样在须要注销的时候也只须要注销一次。举个简单的例子,你在百度首页登陆成功以后,你再访问百度百科、百度知道、百度贴吧等网站也会处于登陆状态了,这就是一个单点登陆的真实案例。java

OAuth2.0受权与单点登陆的区别

根据OAuth2.0受权与单点登陆的概念,咱们能够得知两者至少存在如下几点区别:git

  1. 从信任角度来看。OAuth2.0受权服务端和第三方客户端不属于一个互相信任的应用群(一般都不是同一个公司提供的服务),第三方客户端的用户不属于OAuth2.0受权服务端的官方用户;而单点登陆的服务端和接入的客户端都在一个互相信任的应用群(一般是同一个公司提供的服务),各个子系统的用户属于单点登陆服务端的官方用户。
  2. 从资源角度来看。OAuth2.0受权主要是让用户自行决定——“我”在OAuth2.0服务提供方的我的资源是否容许第三方应用访问;而单点登陆的资源都在客户端这边,单点登陆的服务端主要用于登陆,以及管理用户在各个子系统的权限信息。
  3. 从流程角度来看。OAuth2.0受权的时候,第三方客户端须要拿预先“商量”好的密码去获取Access Token;而单点登陆则不须要。

二 单点登陆服务端的设计

对于一个接入单点登陆的子系统而言,进行单点登陆须要如下两个步骤:web

  1. client请求单点登陆服务端,获取Access Token
  2. client由于不能判断给它的Access Token是单点登陆服务端返回仍是用户伪造,因此须要再次请求单点登陆服务端,校验Access Token是否有效,若是有效则返回用户基本信息以及相应的用户在client上所属的角色、权限等信息

所以,单点登陆服务端的设计主要围绕这两个接口展开,其主要流程是这样的:spring

单点登陆的主要流程

数据库的表结构设计

提示:我在下面只介绍一些表的主要字段,这个Demo中使用的完整的表结构能够参考:gitee.com/zifangsky/O…sql

(1)sso_client_details数据库

接入单点登陆的子系统详情表。相似于百度的百度百科、百度知道、百度贴吧等应用子系统,每一个想要接入单点登陆的子系统都须要事先在服务端这里“备案”。一方面是为了防止用户在服务端登陆成功生成的Access Token被重定向到非法网站,从而致使用户的Access Token被窃取;另外一方面是记录接入的子系统的注销URL,便于开发单点注销功能。因此主要须要如下几个字段:apache

  • client_name:子系统的名称
  • redirect_url:获取Access Token成功后的回调URL
  • logout_url:用户在子系统的注销URL(用户登陆状态能够分为:全局登陆——在单点登陆服务端的登陆状态;局部登陆——在子系统的登陆状态,注销的时候须要同时注销用户在单点登陆服务端和应用子系统的登陆状态)

(2)sso_access_tokenjson

单点登陆的Access Token信息表。这个表主要体现出哪一个用户在哪一个子系统登陆,以及生成的令牌的结束日期是哪天。因此主要须要如下几个字段:bash

  • access_tokenAccess Token字段
  • user_id:代表是哪一个用户登陆
  • client_id:代表是在哪一个子系统登陆
  • expires_in:过时时间戳,代表这个Token在哪一天过时

(3)sso_refresh_token

单点登陆的Refresh Token信息表。这个表主要用来记录Refresh Token,在设计表结构的时候须要关联它对应的sso_access_token表的记录。因此主要须要如下几个字段:

  • refresh_tokenRefresh Token字段
  • token_id:它对应的sso_access_token表的记录
  • expires_in:过时时间戳

三 单点登陆服务端主要接口的代码实现

这个Demo的单点登陆服务端的完整可用源码能够参考:gitee.com/zifangsky/O…

(1)获取Access Token:

client请求单点登陆服务端,获取Access Token,获取完成以后重定向到请求中的回调URL。

接口地址http://127.0.0.1:7000/sso/token?redirect_uri=http://192.168.197.130:6080/login

/** * 获取Token * @author zifangsky * @date 2018/8/30 16:30 * @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);

	//过时时间
	Long expiresIn = DateUtils.dayToSecond(ExpireEnum.ACCESS_TOKEN.getTime());
	//回调URL
	String redirectUri = request.getParameter("redirect_uri");
	//查询接入客户端
	SsoClientDetails ssoClientDetails = ssoService.selectByRedirectUrl(redirectUri);

	//获取用户IP
	String requestIp = SpringContextUtils.getRequestIp(request);

	//生成Access Token
	String accessTokenStr = ssoService.createAccessToken(user, expiresIn, requestIp, ssoClientDetails);
	//查询已经插入到数据库的Access Token
	SsoAccessToken ssoAccessToken = ssoService.selectByAccessToken(accessTokenStr);
	//生成Refresh Token
	String refreshTokenStr = ssoService.createRefreshToken(user, ssoAccessToken);
	logger.info(MessageFormat.format("单点登陆获取Token:username:【{0}】,channel:【{1}】,Access Token:【{2}】,Refresh Token:【{3}】"
			,user.getUsername(),ssoClientDetails.getClientName(),accessTokenStr,refreshTokenStr));

	String params = "?code=" + accessTokenStr;
	return new ModelAndView("redirect:" + redirectUri + params);
}
复制代码

相应地,调用的cn/zifangsky/service/impl/SsoServiceImpl.java类里面的生成逻辑:

@Override
public String createAccessToken(User user, Long expiresIn, String requestIP, SsoClientDetails ssoClientDetails) {
	Date current = new Date();
	//过时的时间戳
	Long expiresAt = DateUtils.nextDaysSecond(ExpireEnum.ACCESS_TOKEN.getTime(), null);

	//1. 拼装待加密字符串(username + 渠道CODE + 当前精确到毫秒的时间戳)
	String str = user.getUsername() + ssoClientDetails.getClientName() + String.valueOf(DateUtils.currentTimeMillis());

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

	//3. 保存Access Token
   SsoAccessToken savedAccessToken = ssoAccessTokenMapper.selectByUserIdAndClientId(user.getId(), ssoClientDetails.getId());
	//若是存在匹配的记录,则更新原记录,不然向数据库中插入新记录
	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. 返回Access Token
	return accessTokenStr;
}

@Override
public String createRefreshToken(User user, SsoAccessToken ssoAccessToken) {
	Date current = new Date();
	//过时时间
	Long expiresIn = DateUtils.dayToSecond(ExpireEnum.REFRESH_TOKEN.getTime());
	//过时的时间戳
	Long expiresAt = DateUtils.nextDaysSecond(ExpireEnum.REFRESH_TOKEN.getTime(), null);

	//1. 拼装待加密字符串(username + accessToken + 当前精确到毫秒的时间戳)
	String str = user.getUsername() + ssoAccessToken.getAccessToken() + String.valueOf(DateUtils.currentTimeMillis());

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

	//3. 保存Refresh Token
	SsoRefreshToken savedRefreshToken = ssoRefreshTokenMapper.selectByTokenId(ssoAccessToken.getId());
	//若是存在tokenId匹配的记录,则更新原记录,不然向数据库中插入新记录
	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);
	}

	//4. 返回Refresh Tokens
	return refreshTokenStr;
}
复制代码

(2)校验Access Token,并返回用户信息:

client在获取到Access Token后,再次调用单点登陆服务端接口,用于“校验Access Token,并返回用户信息”。

接口地址http://127.0.0.1:7000/sso/verify?access_token=11.ad51132688b5be3f476592356c78aef71d235f07.2592000.1539143183

返回以下

{
	"access_token": "11.ad51132688b5be3f476592356c78aef71d235f07.2592000.1539143183",
	"refresh_token": "12.c10cb9001bf0e2c7f808580318715fc089673279.31536000.1568087183",
	"expires_in": 2592000,
	"user_info": {
		"id": 2,
		"username": "zifangsky",
		"password": "$5$toOBSeX2$hSnSDyhJmVVRpbmKuIY4SxDgyeGRGacQaBYGrtEBnZA",
		"mobile": "110",
		"email": "admin@zifangsky.cn",
		"createTime": "2017-12-31T16:00:00.000+0000",
		"updateTime": "2017-12-31T16:00:00.000+0000",
		"status": 1,
		"roles": [{
				"id": 2,
				"roleName": "user",
				"description": "普通用户",
				"funcs": null
			}
		]
	}
}
复制代码

首先在一个拦截器里校验Access Token是否有效:

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;

/** * 用于校验Access Token是否为空以及Access Token是否已经失效 * * @author zifangsky * @date 2018/8/30 * @since 1.0.0 */
public class SsoAccessTokenInterceptor extends HandlerInterceptorAdapter{
    @Resource(name = "ssoServiceImpl")
    private SsoService ssoService;

    /** * 检查Access Token是否已经失效 */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String accessToken = request.getParameter("access_token");

        if(StringUtils.isNoneBlank(accessToken)){
            //查询数据库中的Access Token
            SsoAccessToken ssoAccessToken = ssoService.selectByAccessToken(accessToken);

            if(ssoAccessToken != null){
                Long savedExpiresAt = ssoAccessToken.getExpiresIn();
                //过时日期
                LocalDateTime expiresDateTime = DateUtils.ofEpochSecond(savedExpiresAt, null);
                //当前日期
                LocalDateTime nowDateTime = DateUtils.now();

                //若是Access Token已经失效,则返回错误提示
                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);
        }
    }
    
    /** * 组装错误请求的返回 */
    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;
    }

}
复制代码

而后返回用户信息:

/** * 校验Access 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 = "/verify", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public Map<String,Object> verify(HttpServletRequest request) {
	Map<String, Object> result = new HashMap<>(8);

	//获取Access Token
	String accessToken = request.getParameter("access_token");

	try {
		//过时时间
		Long expiresIn = DateUtils.dayToSecond(ExpireEnum.ACCESS_TOKEN.getTime());
		//查询Access Token
		SsoAccessToken ssoAccessToken = ssoService.selectByAccessToken(accessToken);
		//查询Refresh Token
		SsoRefreshToken ssoRefreshToken = ssoService.selectByTokenId(ssoAccessToken.getId());
		//查询用户信息
		UserBo userBo = userService.selectUserBoByUserId(ssoAccessToken.getUserId());

		//组装返回信息
		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);
		return result;
	}
}
复制代码

(3)经过Refresh Token刷新Access Token接口:

若是客户端的Access Token过时了,那么就能够经过这个接口刷新Access Token。

接口地址http://127.0.0.1:7000/sso/refreshToken?refresh_token=12.c10cb9001bf0e2c7f808580318715fc089673279.31536000.1568087183

返回以下

{
	"access_token": "11.40f0270697c37db4570e41e0f6f335bf6c2f8902.2592000.1539164947",
	"refresh_token": "12.c10cb9001bf0e2c7f808580318715fc089673279.31536000.1568087183",
	"expires_in": 2592000,
	"user_info": {
		"id": 2,
		"username": "zifangsky",
		"password": "$5$toOBSeX2$hSnSDyhJmVVRpbmKuIY4SxDgyeGRGacQaBYGrtEBnZA",
		"mobile": "110",
		"email": "admin@zifangsky.cn",
		"createTime": "2017-12-31T16:00:00.000+0000",
		"updateTime": "2017-12-31T16:00:00.000+0000",
		"status": 1,
		"roles": [{
				"id": 2,
				"roleName": "user",
				"description": "普通用户",
				"funcs": null
			}
		]
	}
}
复制代码
/** * 经过Refresh Token刷新Access 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);

	//获取Refresh Token
	String refreshTokenStr = request.getParameter("refresh_token");
	//获取用户IP
	String requestIp = SpringContextUtils.getRequestIp(request);

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

		if(ssoRefreshToken != null) {
			Long savedExpiresAt = ssoRefreshToken.getExpiresIn();
			//过时日期
			LocalDateTime expiresDateTime = DateUtils.ofEpochSecond(savedExpiresAt, null);
			//当前日期
			LocalDateTime nowDateTime = DateUtils.now();

			//若是Refresh Token已经失效,则须要从新生成
			if (expiresDateTime.isBefore(nowDateTime)) {
				this.generateErrorResponse(result, ErrorCodeEnum.EXPIRED_TOKEN);
				return result;
			} else {
				//获取存储的Access Token
				SsoAccessToken ssoAccessToken = ssoService.selectByAccessId(ssoRefreshToken.getTokenId());
				//查询接入客户端
				SsoClientDetails ssoClientDetails = ssoService.selectByPrimaryKey(ssoAccessToken.getClientId());
				//获取对应的用户信息
				User user = userService.selectByUserId(ssoAccessToken.getUserId());

				//新的过时时间
				Long expiresIn = DateUtils.dayToSecond(ExpireEnum.ACCESS_TOKEN.getTime());
				//生成新的Access Token
				String newAccessTokenStr = ssoService.createAccessToken(user, expiresIn, requestIp, ssoClientDetails);
				//查询用户信息
				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));

				//组装返回信息
				result.put("access_token", newAccessTokenStr);
				result.put("refresh_token", ssoRefreshToken.getRefreshToken());
				result.put("expires_in", expiresIn);
				result.put("user_info", userBo);
				return result;
			}
		}else {
			this.generateErrorResponse(result, ErrorCodeEnum.INVALID_GRANT);
			return result;
		}
	}catch (Exception e){
		e.printStackTrace();
		this.generateErrorResponse(result, ErrorCodeEnum.UNKNOWN_ERROR);
		return result;
	}
}

/** * 组装错误请求的返回 */
private void generateErrorResponse(Map<String,Object> result, ErrorCodeEnum errorCodeEnum) {
	result.put("error", errorCodeEnum.getError());
	result.put("error_description",errorCodeEnum.getErrorDescription());
}
复制代码

(4)单点注销接口:

在这个Demo项目中,我没有提供单点注销功能的示例代码,可是我能够简单说一下单点注销功能的主要流程,若是须要这个功能能够自行使用代码实现:

  1. 用户在应用子系统请求注销登陆;
  2. 用户在应用子系统注销完成后,应用子系统后台请求单点登陆服务端的注销接口;
  3. 单点登陆服务端的注销接口根据用户的Token在数据库中查询当前用户登陆的全部应用子系统的注销接口,而后依次调用注销便可。

四 接入单点登陆的子系统的关键代码

这个Demo的单点登陆子系统的完整可用源码能够参考:gitee.com/zifangsky/O…

其实,对于接入单点登陆的子系统来讲,登陆模块调用单点登陆服务端提供的接口就能够了。

登陆校验过滤器:

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;

/** * 定义一些页面须要作登陆检查 * * @author zifangsky * @date 2018/7/26 * @since 1.0.0 */
public class LoginInterceptor extends HandlerInterceptorAdapter{

    /** * 检查是否已经登陆 */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();

        //获取session中存储的用户信息
        UserBo user = (UserBo) session.getAttribute(Constants.SESSION_USER);

        if(user != null){
            return true;
        }else{
            //若是token不存在,则跳转等登陆页面
            response.sendRedirect(request.getContextPath() + "/login?redirectUrl=" + SpringContextUtils.getRequestUrl(request));

            return false;
        }
    }
}
复制代码

登陆相关的代码逻辑:

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;

/** * 登陆 * @author zifangsky * @date 2018/7/9 * @since 1.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;

    /** * 登陆验证(实际登陆调用认证服务器) * @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){
        //当前系统登陆成功以后的回调URL
        String redirectUrl = request.getParameter("redirectUrl");
        //当前系统请求认证服务器成功以后返回的Token
        String code = request.getParameter("code");

        //最后重定向的URL
        String resultUrl = "redirect:";
        HttpSession session = request.getSession();

        //1. code为空,则说明当前请求不是认证服务器的回调请求,则重定向URL到认证服务器登陆
        if(StringUtils.isBlank(code)){
            //若是存在回调URL,则将这个URL添加到session
            if(StringUtils.isNoneBlank(redirectUrl)){
                session.setAttribute(Constants.SESSION_LOGIN_REDIRECT_URL,redirectUrl);
            }

            //拼装请求Token的地址
            resultUrl += accessTokenUri;
        }else{
            //2. 验证Token,并返回用户基本信息、所属角色、访问权限等
            SsoResponse verifyResponse = restTemplate.getForObject(verifyUri, SsoResponse.class
                    ,code);

            //若是正常返回
            if(StringUtils.isNoneBlank(verifyResponse.getAccess_token())){
                //2.1 将用户信息存到session
                session.setAttribute(Constants.SESSION_USER,verifyResponse.getUser_info());

                //2.2 将Access Token和Refresh Token写到cookie
                CookieUtils.addCookie(response,Constants.COOKIE_ACCESS_TOKEN, verifyResponse.getAccess_token(),request.getServerName());
                CookieUtils.addCookie(response,Constants.COOKIE_REFRESH_TOKEN, verifyResponse.getRefresh_token(),request.getServerName());
            }

            //3. 从session中获取回调URL,并返回
            redirectUrl = (String) session.getAttribute(Constants.SESSION_LOGIN_REDIRECT_URL);
            session.removeAttribute("redirectUrl");
            if(StringUtils.isNoneBlank(redirectUrl)){
                resultUrl += redirectUrl;
            }else{
                resultUrl += "/user/userIndex";
            }
        }

        return new ModelAndView(resultUrl);
    }

}
复制代码

固然,上面代码中使用到的一些配置就是咱们单点登陆服务端的接口地址:

own.sso.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}
复制代码

测试:

  1. SsoClientDemo项目部署在跟ServerDemo项目不一样的服务器;
  2. 第一次启动SsoClientDemo项目并访问须要登陆的页面,好比:http://192.168.197.130:6080/user/userIndex
  3. 能够发现这时跳转到ServerDemo项目,在服务端登陆成功以后,跳转到SsoClientDemo项目/user/userIndex,说明客户端也登陆成功了;
  4. 重启SsoClientDemo项目,并再次访问http://192.168.197.130:6080/user/userIndex,能够发现此次是直接登陆了(固然也能够把SsoClientDemo项目部署到多个服务器上面,前后登陆查看效果),这说明单点登陆功能已经实现。

本篇文章到此结束,感谢你们的阅读。

参考:

相关文章
相关标签/搜索