什么是单点?
借用百度百科的话:
单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
单点登录图解:
什么是Oauth2.0?
OAuth 就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。
引用阮一峰老师的文章:OAuth 2.0 的四种方式 - 阮一峰的网络日志
正文:
前段时间在开发自己博客的时候,需要用到登录,当时的第一想法是做简单的密码验证就行了。不过为了熟悉一下SSO的流程,还是准备试用一下。
让我从SSO转变为Oauth的地方,是因为:SSO的限制,SSO需要在同一主域名下才能有效地写入cookie例如 www.baidu.com,如果试用SSO ,SSO的域名就需要是 ***.baidu.com。当然,不排除有其他方法可以实现cookie的写入。但正常的SSO流程就是需要在同一主域名下。
而Oauth2.0并没有限制,而且Oauth2.0更符合我对单点登录的认知。
开发流程:
1:授权系统开发
授权系统,这里其实就当做是一个最简单的登录验证器使用就行,有一个基础的增删改查就行
package com.hao.sso.vali.controller;
import java.util.concurrent.TimeoutException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.hao.bios.core.cache.ZboxCacheUtil;
import com.hao.bios.core.utils.StringUtil;
import com.hao.bios.core.utils.ZboxResult;
import com.hao.sso.vali.SsoConstants;
import com.hao.sso.vali.service.SsoLoginService;
import com.hao.sso.vali.vo.SsoLoginValiVO;
import com.hao.sso.vali.vo.ZboxSsoAuthPassVO;
import net.rubyeye.xmemcached.exception.MemcachedException;
@Controller
@RequestMapping("/sso/login")
public class LoginController {
@Autowired
private ZboxCacheUtil cache;
@Autowired
private SsoLoginService ssoLoginService;
@RequestMapping("/isLogin")
@ResponseBody
public boolean isLogin(HttpServletRequest req, String accessToken) {
if (StringUtil.isEmpty(accessToken)) {
return false;
}
ZboxSsoAuthPassVO authVO = cache.get(SsoConstants.LOGIN_AUTH_VALI_CACHE_POOL, accessToken, ZboxSsoAuthPassVO.class);
if (authVO == null) {
return false;
}
long outTime = authVO.getExpires();
long nowTime = System.currentTimeMillis();
if (outTime!=0 && nowTime > outTime) {
return false;
}
return true;
}
@RequestMapping("/login")
@ResponseBody
public String login(ServletRequest request, ServletResponse response, String userName, String pwd) throws TimeoutException, InterruptedException, MemcachedException {
HttpServletRequest req = (HttpServletRequest) request;
boolean isLogin = ssoLoginService.checkLogin(userName, pwd);
if (isLogin) {
SsoLoginValiVO valiVO = new SsoLoginValiVO();
String ip = getIpAddress(req);
valiVO.setIp(ip);
valiVO.setIsLogin(true);
valiVO.setLoginTime(StringUtil.getNowTimeString());
valiVO.setOutTime(0);
valiVO.setUserId(userName);
String key = StringUtil.getUUID();
cache.put(SsoConstants.LOGIN_VALI_CACHE_POOL, key, valiVO);
String sign = StringUtil.getUUID();
cache.put(SsoConstants.LOGIN_AUTH_CACHE_POOL, sign, key);
return sign;
}
return "";
}
@RequestMapping("/getAuthPass")
@ResponseBody
public ZboxResult getAuthPass(ServletRequest request, ServletResponse response, String sign) throws TimeoutException, InterruptedException, MemcachedException {
ZboxResult rul = new ZboxResult();
String key = cache.get(SsoConstants.LOGIN_AUTH_CACHE_POOL, sign, String.class);
if (StringUtil.isEmpty(key)) {
rul.setSuccess(false);
rul.setMessage("授权码无效");
return rul;
}
SsoLoginValiVO vali = cache.get(SsoConstants.LOGIN_VALI_CACHE_POOL, key, SsoLoginValiVO.class);
if (vali == null) {
rul.setSuccess(false);
rul.setMessage("未登录");
return rul;
}
if (vali.getIsLogin() == false) {
rul.setSuccess(false);
rul.setMessage("登陆失效");
return rul;
}
long outTime = vali.getOutTime();
long nowTime = System.currentTimeMillis();
if (outTime!=0 && nowTime > outTime) {
rul.setSuccess(false);
rul.setMessage("登陆超时");
return rul;
}
String accessToken = StringUtil.getUUID();
long expires = System.currentTimeMillis() + 60*60*1000L;
String refreshToken = StringUtil.getUUID();
//获取用户信息
String info = "";
ZboxSsoAuthPassVO authVO= new ZboxSsoAuthPassVO();
authVO.setAccessToken(accessToken);
authVO.setExpires(expires);
authVO.setInfo(info);
authVO.setRefreshToken(refreshToken);
authVO.setScope("all");
authVO.setTokenType("zbox_user_pass");
authVO.setUid(vali.getUserId());
cache.put(SsoConstants.LOGIN_AUTH_VALI_CACHE_POOL, accessToken, authVO);
rul.setSuccess(true);
rul.setData(authVO);
return rul;
}
@RequestMapping("/loginOut")
@ResponseBody
public boolean loginOut(ServletRequest request, String accessToken) throws TimeoutException, InterruptedException, MemcachedException {
ZboxSsoAuthPassVO authVO = cache.get(SsoConstants.LOGIN_AUTH_VALI_CACHE_POOL, accessToken, ZboxSsoAuthPassVO.class);
if (authVO != null) {
cache.put(SsoConstants.LOGIN_AUTH_VALI_CACHE_POOL, accessToken, null);
cache.delete(SsoConstants.LOGIN_AUTH_VALI_CACHE_POOL, accessToken);
}else {
return false;
}
return true;
}
public String getSsoCookie(HttpServletRequest req) {
Cookie[] cookies = req.getCookies();
if (cookies!=null) {
for (Cookie cookie : cookies) {
String name = cookie.getName();
if (SsoConstants.SSO_LOGIN_VALI_COOKIE_KEY.equals(name)) {
return cookie.getValue();
}
}
}
return "";
}
public String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("X-Forward-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("x-forwarded-for");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
具体其实就是几个接口:登录、注销、验证登录、授权令牌
2:业务系统接入
直接上逻辑流程:
1:用户访问业务系统
2:业务系统先验证本地cookie是否有效,
无效:直接返回前台未登录;
有效:访问认证系统,检查登录情况,未登录则返回业务系统,业务系统返回前台未登录
3:业务检查到未登录,跳转到认证系统登录页面进行登录,登录成功之后认证系统返回一个授权码
4:业务系统携带授权码,在后端使用双方协议的id和秘钥(授权系统给业务系统的一个账户密码)携带着授权码,去认证系统获取令牌。
5:业务系统拿到令牌后使用令牌访问认证系统的api。