sso概念
什么是单点登录?单点登录全称Single Sign On(以下简称SSO),是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,包括单点登录与单点注销两部分 (即:一个账户登录访问多个子系统)
1. 登录模块说明
相比于单系统登录,sso需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,sso认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。这个过程,也就是单点登录的原理,如图:
2.注销模块说明
单点登录自然也要单点注销,在一个子系统中注销,所有子系统的会话都将被销毁,如图:
3.整体sso认证流程图
4.实现
sso采用客户端/服务端架构,先看sso-client与sso-server要实现的功能(sso认证中心=sso-server)
sso-client
1.拦截子系统未登录用户请求,跳转至sso认证中心
2.接收并存储sso认证中心发送的令牌
3.与sso-server通信,校验令牌的有效性
4.建立局部会话
5.拦截用户注销请求,向sso认证中心发送注销请求
6.接收sso认证中心发出的注销请求,销毁局部会话
sso-server
1.验证用户的登录信息
2.创建全局会话
3.创建授权令牌
4.与sso-client通信发送令牌
5.校验sso-client令牌有效性
6.系统注册
7.接收sso-client注销请求,注销所有会话
sso-server关键代码
@WebServlet("/login")
public class UserLoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private UserAccountService userAccountService = new UserAccountService();
/**
* 进入登录页
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获得原始请求的url并保存传递,当登录成功时,让浏览器再次跳转到这个url
String origUrl = req.getParameter("origUrl");
req.setAttribute("origUrl", origUrl);
req.getRequestDispatcher("/WEB-INF/view/login.jsp").forward(req, resp);
}
/**
* 登录表单提交时处理
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String account = request.getParameter("account");
String passwd = request.getParameter("passwd");
String origUrl = request.getParameter("origUrl");
// 按account查找用户
User user = null;
try {
user = userAccountService.findUserByAccount(account);
if (user != null) {
if (user.getPasswd().equals(passwd)) { // 判断密码是否正确
// 1 生成token
String token = KeyGenerator.generate();
// 2 将token user存储到全局唯一数据结构中
TokenUserData.addToken(token, user);
// 3 写cookie JVM
Cookie tokenCookie = new Cookie("token", token);
tokenCookie.setPath("/");
// tokenCookie.setDomain(DOMAIN);
tokenCookie.setHttpOnly(true);
response.addCookie(tokenCookie);
// 4 跳转到原请求
if (StringUtil.isEmpty(origUrl)) {
origUrl = "login_success";
} else {
origUrl = URLDecoder.decode(origUrl, "utf-8");
}
response.sendRedirect(origUrl);
} else {
backToLoginPage(request, response, account, origUrl, "密码不正确");
}
} else { // 用户不存在
backToLoginPage(request, response, account, origUrl, "用户不存在");
}
} catch (SQLException e) {
e.printStackTrace();
backToLoginPage(request, response, account, origUrl, "发生系统错误!");
}
}
/**
* 登录错误返回的信息
*
* @param req
* @param resp
* @param account
* @param origUrl
* @param errInfo
* @throws ServletException
* @throws IOException
*/
private void backToLoginPage(HttpServletRequest req, HttpServletResponse resp, String account, String origUrl,
String errInfo) throws ServletException, IOException {
req.setAttribute("account", account);
req.setAttribute("origUrl", origUrl);
req.setAttribute("errInfo", errInfo);
req.getRequestDispatcher("/WEB-INF/view/login.jsp").forward(req, resp);
}
sso-client关键代码
public class SSOFilter implements Filter {
// SSO Server登录页面URL
private static final String SSO_LOGIN_URL = "/server/login",
SSO_VALIDATE_URL = "http://localhost:8080/server/validate";
// 拦截操作
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
// 从请求中提取token
String token = CookieUtil.getCookie(req, "token");
// 本次请求的完整路径
String origUrl = req.getRequestURL().toString();
String queryStr = req.getQueryString();
if (queryStr != null) {
origUrl += "?" + queryStr;
}
// token 不存在,跳转到SSOServer用户登录页
if (token == null) {
resp.sendRedirect(SSO_LOGIN_URL + "?origUrl="
+ URLEncoder.encode(origUrl, "utf-8"));
} else { // token存在,验证有效性
URL validateUrl = new URL(SSO_VALIDATE_URL + "?token=" + token);
HttpURLConnection conn = (HttpURLConnection) validateUrl
.openConnection();
conn.connect();
InputStream is = conn.getInputStream();
byte[] buffer = new byte[is.available()];
is.read(buffer);
String ret = new String(buffer);
if (ret.length() == 0) { // 返回空字符串,表示 token无效
resp.sendRedirect(SSO_LOGIN_URL + "?origUrl="
+ URLEncoder.encode(origUrl, "utf-8"));
} else {
String[] tmp = ret.split(";");
User user = new User();
for (int i = 0; i < tmp.length; ++i) {
String[] attrs = tmp[i].split("=");
switch (attrs[0]) {
case "id":
user.setId(Integer.parseInt(attrs[1]));
break;
case "name":
user.setName(attrs[1]);
break;
case "account":
user.setAccount(attrs[1]);
break;
}
}
request.setAttribute("user", user);
chain.doFilter(request, response);
}
}
}
参考说明:http://www.importnew.com/22863.html?replytocom=541115#respond
极客学院