IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> SSO 轻量级实现指南(原生 Java 实现):SSO Client 部分 -> 正文阅读

[Java知识库]SSO 轻量级实现指南(原生 Java 实现):SSO Client 部分

根据单点登录的定义,客户端可以完全不用创建自己的用户系统,它只需要接入 SSO 中心的服务就好。SSO 中心关于用户的常规业务都在其内。那么客户端接入单点登录,需要做什么工作呢?首先用户一般常规操作有:

  • 用户注册。这部分 SSO 中心提供注册接口。客户端自定义自己风格注册 UI,跨域请求数据到 SSO 中心接口即可;
  • 用户登录。这部分 SSO 中心提供登录接口。客户端自定义自己风格登录 UI,跨域请求数据到 SSO 中心接口即可;
  • 用户注销登陆。这部分 SSO 中心提供登录接口,跨域请求数据到 SSO 中心接口即可;
  • 用户常规查询操作,例如查询列表、单个用户详情等,这部分 SSO 中心开放相关 API。

一般常规接口上文已经讨论过了。可见 SSO 中心一个特性要求便是允许“跨域访问”,这个问题不大,进行相关配置即可。

SSO 中心,即认证中心,关键一点在于用户的认证。除了上述登录是重要的认证过程外,每次涉及相关操作都必须进行认证,否则就是非法访问。

认证的问题

如果按照 OAuth 本来的目的,资源服务器跟认证服务器是在一块的,比如说微博,它有个开放平台你可以根据 AccessToken 获取它微博内容。每次访问都有提供 AccessToken 参数,看是否合法才允许访问。

但目前我们搞的不是纯粹 OAuth,上文《SSO 与 OAuth 傻傻分不清?》小节已经说过了。SSO 认证中心往往不是跟资源服务器在一起的“单体”结构,而是独立部署的;而且应用端(即客户端)肯定都有自己的资源服务,肯定需要用户认证、权限校验之类的操作。那么问题来了,校验客户端凭证令牌(即 AccessToken)这项工作,——是放在应用端还是 SSO 中心呢?

显然易见,作为统一的认证中心,SSO 中心无疑拥有最根本的用户状态记录,一切皆以 SSO 中心的为准。但每次访问资源的认证工作都要通讯 SSO 中心,性能成本会不会太高呢?对于 SSO 中心服务器的性能也是严重的考验。对此,笔者考虑了以下几个个解决方案。

  • 还是在 SSO 中心校验,但采取优化手段:对已验证的 token 进行缓存,仅首次访问时调用 SSO 验证一次,一般缓存10分钟这种,便于 SSO 进行 token 撤销。
  • 无须 SSO 校验 token,采用自描述的 token。这种自描述的 Token 比普通的 Token 的复杂,解密之后包含了更多的信息,根据这些信息对比、校验便能清楚是否合法,以及一定的用户信息。举个例子,如“重置密码”,在邮件中包含一个带 token 的连接,后端得到这 token 后其实有时间戳的信息的,再对比一下便能知道是否超时的请求。
  • 采用自描述的 Token,其实跟大家说 JWT 就可以了,它就是干这事的。不过笔者说实话还不太懂 JWT,当前方案中还没有使用 JWT。
  • 应用端自建用户登录会话。其实就是冗余一套 SSO 中心的,用户登录之后回来马上搞自己的 Session。但怎么同步是个问题,而且隐约好像不是“单点”的意思了。当前我正在使用这方案。

应用端自建用户登录会话

既然选定了这个方案,那我们就看看怎么做吧。首先是用户登录之后马上建立 Session。源码在这里

这属于客户端登录的一部分,得到授权码之后在服务端发起请求。

@GetMapping(value = "clientLogin", produces = JSON)
public String clientLogin(@RequestParam String code, HttpServletRequest req) {
	Map<String, Object> params = new HashMap<>();
	params.put("code", code);
	params.put("grant_type", GRANT_TYPE);
	params.put("client_id", clientId);
	params.put("client_secret", clientSecret);

	Map<String, Object> result = Post.api(api + "/sso/authorize", params);

	UserSession saveSession = saveSession(result);
	// 存入 session
	req.getSession().setAttribute(saveSession.accessToken.getAccessToken(), saveSession);

	return "${User.home}".equals(userHome) ? toJson(result) : "redirect:/" + userHome;
}

/**
 * JSON 结果转换为 Session 存储,形成本地登录状态
 * 
 * @param result
 * @return
 */
static UserSession saveSession(Map<String, Object> result) {
	AccessToken accessToken = new AccessToken();
	accessToken.setAccessToken(result.get("access_token").toString());
	accessToken.setRefreshToken(result.get("refresh_token").toString());
	accessToken.setScope(result.get("scope").toString());
	accessToken.setExpiresIn(((Integer) result.get("expires_in")).longValue());

	@SuppressWarnings("unchecked")
	Map<String, Object> userJson = (Map<String, Object>) result.get("user");
	User user = MapTool.map2Bean(userJson, User.class, true);

	UserSession userSession = new UserSession();
	userSession.accessToken = accessToken;
	userSession.user = user;

	return userSession;
}

若登录成功,就在客户端本地产生 Session。其中重点就是 UserSession ,它包含了用户和 AccessToken 两种对象,以 Token 为 key 存到 Session 中。

校验拦截 Token

有了本地的用户登录状态,就无须访问 SSO 中心校验了,于是也变得简单和高效了。所有校验都发生在本地进行。我们看看这个拦截器 SsoAccessTokenInterceptor,它是标准的 Spring 拦截器。

你先需要在 yaml 配置中定义一下要保护资源的访问路径,即接口,按照 Spring 拦截器的配置。

User:
 resources: /api/**, /user/**	  # 要保护的资源
 excludeResources: /user/login/** # 排除的路径

记得路径后面要加上 ** 同贝所有子路径。

/**
 * 要保护的资源(只有登录了才能访问)
 */
@Value("${User.resources}")
private String[] protectPerfix;

/**
 * 要保护的资源(只有登录了才能访问)
 */
@Value("${User.excludeResources}")
private String[] excludeResources;

/**
 * 加入拦截器
 */
@Override
public void addInterceptors(InterceptorRegistry registry) {
	registry.addInterceptor(tokenInterceptor).addPathPatterns(protectPerfix).excludePathPatterns(excludeResources);

	super.addInterceptors(registry);
}

拦截器代码

import java.io.IOException;
import java.time.LocalDateTime;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import com.ajaxjs.framework.BaseController;
import com.ajaxjs.user.sso.model.AccessToken;
import com.ajaxjs.user.sso.model.UserSession;
import com.ajaxjs.util.date.LocalDateUtils;

/**
 * 校验 AccessToken 的拦截器
 * 
 * @author Frank Cheung<sp42@qq.com>
 *
 */
@Component
public class SsoAccessTokenInterceptor implements HandlerInterceptor {
	@Override
	public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) {
		String accessToken = req.getParameter("access_token");

		if (!StringUtils.hasText(accessToken)) {
			err(resp, "缺少 access_token 参数");

			return false;
		}

		Object object = req.getSession().getAttribute(accessToken);

		if (object == null) {
			// TODO 是否拿 Token 去 SSO 中心再校验一下
			err(resp, "非法 AccessToken");

			return false;
		} else {
		}

		UserSession userSess = (UserSession) object;

		// 如果 Access Token 已经失效,则返回错误提示
		if (checkIfExpire(userSess.accessToken)) {
			// TODO 是否要删除过期 token?
			err(resp, "access_token 已超时");
			return false;
		} else
			return true;
	}

	/**
	 * 获取 expiresIn 与当前时间对比,看是否超时
	 * 
	 * @param token 令牌
	 * @return true 表示超时
	 */
	static boolean checkIfExpire(AccessToken token) {
		long expiresIn = token.getExpiresIn();
		LocalDateTime expiresDateTime = LocalDateUtils.ofEpochSecond(expiresIn);// 过期日期
		return expiresDateTime.isBefore(LocalDateTime.now());
	}

	static void err(HttpServletResponse resp, String msg) {
		resp.setStatus(HttpStatus.UNAUTHORIZED.value());
		resp.setHeader("Content-type", "application/json;charset=UTF-8");

		try {
			resp.getWriter().write(BaseController.jsonNoOk(msg));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

SSO Client

上面所述的所有代码都在 SSO Client 这个工程中,可以通过 Maven 加入到你的工程中。

设置 Session 超时时间,在 web.xml 配置一下。

<!-- 时间单位为分钟   -->  
<session-config>
      <session-timeout>15</session-timeout>
</session-config>

Spring Boot 设置 yml

server:
   port: 8089
   session:
      timeout: 1800  #以秒为单位

Ja
va 设置:

session.setMaxInactiveInterval(30*60;//以秒为单位
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-30 08:33:07  更:2022-04-30 08:34:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 2:13:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码