1、SSO简介
单点登录(SingleSignOn,SSO),就是通过用户的一次性鉴别登录。当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。
2、JSON Web Token
传统的认证是采用session会话机制,存在以下的缺点:
1)用户信息存储在内存中,用户规模大之后增加服务器开销;
2)由于登录信息存储在内存中,限制了登录机器,不利于分布式站点。
3)多个服务需要采用全局缓存或数据库做存储。
所以在我们的SSO系统中采用了JSON Web Token(JWT)令牌的方式来进行用户认证。
它的优点是任一子系统用户登录成功时创建token令牌,各子系统共用token令牌,只要携带的token令牌在SSO系统认证通过,表示该用户访问子系统合法。
详解请访问:JSON Web Token(JWT)
3、模拟域名
编辑C:\Windows\System32\drivers\etc\hosts文件,添加以下内容
127.0.0.1 www.sso.com
127.0.0.1 www.web.com
配置sso系统端口号为8080,使用 http://www.sso.com:8080 域名对sso系统进行访问;
配置web系统端口号为8081,使用 http://www.web.com:8081 域名对web系统进行访问。
4、项目结构
(1)sso工程,单点登录系统
主要实现了用户登录、创建token、校验token、把token发送到web系统。
(2)sso-client工程,sso客户端jar包
业务系统访问单点登录系统的公共代码封装,业务系统需要引用该jar包。
(3)web工程,业务系统
业务系统负责拦截需要登录权限url,并向sso系统认证token合法性。
5、主要流程
(1)登录流程
1.web系统拦截器,先从request请求中获取token进行认证,认证成功进入步骤2,若认证失败进入步骤3。
2.将token保存到局部cookie中,拦截器返回true,进入业务代码,结束。
3.从局部cookie中获取token进行认证,若成功进入步骤4,若失败进入步骤5。
4、拦截器返回true,进入业务代码,结束。
5.携带returnUrl重定向到获取sso登录页面,sso从全局cookie获取token,认证成功进入步骤6,认证失败进入步骤7。
6.携带token重定向到returnUrl,进入步骤1。
7.跳转sso登录页面,提交用户密码,验证用户登录信息,若成功进入步骤8,若失败重新进入步骤7。
8.创建token,并将token保存到全局cookie和redis,携带token重定向到returnUrl,进入步骤1。
名词解释:
局部cookie:业务系统对应的cookie。
全局cookie:sso系统对应的cookie。
流程图
(2)注销流程
用户向web系统发出注销请求,web系统删除局部cookie中的token后,重定向到sso系统注销地址,sso系统删除全局cookie和redis中的token。
流程图
6、主要代码
1.SSO系统的主要实现类 SsoController
/**
* @author : alex
* @date : 2020/1/28
*/
@Controller
public class SsoController {
@Autowired
RedisUtil redisUtil;
/**
* 验证token是否合法
*
* @param request
* @param response
* @return
*/
@RequestMapping(value = "/verifyToken", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
@ResponseBody
public Result verifyToken(HttpServletRequest request, HttpServletResponse response) {
String tokenParam = request.getParameter("token");
if (verifyToken(tokenParam)) {
delayToken(tokenParam);
return Result.buildIsOk("success");
}
return Result.buildIsFail("fail");
}
/**
* 跳转登录页
*
* @param request
* @return
* @throws Exception
*/
@RequestMapping(value = "/login")
public ModelAndView login(HttpServletRequest request, ModelAndView modelAndView) {
String token = getCookies(Constants.TOKEN_COOKIE_KEY, request);
String returnUrl = request.getParameter("returnUrl");
if (verifyToken(token) && !StringUtils.isEmpty(returnUrl)) {
returnUrl = returnUrl + "?token=" + token;
modelAndView.setViewName("redirect:" + returnUrl);
} else {
//TODO 删除错误的token
modelAndView.addObject("returnUrl", returnUrl);
modelAndView.setViewName("login");
}
return modelAndView;
}
/**
* 用户登录
*
* @param request
* @param response
* @param modelAndView
* @return
*/
@RequestMapping(value = "/signIn")
public ModelAndView signIn(HttpServletRequest request, HttpServletResponse response, ModelAndView modelAndView) {
String returnUrl = request.getParameter("returnUrl");
String userName = request.getParameter("userName");
String password = reques