Single-Sign-On即单点登录,从一个页面登录,就可以访问其他互信系统,避免用户重新登录,例如登录了百度,则百度云等其他百度旗下其他网站均可免登录访问。在整个业务服务群中起到了辅助、集成的作用。
SSO体系结构
-
客户端调用模块
-
认证中心
认证中心是SSO系统的核心,它先验证用户是否有权限访问系统,如果有让用户访问系统资源;如果没有,提供一个登录页面。
-
用户管理系统和用户数据获取接口
用于为认证中心提供用户验证数据支持
-
令牌 token
用户验证期间,生成token,保存,如果用户再次登入其他授信系统,则直接验证token信息是否正确以确定用户是否进入资源访问,无需第二次重复登录。
-
整体流程:客户端调用应用系统,通过同一的认证中心进行信息验证,认证中心通过访问数据库接口,进行用户信息核对,生成token允许用户访问应用系统资源。
SSO认证流程
-
获取唯一标识token
-
验证该标识是否存在且有效,若不存在,或者token无效,均重定向到指定登录页,生成token;
-
再次验证,判断用户是否进入系统
SSO系统的必要模块
例如创建一个user表及其对应实体,SSOServer 代码演示,创建服务端loginServlet,用于获取request用户信息、认证用户、生成token操作
- @WebServlet("/login")
- public class UserLoginServlet extends HttpServlet{
- private static final long serialVersionUID = 1L;
- private UserAccountService accountService;
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- //获得原始请求的URL并保存传递,登录成功时,让浏览器再次跳转到该URL
- String origURL =req.getParameter("origURL");
- req.setAttribute("origURL", origURL);//目的在于在整个会话中,把原始的请求地址保存到req中
- req.getRequestDispatcher("WEB-INFO/view/login.jsp").forward(req, resp);
- }
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- String account=req.getParameter("account");
- String password=req.getParameter("password");
- String origURL=req.getParameter("origURL");
- //按照account查找用户
- User user=null;
- try{
- user=accountService.findUserByAccout(account);
- if(user!=null)
- {
- if(user.getPassword()==password)//.equals(password)
- {
- //1、生成token java UUID随机生成
- String token=UUID.randomUUID().toString().replace("-", "").toLowerCase();
- //2、将token存到全局唯一数据结构中
- TokenUserData.addToken(token, user);
- //3、写入cookie
- Cookie tokenCookie=new Cookie("token", token);
- tokenCookie.setPath("/");//localhost:8091 同一个域,不同路径;
- tokenCookie.setHttpOnly(true);
- resp.addCookie(tokenCookie);
- //4、跳转请求
- if(origURL!=null)
- {
- origURL="login_success";
- }else{
- origURL=URLDecoder.decode(origURL,"utf-8");
- }
- resp.sendRedirect(origURL);
- }else
- {
- backToLoginPage(req, resp, account, origURL, "密码不正确");
- }
- }else
- {
- this.backToLoginPage(req, resp, account, origURL, "用户不存在");
- }
- }catch(Exception e)
- {
- e.printStackTrace();
- backToLoginPage(req, resp, account, origURL, "系统错误");
- }
- }
- private void backToLoginPage(HttpServletRequest req,
- HttpServletResponse resp, String account, String origURL,
- String errInfo) throws ServletException, IOException {
- req.setAttribute("origURL", origURL);
- req.setAttribute("account", account);
- req.setAttribute("errInfo", errInfo);
- req.getRequestDispatcher("/WEB-INF/view/login.jsp").forward(req, resp);
- }
SSO客户端模块Filter
- public class SSOFilter implements Filter {
- // SSO Server登录页面URL
- private static final String SSO_LOGIN_URL = "/server/login",
- SSO_VALIDATE_URL = <a target=_blank href="http://localhost:8080/server/validate">http://localhost:8080/server/validate</a>;
- // 拦截操作
- @Override
- public void doFilter(ServletRequest request, ServletResponse response,
- FilterChain chain) throws IOException, ServletException {
- HttpServletRequest req = (HttpServletRequest) request;
- HttpServletResponse resp = (HttpServletResponse) response;
- // 从请求中提取token-已从服务端将token信息写入cookie
- 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);
- }
- }
- }
客户端调用模块:直接在客户端的web.xml中配置上面SSOClient的Filter,对所有的jsp页面进行拦截;而SSOClient通过从cookie获取服务器写入的token进行认证;并保留生成的token信息,实现单点登录。
- <filter>
- <filter-name>sso</filter-name>
- <filter-class>demo.sso.client.SSOFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>sso</filter-name>
- <url-pattern>*.jsp</url-pattern>
- </filter-mapping>