SSO单点登录实例详解(前端传Code授权登录)

什么是 SSO(单点登录)

SSO 英文全称 Single Sign On,单点登录。SSO 是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

单点登录流程

单点登录大致流程如下所示:
在这里插入图片描述
单点登录详细流程:
首先在前端点击登录子系统的时候(主系统已经完成账号和密码登录),主系统会给前端在页面的地址栏上面给出code值(我感觉不是很安全,但就是设计成这样),code值是随机生成的,并且有时效性(过段时间就无效了)。
在这里插入图片描述
后端代码里面会直接接收这样的code值:
在这里插入图片描述
之后根据文档,把code值和其他在配置文件配置的固定值封装在一起并且传给主系统以获取accessToken 以及其他的信息(用户登录信息等),代码如下:

// 使用授权码code换取accessToken
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("code", code));
params.add(new BasicNameValuePair("client_id", Configuration.getClientId()));
params.add(new BasicNameValuePair("client_secret", Configuration.getClientSecret()));
params.add(new BasicNameValuePair("auth_type", Configuration.getGrantType()));
params.add(new BasicNameValuePair("redirect_uri", Configuration.getRedirectUri()));
JSONObject result = HttpClientUtils.post(Configuration.getClientUrl(), params);
log.info("使用授权码code换取accessToken  URL:{},params:{}",Configuration.getClientUrl(),JSON.toJSONString(params));

接下来把获取的信息遍历并且封装起来,最后需要把这些信息都传给前端:

// 一般用HashMap传给前端
Map<String,Object> res = new HashMap<String,Object>();
// 直接用迭代器遍历JSONObject对象result 
Iterator iter = result.entrySet().iterator();
while (iter.hasNext()) {
	Map.Entry entry = (Map.Entry) iter.next();
	res.put(entry.getKey().toString(), entry.getValue().toString());
}

如果主系统传给我的result不为空,就直接开始解析这个accessToken以获取单点登录的用户信息,子系统可以直接解析这个accessToken

String key = Configuration.getJwtKey();
Claims claims = Jwts.parser().setSigningKey(generalKey(key)).parseClaimsJws(result.getString("access_token")).getBody();
JWTInfo info = new JWTInfo(claims);
log.info("解析access_token:{}", info);
log.info("state:" + state);
log.info("用户信息:{}", info);

解析accessToken之后,就开始走子系统(我这个系统)的登录逻辑。
如果不存在时,则直接添加用户(和直接新增一个用户一样的逻辑):

// 本地登陆逻辑
User user = UserService.findByUserName(info.getUsername());
if (user==null){
	log.info("当前用户在系统不存在时 新增用户");
	// 当前用户在系统不存在时 新增用户
	User User=new User();
	// 默认密码是当前账号名
	User.setPassword(info.getUsername());
	User.setUsername(info.getUsername());
	User.setTruename(info.getName());
	User.setNickname(info.getUsername());
	User.setTenantid(tenantid_default);
	User.setSchemaname(crtenant + tenantid_default.toString());
	// 设置用户状态
	User.setStatus(1);
	// todo 给新增用户设置默认的权限
	log.info("新增用户,参数:{}",User);
	user = UserService.addUser(User);
	log.info("新增用户返回的结果:{}",user==null?"null":JSON.toJSONString(user));
}

如果新增完用户或者本地本来就有这个用户,就继续往下走,先把这个用户放入登录用户的上下文(当前线程ThreadLocal,每个用户都会有单独的线程,以用户名作为当前线程的唯一标识,以后都从这里取用户名),然后再新增一个JWT Token(本系统新增的token信息,目的是为了传给前端,前端以后都用这个token信息来获取数据),并且将这个token信息放入res中,最后通过mergeRightValues来整合用户的权限信息:

log.info("使用用户名密码登录 username:{},password:{}",user.getUsername()==null?"null":user.getUsername(),user.getPassword()==null?"null":user.getPassword());
//  放入当前登录用户上下文
UserContext.setCurrentUserName(user.getUsername());
String token = JWTUtils.createJWT("username", user.getUsername(), user.getUsername(), expire * 1000);
res.put("Token", token);
String rightvalues = authService.mergeRightValues(user);

接着,通过access_token来获取主系统的用户信息和token信息:

// 获取主系统的token
String access_token = result.getString("access_token");
List<NameValuePair> queryParams = new ArrayList<>();
queryParams.add(new BasicNameValuePair("token", access_token));
FrontUserDto frontUserDto = HttpClientUtils.getForxxx("http://172.xxx.xxx:8080/xxx/xxx/xxx/front/info", queryParams, access_token);
log.info("获取主系统的信息:frontUserDto:{}",frontUserDto);
res.put("userinfo", frontUserDto);

接下来把用户的媒体权限信息保存到redis中,之后前端每次从后端获取数据的时候都会从redis里面直接获取用户的媒体权限信息来确保信息的安全性:

// 把用户的权限缓存到redis中
List<PermissionInfoDto> copyright = frontUserDto.getElements().stream().filter(permissionInfoDto -> permissionInfoDto.getCode().contains("COPYRIGHT")).collect(Collectors.toList());
String userMediaIds = loginUserService.getMediaIdsBasedMediaName(copyright);
if (StringUtils.isEmpty(userMediaIds)){
	return new CallBackMessage(result);
}
redisTemplate.opsForValue().set("userMedia-"+ frontUserDto.getUsername(),userMediaIds);

接下来解析之前的权限信息rightvalues,解析成为权限ID值rightIds (这个ID值具有检索用户权限的作用,直接传给前端,之后前端会根据这些权限ID值来获取数据,需要注意的是,这个和前面的媒体权限ID不是一样的):

String rightIds = authService.getRightIdsByRightvalues(rightvalues);
log.info("defaultRightIds:{}",defaultRightIds==null?"null":defaultRightIds);
log.info("rightIds:{}",rightIds==null?"null":rightIds);
//当获取到权限为空时  使用默认权限防止前端报错
if (StringUtils.isBlank(rightIds)){
	rightIds=StringUtils.join(defaultRightIds.split(","), "_");
}else {
	rightIds = StringUtils.join(rightIds.split(","), "_");
}
log.info("rightIds:{}",rightIds==null?"null":rightIds);
log.info("token:{}",token==null?"null":token);
res.put("rightIds", rightIds);

接着将前面通过code获取的refresh_token缓存到redis中,之后注销登录的时候需要通过获取refresh_token来确认这个用户以此来注销用户:

//将refresh_token缓存到redis
String refreshToken=result.getString("refresh_token");
if (StringUtils.isNotBlank(refreshToken)&&StringUtils.isNotBlank(token)){
	String redisKey="data:center:auth:logout:";
	stringRedisTemplate.opsForValue().set(redisKey+token,refreshToken,1, TimeUnit.DAYS);
}

最后,把登录日志存入数据库,并且返回res给前端:

//记录登录日志
MySchema mySchema = new MySchema();
mySchema.setSchemaname(user.getSchemaname());
mySchema.setTenantid(user.getTenantid());
MySchemaHolder.setCurrentMySchema(mySchema);
sysLogService.addSafetyLog(user, request);
return new CallBackMessage(res);

全部代码

	@RequestMapping(value = "/token", method = RequestMethod.GET)
	public CallBackMessage login(String code, String state, HttpServletResponse response,HttpServletRequest request) throws IOException {
		Map<String,Object> res = new HashMap<String,Object>();
		// 使用授权码code换取accessToken
		List<NameValuePair> params = new ArrayList<>();
		params.add(new BasicNameValuePair("code", code));
		params.add(new BasicNameValuePair("client_id", Configuration.getClientId()));
		params.add(new BasicNameValuePair("client_secret", Configuration.getClientSecret()));
		params.add(new BasicNameValuePair("auth_type", Configuration.getGrantType()));
		params.add(new BasicNameValuePair("redirect_uri", Configuration.getRedirectUri()));
		JSONObject result = HttpClientUtils.post(Configuration.getClientUrl(), params);
		log.info("使用授权码code换取accessToken  URL:{},params:{}",Configuration.getClientUrl(),JSON.toJSONString(params));

		Iterator iter = result.entrySet().iterator();
		while (iter.hasNext()) {
			Map.Entry entry = (Map.Entry) iter.next();
			res.put(entry.getKey().toString(), entry.getValue().toString());
		}
		System.out.println(res);

		if (result != null) {
			log.info("使用授权码code换取accessToken,返回结果:{}",result.toJSONString());
			try {
				String key = Configuration.getJwtKey();
				Claims claims = Jwts.parser().setSigningKey(generalKey(key)).parseClaimsJws(result.getString("access_token")).getBody();
				JWTInfo info = new JWTInfo(claims);
				log.info("解析access_token:{}", info);
				log.info("state:" + state);
				log.info("用户信息:{}", info);
				// 本地登陆逻辑
				User user = UserService.findByUserName(info.getUsername());
				if (user==null){
					log.info("当前用户在系统不存在时 新增用户");
					// 当前用户在系统不存在时 新增用户
					User User=new User();
					// 默认密码是当前账号名
					User.setPassword(info.getUsername());
					User.setUsername(info.getUsername());
					User.setTruename(info.getName());
					User.setNickname(info.getUsername());
					User.setTenantid(tenantid_default);
					User.setSchemaname(crtenant+tenantid_default.toString());
					// 设置用户状态
					User.setStatus(1);
					// todo 给新增用户设置默认的权限
					log.info("新增用户,参数:{}",User);
					user = UserService.addUser(User);
					log.info("新增用户返回的结果:{}",user==null?"null":JSON.toJSONString(user));
				}
				log.info("使用用户名密码登录 username:{},password:{}",user.getUsername()==null?"null":user.getUsername(),user.getPassword()==null?"null":user.getPassword());
				// 放入当前登录用户上下文
				UserContext.setCurrentUserName(user.getUsername());
				String token = JWTUtils.createJWT("username", user.getUsername(), user.getUsername(), expire * 1000);
				res.put("copyRightToken", token);
				String rightvalues = authService.mergeRightValues(user);

				// 获取主系统的token
				String access_token = result.getString("access_token");
				List<NameValuePair> queryParams = new ArrayList<>();
				queryParams.add(new BasicNameValuePair("token", access_token));
				FrontUserDto frontUserDto = HttpClientUtils.getForSzxm("http://xxxx.xxxx.xxx/xxx/xxx/xxx/front/info", queryParams, access_token);
				log.info("获取主系统的信息:frontUserDto:{}",frontUserDto);
				res.put("userinfo", frontUserDto);

				// 把用户的权限缓存到redis中
				List<PermissionInfoDto> copyright = frontUserDto.getElements().stream().filter(permissionInfoDto -> permissionInfoDto.getCode().contains("COPYRIGHT")).collect(Collectors.toList());
				String userMediaIds = loginUserService.getMediaIdsBasedMediaName(copyright);
				if (StringUtils.isEmpty(userMediaIds)){
					return new CallBackMessage(result);
				}
				redisTemplate.opsForValue().set("userMedia-"+ frontUserDto.getUsername(),userMediaIds);

//          	String rightIds = "";
				String rightIds = authService.getRightIdsByRightvalues(rightvalues);
				log.info("defaultRightIds:{}",defaultRightIds==null?"null":defaultRightIds);
				log.info("rightIds:{}",rightIds==null?"null":rightIds);
				// 当获取到权限为空时  使用默认权限防止前端报错
				if (StringUtils.isBlank(rightIds)){
					rightIds=StringUtils.join(defaultRightIds.split(","), "_");
				}else {
					rightIds = StringUtils.join(rightIds.split(","), "_");
				}
				log.info("rightIds:{}",rightIds==null?"null":rightIds);
				log.info("token:{}",token==null?"null":token);

				res.put("rightIds", rightIds);

				// 将refresh_token缓存到redis
				String refreshToken=result.getString("refresh_token");

				if (StringUtils.isNotBlank(refreshToken)&&StringUtils.isNotBlank(token)){
					String redisKey="data:center:auth:logout:";
					stringRedisTemplate.opsForValue().set(redisKey+token,refreshToken,1, TimeUnit.DAYS);
				}

				// 记录登录日志
				MySchema mySchema = new MySchema();
				mySchema.setSchemaname(user.getSchemaname());
				mySchema.setTenantid(user.getTenantid());
				MySchemaHolder.setCurrentMySchema(mySchema);
				sysLogService.addSafetyLog(user, request);
				return new CallBackMessage(res);
			} catch (Exception e) {
				log.error(e.getMessage());
				log.info("sendRedirect4:{}",Configuration.getLoginUrl());
				response.sendRedirect(Configuration.getLoginUrl());
			}
		} else {
			log.info("sendRedirect4:{}",Configuration.getLoginUrl());
			response.sendRedirect(Configuration.getLoginUrl());
		}
		return new CallBackMessage("失败");
	}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值