SSO系统架构原理
SSO,Single Sign On即单点登录,在跨域下,传统的session会话无法满足企业级不同站点间的授权登录操作,为了解决这一问题,便采用了单点登录系统架构。其大致流程可为以下:
- 用户首次进入单点系统中某一站点A,用户需要进行账户登录认证,此时页面跳转到SSO统一认证服务授权中心,进行登录授权
- 认证中心验证用户信息,并确认站点信息合法性,生成一个服务认证中心总token,将服务中心token进行缓存并响应返回给客户端
- 此时用户拿到站点服务端token,之后用服务端总token进行校验验证token合法性,服务端缓存中确认token合法性,将站点tokenA返回客户端
- 客户端通过token登录验证确认token合肥,并将自己的站点tokenA缓存
此时浏览器生成服务中心token以及站点tokenA - 当用户进入单点系统站点B,由于浏览器中缓存含有用户token信息,此时便携带认证总token以及站点信息像服务认证中心进行合法性校验
- 当验证成功,服务器便生成一个站点tokenB,并加入用户token缓存中,而客户端收到服务端响应tokenB,将tokenB缓存到客户端
- 浏览器此时将tokenB加入缓存,用户便在站点B实现了登录
而做客户端缓存tokenA,tokenB的目的,便是每次客户token验证时,都不需要直接与认证中心进行直接交互,认证中心只管授权与认证的业务,减缓服务中心认证压力
springboot下的sso后台系统架构
了解了整个系统原理,后台SSO登录模块最主要需要服务端与客户端两个模块,为了实现系统的可插拔性和模块化,采用了springboot下的模块化开发来实现
根据原理架构,整个项目模块定义成如下模块部署
- 核心core包,包含了定义的核心接口,其中含有管理token接口,用户管理登录注册等操作接口,另外还定义了用户可定制化执行操作接口,用于系统拓展,同时包含持久层操作,定义整个系统所用用户表
- sso认证中心,引入核心core包,去实现其中的用户管理,token管理接口,用于实现用户的登录与注册,授权给用户认证权限,生成用户认证令牌,同时对于客户端的认证请求进行认证授权
- sso客户端组件,用于整个sso系统下,各个站点的应用客户端系统,对于站点去集成系统,引入client依赖即可,其中包含了对于用户令牌有效性的认证,实现用户和各个站点之间的认证交互
- sso本地端组件,这个组件是单机部署的策略,在不采用sso系统时也能够通过组件式引入直接用于登录注册认证等一套完整的用户登录功能
可插拔的组件式开发基础依赖于:
- maven私服的jar包管理机制
- 模块间的隔离性,业务间的隔离系统设计,抽象与定义
- springboot的外部自定义bean注入,IOC容器理念
插拔模块的优势:
试想一下,当你的系统极其庞大,变得臃肿不堪,模块与模块间耦合,业务层间相互调用,发现项目A关联项目B,项目C关联项目A,当引入项目A时发现根本无法解耦,需要引入大量无相关业务,那么整个项目基本已经告别维护和复用了。
可插拔的系统设计理念在于,通过便捷的maven的jar包管理机制,只需要引入相关依赖模块,与其他业务相剥离,强调的时即走即用的设计理念
抽象与多态的接口设计
谈起抽象,可能很多人都会知道java语言的这几大特性,但是不得不在老生常谈一下,因为在构建系统的过程中,才会感受到抽象与多态的艺术性。
抽象
,把握住抽象的核心理念,它是对功能的一种描述
,它是对我们系统的某一类功能进行抽象,并明确的描述出这个接口需要去完成哪些事情,而你要做得就是围绕这个设计,去完成他所要描述的事情。
多态
,多态的思想就更具有艺术性了。由于抽象的思想只是功能上的描述,能够完成他的功能的事情会有千千万万种方式,而多态的艺术性就在于,正是由于你的这种千万种不确定性,我不去指定到底到一种方式才能满足我的要求,通过我的基类,抽象类,接口等,在具体的业务中,去指向满足我要求具体实现
,完美的解决了系统的拓展性
SSO核心接口的定义
依据抽象与多态的设计思想,根据SSO功能,我们定义出SSO的核心接口和基类:
- AuthManage,描述功能:用户合法性交互
/**
* 用户验证接口设计:功能设计关心用户验证,里面定义的方法主要是
* 用户登录,注册,注销
*/
public interface IAuthManager{
/**
* 用户注册
* @param username
* @param password
* @return
*/
AuthUser register(String username, String password) throws Exception;
/**
* 根据用户名密码登录系统
* @param username
* @param password
* @param appId
* @return
* @throws Exception
*/
SsoInfo login(String username, String password, Long appId) throws Exception;
/**
* 退出登录
*
* @param token
*/
void logout(String token) throws Exception;
}
- ITokenManager ,描述功能:用户令牌管理
/**
* SSO系统的令牌加密方式采用了JWT,在HTTP无状态式的交互方式下
* 携带加密Token令牌,主要描述的功能:对Token令牌的管理,缓存
* 以及合法性验证等
*/
public interface ITokenManager {
/**
* 根据token登录系统
* @param token
* @param appId
* @return
* @throws Exception
*/
SsoInfo loginToken(String token, Long appId) throws Exception;
/**
* 校验令牌是否合法
* @param token
* @return
* @throws Exception
*/
AuthUser checkToken(String token) throws Exception;
/**
* 添加用户授权信息到缓存中
* @param userId
* @param sessionInfo
* @throws Exception
*/
void put(Long userId, GxSessionInfo sessionInfo) throws Exception;
/**
* 移除缓存用户登录信息
* @param userId
* @throws Exception
*/
void remove(Long userId) throws Exception;
/**
* 根据用户ID获取缓存用户信息
* @param userId
* @return
* @throws Exception
*/
SessionInfo get(Long userId) throws Exception;
/**
* <p>验证token是否合法</p>
* <p>1.验证token合法性,先用core包中TokenHelper.verifyToken验证token合法性,取到用户id</p>
* <p>2.根据用户id获取缓存中对象,取到tokenMap用户令牌列表,并校验token是否与缓存列表中token一致</p>
* <p>3.tokenMap缓存规则需要保持一致,需要采用,key:TokenHelper.USER_ID + "_" + userId(用户总token);
* TokenHelper.APP_ID + "_" + appId(各站点token)</p>
* <p>4.默认采用本地token缓存形式,TokenManagerServiceImpl</p>
* @param token
* @return
* @throws Exception
*/
SessionInfo check(String token) throws Exception;
}
- AbstractAuthListener,描述功能:用户定制化监听任务
/**
* 用户定制化业务,定义两个接口,在用户登出前与登录后
* 用户想做的某些具体业务
*
*/
public abstract class AbstractAuthListener{
public AbstractAuthListener() {
ListenerBeanContainer.addListenerManager(this.getListenerBeanName(),this);
}
/**
* 注册监听bean,对实现类进行自定义命名
* @return String beanName
*/
protected abstract String getListenerBeanName();
/**
* <p>用户登出前干的一些事情</p>
*
* @param authUser 登录用户
*/
public void beforeLogout(GxAuthUser authUser) throws Exception {
}
/**
* <p>用户登录后干的一些事情</p>
*
* @param authUser 登录用户
*/
public void afterLogin(GxAuthUser authUser) throws Exception {
}
}
关于系统核心接口设计紧扣设计理念,对于具体的系统实现我们下次再说