sso

1、Client1-创建客户端client1项目(java web项目)

sso_第1张图片

对应pom.xml引入

  
    
      junit
      junit
      4.11
      test
    

    
      javax.servlet
      javax.servlet-api
      3.0.1
      provided
    

    
      org.springframework
      spring-webmvc
      4.2.3.RELEASE
    
  

2、Client1-新建LoginFilter文件,在这里doFilter方法中目前先直接放行

package com.sso.client1.filter;

import com.sso.client1.util.SSOClientUtil;

import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;


/**
 * @param
 * @Author wangbin
 * @description
 * @CreateDate 2019/6/21
 * @return
 */
public class LoginFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //直接放行
        chain.doFilter(request,response);
        return;
    }

    @Override
    public void destroy() {

    }
}

3、Client1-web.xml配置文件添加LoginFilter过滤器,拦截所有的请求





  Archetype Created Web Application

  
    loginFilter
    com.sso.client1.filter.LoginFilter
  
  
    loginFilter
    /*
  


4、Client1-添加一个index.jsp作为首页,直接放在webapp目录下(不要放WEB-INF目录)

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>



    
    客户端1


客户端1的首页

客户端1端口号为8011

点击下面按钮退出登录

退出

5、Client1-配置client1项目的tomcat,将http端口号设置为8011,为防止后面项目端口冲突,把JMX PORT端口号也改了

sso_第2张图片

6、Client1-启动项目,到此能正常启动,访问localhost:8011/index.jsp能正常看到页面即可

7、Client1-完善过滤器的执行方法,具体的流程步骤都写在代码注释里面,里面用到SSOClientUtil工具类去判断token是否有效,后面会放上,这里不应该注重是如何判断的,而是应该注重这里需要去判断

@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest)request;
        HttpServletResponse resp = (HttpServletResponse)response;
        //判断是否有局部会话
        HttpSession session = req.getSession();
        Object isLogin = session.getAttribute("isLogin");
        if(isLogin!=null && (Boolean) isLogin){
            //已经登录则直接放行访问页面
            chain.doFilter(request,response);
            return;
        }
        //未登录
        //判断是否有令牌(如果有则进行校验)
        String token = request.getParameter("token");
        if (token != null){
            //确认令牌token是否有效.
            Boolean verify = SSOClientUtil.verify(token, SSOClientUtil.getUrl(req,SSOClientUtil.CLIENTURL), session.getId());
            if (verify){
                //token有效,创建局部会话设置登录状态,并放行
                session.setAttribute("isLogin",true);
                chain.doFilter(request,response);
                return;
            }
        }
        //如果没有token,或者token无效
        //去认证中心验证是否有登录状态,可能其他系统已经登录了
        SSOClientUtil.redirectToSSOURL(req,resp);
    }

8、附上两个Util类

(1)SSOClientUtil.java

package com.sso.client1.util;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class SSOClientUtil {
	//统一认证中心地址(域名)
	public static final String SERVER_DOMAIN="http://localhost:8080";
	//统一认证中心检查是否已经登录的访问地址
	public static final String SERVER_CHECK_URL="/checkLogin?redirectUrl=";
	//统一认证中心的token认证地址
	public static final String SERVER_VERIFY_URL="/verify";
	//统一认证中心的登出地址
	public static final String SERVER_LOGOUT_URL="/logOut";
	//客户端的登出地址
	public static final String CLIENT_LOGOUT_URL="/logOut";
	//统一认证中心的token认证方法的token参数名
	public static final String TOKEN_NAME="token";
	//统一认证中心的token认证方法的登出地址参数名
	public static final String CLIENTURL="clientURL";
	//获取客户端重定向地址
	public static final String REDIRECT_URL="redirectURL";
	//统一认证中心的token认证方法的jsessionid参数名
	public static final String JSESSIONID="jsessionid";
	/**
	 * 获取客户端的登出地址 http://localhost:8011/logOut
	 * 获取客户端的重定向地址 http://localhost:8011/index.jsp
	 * 获取认证中心登出地址 http://localhost:8080/logOut
	 * @return
	 */
	public static String getUrl(HttpServletRequest request, String name){
		//获取请求协议,是http或者https
		String scheme = request.getScheme();
		//获取主机
		String host = request.getServerName();
		//获取端口号
		int port = request.getServerPort();
		//获取请求地址
		String servletPath = request.getServletPath();
		if(name != null && (CLIENTURL.equals(name)||name==CLIENTURL)){
			//返回客户端登出地址
			return scheme + "://" + host + ":" + port + CLIENT_LOGOUT_URL;
		}else if(name != null && (REDIRECT_URL.equals(name)||name==REDIRECT_URL)){
			//返回客户端重定向地址
			return scheme + "://"+host + ":"+port + servletPath;
		}else if(name != null && (SERVER_LOGOUT_URL.equals(name)||name==SERVER_LOGOUT_URL)){
			//返回认证中心登出地址
			String serverURL = SERVER_DOMAIN;
			String logOutURL = SERVER_LOGOUT_URL;
			return serverURL+logOutURL;
		}
		return "";
	}

	/**
	 * 根据request获取跳转到统一认证中心的地址
	 * http://localhost:8080/checkLogin?redirectUrl=http://localhost:8011/index.jsp
	 * 通过Response跳转到指定的地址
	 * @param request
	 * @param response
	 * @throws IOException
	 */
	public static void redirectToSSOURL(HttpServletRequest request,HttpServletResponse response) throws IOException{
		String serverURL = SERVER_DOMAIN;
		String checkURL = SERVER_CHECK_URL;
		String redirectUrl = getUrl(request,REDIRECT_URL);
		StringBuilder url = new StringBuilder(50);
		url.append(serverURL).append(checkURL).append(redirectUrl);
		response.sendRedirect(url.toString());
	}
	/**
	 * 验证token是否有效
	 * 如果有效把客户端的登出地址和jsessionid传递到统一认证中心,方便进行单点注销.
	 * @param token	
	 * @param clientURL
	 * @param jsessionid
	 * @return 
	 */
	public static Boolean verify(String token, String clientURL,String jsessionid) {
		String serverURL = SERVER_DOMAIN;
		String verifyURL = SERVER_VERIFY_URL;
		Map params = new HashMap();
		params.put(TOKEN_NAME, token);
		params.put(CLIENTURL, clientURL);
		params.put(JSESSIONID, jsessionid);
		try {
			//判断token是否有效,直接发送一个带上token参数的请求到认证中心
			//根据返回结果判断是否有效
			String responseContent = HttpUtil.sendHttpRequest(serverURL+verifyURL, params);
			if("true".endsWith(responseContent)){
				return true;
			}
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return false;
	}
}

(2)HttpUtil.java

package com.sso.client1.util;

import org.springframework.util.StreamUtils;

import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Map.Entry;

public class HttpUtil {
	/**
	 * 模拟浏览器的请求
	 * @param httpURL 发送请求的地址
	 * @param params  请求参数
	 * @return
	 * @throws Exception
	 */
	public static String sendHttpRequest(String httpURL,Map params) throws Exception{
		//建立URL连接对象
		URL url = new URL(httpURL);
		//创建连接
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		//设置请求的方式(需要是大写的)
		conn.setRequestMethod("POST");
		//设置需要响应结果
		conn.setDoOutput(true);
		//判断是否有参数.
		if(params!=null&¶ms.size()>0){
			StringBuilder sb = new StringBuilder();
			for(Entry entry:params.entrySet()){
				sb.append("&").append(entry.getKey()).append("=").append(entry.getValue());
			}
			//sb.substring(1)去除最前面的&
			conn.getOutputStream().write(sb.substring(1).toString().getBytes("utf-8"));
		}
		//发送请求到服务器
		conn.connect();
		//获取远程响应的内容.
		String responseContent = StreamUtils.copyToString(conn.getInputStream(),Charset.forName("utf-8"));
		conn.disconnect();
		return responseContent;
	}
	/**
	 * 模拟浏览器的请求
	 * @param httpURL 发送请求的地址
	 * @param jesssionId 会话Id
	 * @return
	 * @throws Exception
	 */
	public static void sendHttpRequest(String httpURL,String jesssionId) throws Exception{
		//建立URL连接对象
		URL url = new URL(httpURL);
		//创建连接
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		//设置请求的方式(需要是大写的)
		conn.setRequestMethod("POST");
		//设置需要响应结果
		conn.setDoOutput(true);
		conn.addRequestProperty("Cookie","JSESSIONID="+jesssionId);
		//发送请求到服务器
		conn.connect();
		conn.getInputStream();
		conn.disconnect();
	}
}

9、重启项目,访问localhost:8011/index.jsp,会检查到未登录,然后跳转到认证中心去检查是否有登录状态,这里因为还没写认证中心的项目,所有访问会出错,但是能看到已经跳转,并且带上了重定向地址

sso_第3张图片

10、到此已经完成client1项目登录的代码(还有部分登出注销的逻辑,后面补上)

11、同样的方法创建server项目,项目结构如下

sso_第4张图片

12、附上web.xml文件,以及pom.xml依赖部分、applicationContext.xml



  Archetype Created Web Application
  
    SpringMVC
    org.springframework.web.servlet.DispatcherServlet
    
      contextConfigLocation
      classpath:applicationContext.xml
    
    1
  
  
    SpringMVC
    /
  

    
      javax.servlet
      servlet-api
      2.5
      provided
    

    
      javax.servlet
      jstl
      1.1.2
    

    
      taglibs
      standard
      1.1.2
    

    
      org.springframework
      spring-webmvc
      4.2.3.RELEASE
    
  


	
	
	
	
		
		
	

13、LoginController的checkLogin方法、login方法、verity方法

package com.sso.server.controller;

import com.sso.server.util.HttpUtil;
import com.sso.server.util.SSOServerUtil;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpSession;
import java.util.*;

@Controller
public class LoginController {

    /**
     * 检查是否有登录状态
     * @param redirectUrl
     * @return
     */
    @RequestMapping("checkLogin")
    public String checkLogin(String redirectUrl,Model model,HttpSession session){
        //判断是否有其他系统登录过(判断是否有全局会话)
        Object token = session.getAttribute("token");
        if(token!=null){
            //重定向到应用系统,并且带上令牌
            model.addAttribute("token",token);
            return "redirect:"+redirectUrl;
        }
        model.addAttribute("redirectUrl",redirectUrl);
        //跳转到登录页面
        return "forward:/login.jsp";
    }

    /**
     * 登录表单提交的方法
     * @param redirectUrl
     * @return
     */
    @RequestMapping("login")
    public String login(String redirectUrl, String username, String password, Model model, HttpSession session){
        if(username.equals("admin")&&password.equals("1")){
            //登录成功
            //1.创建令牌
            String token = UUID.randomUUID().toString();
            //2.把令牌放到全局会话中
            session.setAttribute("token",token);
            //3.放到map容器中(只要存在该容器中的token都是合法的)
            SSOServerUtil.map.put(token,new ArrayList>());
            //把令牌带回到客户端
            model.addAttribute("token",token);
            //3.跳转到客户端
            return "redirect:"+redirectUrl;
        }else{
            //请求转发到登录页面
            model.addAttribute("errorMsg","登录失败!");
            model.addAttribute("redirectUrl",redirectUrl);
            return "forward:/login.jsp";
        }
    }

    /**
     * 校验令牌是否合法
     * @param token
     * @return
     */
    @RequestMapping("verify")
    @ResponseBody
    public String verify(String token,String clientURL,String jsessionid){
        //判断token是否存在map容器中,如果存在则代表合法
        List> clients = SSOServerUtil.map.get(token);
        if (clients!=null){
            //注册系统,系统的相关信息保存到list中
            Map map = new HashMap();//一个应用系统的注销地址和当前登录用户的sessionid
            map.put("clientURL",clientURL);
            map.put("jsessionid",jsessionid);
            clients.add(map);
            return "true";
        }else{
            return "false";
        }
    }

    /**
     * 注销
     */
    @RequestMapping("logOut")
    public String logOut(HttpSession session, Model model) throws Exception {
        //获取令牌
        Object token = session.getAttribute("token");
        //注销全局会话
        session.invalidate();
        List> clients = SSOServerUtil.map.remove(token);
        for (Map client : clients) {
            //取出每个系统,让他们注销指定的session
            //客户端注销的地址
            String clientURL = client.get("clientURL");
            //要注销的session
            String jsessionid = client.get("jsessionid");
            //通知每个应用系统注销局部会话(告诉他要注销哪个人的会话)
            HttpUtil.sendHttpRequest(clientURL,jsessionid);
        }
        model.addAttribute("errorMsg","退出登录!");
        model.addAttribute("redirectUrl","http://localhost:8080/logOut.jsp");
        return "forward:/logOut.jsp";
    }
}

14、还没写完

15、写篇博客真是不简单

16、源码丢了,改天再写一个?

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(java,sso,单点登录)