Java_Springmvc+Hibernate5+JWT+Redis实现SSO跨域单点登录

首先,在搞代码之前,我们要了解什么是JWT?

这个一开始我也不太明白,后来经过百度阅读看了很多文章才明白,其实JWT就像一个工具,可以对字符串进行签名(加密)和解签(解密)以及验证字符串。这里只是简单的对JWT主要功能描述一下就行,更具体的了解JWT需要自行百度。

第二:Token为什么要存放在Redis?存储格式又是怎么样的?

当用户登录成功时,将Token放在Redis,同时设置该Redis key的过期时间 = JWT Token过期时间,那么等这个存放某Token的key过期之后,Redis会自动删除这个Redis key(那我就不用刻意的维护Token)。

第三:SSO登陆逻辑(这是根据自己的实际项目绘制的逻辑图,若有问题勿喷。嘿嘿!)

 

 

SSO注销登录逻辑:

 

效果展示:

1.子系统A点击登录:

SSO验证登录成功后,将token存到Redis,如图示例:

然后SSO使用ajax(jsonp)发送命令给所有子系统,让他们添加token到本地Cookie中,然后重定向回子系统

如图:登陆成功

查看Cookie有没有Token?

子系统A(10.1.8.12)

子系统B(10.1.0.192);

以上是在子系统A(10.1.8.12)上完成了登录成功,根据SSO的定义,那么,我访问子系统B(10.1.0.192)就应该是免登录的,接下来验证一下,访问子系统B(10.1.0.192)

可以看到,在子系统A上登录之后,在子系统B上就免登陆了,也就是成功实现了跨域SSO单点登陆

功能演示到此完毕。接下来看看我是如何实现SSO跨域单点登录的。

 

 

1.子系统访问受保护资源,子系统拦截器拦截请求,如下:


public class LoginFilter implements Filter {

	private final Gson gson = new Gson(); 
	String NBC_SSO_LOGIN_URL = PropertiesUtils.getProperty("NBC_SSO_SERVER_LOGIN_URL");
	String TokenLogin_HttpURL = PropertiesUtils.getProperty("NBC_SSO_SERVER_HTTPURL_FOR_TOKEN_CHECK");
	String NBC_USER_TOKEN = PropertiesUtils.getProperty("NBC_SSO_CLIENT_COOKIENAME_FOR_TOKEN");
	String NBC_SSO_CLIENT_COOKIE_TIMEOUT = PropertiesUtils.getProperty("NBC_SSO_CLIENT_COOKIE_TIMEOUT");
	
	 
	 
	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
			throws IOException, ServletException {

		System.out.println("============   doFilter LoginFilter   ============");
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) resp;   
	    HttpSession session = request.getSession();
	    ServletContext application = session.getServletContext();
	    boolean IsLogin = false;
	    Object UID = session.getAttribute("UID");
	    if(UID!=null && application.getAttribute(UID.toString())!=null && application.getAttribute(UID.toString()).equals(session.getId())) {
	    	IsLogin = true;
	    } 
	    if(UID!=null) {
	    	System.err.println("=============application uid:"+application.getAttribute(UID.toString()));
	    }
	    System.err.println("IsLogin:"+IsLogin);
	    //已登陆则放行
	    if(IsLogin) {
	    	chain.doFilter(request, response);
	    }else {
	    	//校验配置文件
		    boolean error = false;
			StringBuffer errorInfo = new StringBuffer("");;
			String errorURL =  request.getContextPath() +"/error/commonError"; 
		    String[] arr1 = new String[] {NBC_USER_TOKEN,NBC_SSO_LOGIN_URL,NBC_SSO_CLIENT_COOKIE_TIMEOUT};
		    String[] arr2 = new String[] {"NBC_SSO_CLIENT_COOKIENAME_FOR_TOKEN","NBC_SSO_SERVER_LOGIN_URL","NBC_SSO_CLIENT_COOKIE_TIMEOUT"};
		    for(int i=0;i<arr1.length;i++) {
		    	if(arr1[i]==null || arr1[i].equals("")) {
		    		error = true;
		    		errorInfo.append(errorInfo.toString().trim().equals("")?arr2[i]:("※"+arr2[i]));
		    	}
		    } 
		    if(error) {
		    	session.setAttribute("error", errorInfo);
		    	response.sendRedirect(errorURL);
		    } 
		    //配置SSO登陆链接
		    String returnRUL = request.getRequestURL().toString();
			String ssoLoginUrl = "";
			try {
				if(returnRUL!=null && !returnRUL.equals("")) {
					returnRUL = URLEncoder.encode(returnRUL, "UTF-8");
					if(NBC_SSO_LOGIN_URL.indexOf("?")>0) {
						ssoLoginUrl = NBC_SSO_LOGIN_URL+"&returnRUL="+returnRUL;
					}else {
						ssoLoginUrl = NBC_SSO_LOGIN_URL+"?returnRUL="+returnRUL;
					} 
				}
				 
			}catch(Exception e) {
				e.printStackTrace();
			}
			
			System.err.println("登陆拦截");
			//若请求中有token参数,则校验token有效性
		    String token = req.getParameter("token");
		    if(token!=null && !token.equals("")) {
		    	SSOCheckResult checkResult = SsoCheck.checkToken(token, TokenLogin_HttpURL, request, response);
		    	System.err.println("tokenOK 0 ?"+checkResult.isSuccess());
		    	System.err.println(gson.toJson(checkResult));
		    	if(checkResult.isSuccess()) {
		    		session.setAttribute("IsLogin",true); 
		    		NBC_ID_UsersVO user = checkResult.getUser();
		    		if(user!=null) {
		    			System.err.println("校验Cookie token,token有效,返回用户信息userInfo:"+gson.toJson(user));
		    			String fuserId = String.valueOf(user.getFuserID());
		    			session.setAttribute("UID", fuserId);  
		    			session.setAttribute("UserName",user.getUserName_CHS() );  
		    			application.setAttribute(fuserId, session.getId());
		    		}
		    		chain.doFilter(request, response); 
		    	}else {
		    		CookieUtils.clearCookie(NBC_USER_TOKEN, request, response); 
		    		response.sendRedirect(ssoLoginUrl);
		    	}
		    }else { 
		    	SSOCheckResult checkResult = SsoCheck.checkToken(null, TokenLogin_HttpURL, request, response); 
		    	
		    	System.err.println("tokenOK 1 ?"+checkResult.isSuccess());
		    	System.err.println(gson.toJson(checkResult));
		    	
		    	
		    	if(checkResult.isSuccess()) { 
		    		token = checkResult.getToken();
		    		session.setAttribute("IsLogin",true); 
		    		NBC_ID_UsersVO user = checkResult.getUser();
		    		if(user!=null) {
		    			System.err.println("校验Cookie token,token有效,返回用户信息userInfo:"+gson.toJson(user));
		    			String fuserId = String.valueOf(user.getFuserID());
		    			session.setAttribute("UID", fuserId);  
		    			session.setAttribute("UserName",user.getUserName_CHS() );  
		    			application.setAttribute(fuserId, session.getId());
		    		}
//		    		boolean existCookie = CookieUtils.existCookie(NBC_USER_TOKEN,request);
//		    		if(!existCookie) {
//		    			CookieUtils.addCookie(NBC_USER_TOKEN, token, Integer.parseInt(NBC_SSO_CLIENT_COOKIE_TIMEOUT) , request, response);
//		    		} 
		    		chain.doFilter(request, response);
		    	}else {
		    		//clear cookie and redirect to login 
		    		response.sendRedirect(ssoLoginUrl);
		    	} 
		    }
	    }
	    
		 

	}

	@Override
	public void destroy() {
		System.out.println("============   Destroy Filter LoginFilter   ============");
	}

	@Override
	public void init(FilterConfig arg0) throws ServletException {
		System.out.println("============   Init Filter LoginFilter   ============");
	}
 
}

以上拦截器中涉及到使用HttpURLConnection访问SSO校验Token有效性。SsoCheck.java


public class SsoCheck {
	
	private static final Gson gson = new Gson();
	private static String NBC_USER_TOKEN = PropertiesUtils.getProperty("NBC_SSO_CLIENT_COOKIENAME_FOR_TOKEN");
	
	public static SSOCheckResult checkToken(String token, String httpURL,HttpServletRequest request, HttpServletResponse response) {
		System.err.println("=============== SSOCheckResult checkToken ===============");
		String clientIp = ComputerInfoUtil.getIP(request);  
		SSOCheckResult result = new SSOCheckResult(false,500,"未知错误",null,token);
		//如果不存在token参数,则在Cookie中查找
		if(token==null || token.equals("")) {
			//Cookie中是否有Token
	    	Cookie[] cookies = request.getCookies(); 
	    	if(cookies!=null && cookies.length>0) { 
	    		for(Cookie cookie : cookies) { 
		    		if(cookie.getName().equals(NBC_USER_TOKEN)) {
		    			token = cookie.getValue();
		    		} 
		    	}
	    	} 
		} 
    	if(token==null || token.equals("")) {
			return new SSOCheckResult(false,404,"参数错误[token]");
		}
    	result.setToken(token);
		String encoding = "UTF-8";
		String returnURL = request.getRequestURL().toString();
		try {
			//创建URL对象
			URL url = new URL(httpURL);
			//创建连接 
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			// true -- will setting parameters
			conn.setDoOutput(true);
			// true--will allow read in from
			conn.setDoInput(true);
			// will not use caches
			conn.setUseCaches(false);
			// setting serialized
			//conn.setRequestProperty("Content-type", "application/x-java-serialized-object");
			conn.setRequestProperty("Content-type", "application/json; charset=UTF-8");
			conn.setRequestProperty("Charsert", "UTF-8"); 
			conn.setRequestMethod("POST");// default is GET   
			conn.setRequestProperty("connection", "Keep-Alive");
			conn.setRequestProperty("Charsert", "UTF-8"); 
			// 1 min
			conn.setConnectTimeout(60000);
			// 1 min
			conn.setReadTimeout(60000); 
			//send args
			conn.addRequestProperty("token",(token==null)?"":URLEncoder.encode( token, "UTF-8")); 
			conn.addRequestProperty("clientIp",(clientIp==null)?"":URLEncoder.encode( clientIp, "UTF-8")); 
			conn.addRequestProperty("returnURL",returnURL==null?"":URLEncoder.encode( returnURL, "UTF-8"));  
			// connect to server (tcp)
			conn.connect(); 
			//获得响应码
		    int code = conn.getResponseCode();//200代表Http OK 
		    String TokenOK = (conn.getHeaderField("TokenOK")==null)?"":conn.getHeaderField("TokenOK"); 
		    String userInfo = (conn.getHeaderField("userInfo")==null)?"":conn.getHeaderField("userInfo"); 

		    result.setErrorCode(code);
		    if(code==HttpURLConnection.HTTP_OK && TokenOK.equals("true")){ 
		    	userInfo = userInfo.equals("")?userInfo:URLDecoder.decode(userInfo, encoding);
		    	userInfo = userInfo.replace("\"", "").replace("\\", "\"");
		    	
		    	NBC_ID_UsersVO user = new NBC_ID_UsersVO();
		    	if(!userInfo.equals("")) { 
		    		user = gson.fromJson(userInfo, NBC_ID_UsersVO.class); 
		    	}
		    	result = new SSOCheckResult(true,0,"",user,token);
			} 
			//关闭连接 
			conn.disconnect();
		}catch(Exception e) {
			e.printStackTrace();
			result.setSuccess(false);
			result.setErrorCode(500);
			result.setErrorMsg("服务器异常:"+e.getMessage()); 
		}
		return result;
	}

}

好了,子系统的逻辑已经处理好,接下来就差SSO端来校验子系统发出的Http校验Token请求了。

那么接下来,需要创建SSO认证中心项目。

创建springboot项目,pom.xml主要引入JWT、Jedis、HttpClient等依赖,以下是我的pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.0.BUILD-SNAPSHOT</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	
	<packaging>war</packaging>
	<groupId>com.nbc</groupId>
	<artifactId>com.nbc</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>NBC_SSO</name>
	<description>Spring Boot SSO Demo</description>

	<properties>
		<java.version>1.8</java.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <activiti.version>5.23.0-RC1</activiti.version><!--5.22.0 / 5.23.0-RC1 -->
        <codehaus.jackson.version>1.9.4</codehaus.jackson.version> 
        <gson.version>2.8.2</gson.version>
        <c3p0.version>0.9.5.2</c3p0.version>
        <fasterxml.jackson.version>2.7.5</fasterxml.jackson.version>
        <hibernate.version>5.1.0.Final</hibernate.version>
        <hibernate.core.version>5.2.0.Final</hibernate.core.version>  
        <hibernate.ehcache.version>5.2.0.Final</hibernate.ehcache.version>
        <hibernate.annotations.version>5.1.0.Final</hibernate.annotations.version>  
        <hibernate.jpa.api.version>1.0.1.Final</hibernate.jpa.api.version> 
        <httpclient.version>4.5.2</httpclient.version>
        <juniversalchardet.version>1.0.3</juniversalchardet.version>
        <jedis.version>2.9.0</jedis.version>
        <junit.version>4.12</junit.version>  
        <jstl.version>1.2</jstl.version>
        <jwt.version>3.4.0</jwt.version>
        <jackson.version>2.9.9</jackson.version>
        <net.sf.json.version>2.4</net.sf.json.version>
        <org.json.version>20190722</org.json.version>
        <poi.version>3.12</poi.version>
        <pdf2dom.version>1.7</pdf2dom.version>
        <pdfbox.version>2.0.12</pdfbox.version>
        <spring.version>5.2.0.BUILD-SNAPSHOT</spring.version><!--4.3.24.RELEASE  5.2.0.BUILD-SNAPSHOT -->
        <sqlserver.version>4.0</sqlserver.version>
        <servlet.api.version>4.0.0</servlet.api.version>
        <xdocreport.version>1.0.5</xdocreport.version>
	</properties>

	<dependencies>
	     <!-- 单元测试 -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<scope>test</scope>
		</dependency>
		
		<!-- gson -->
		<dependency>
			<groupId>com.google.code.gson</groupId>
			<artifactId>gson</artifactId>
			<version>${gson.version}</version>
		</dependency>
		<!-- JSONObject-->
		<dependency>
			<groupId>org.json</groupId>
			<artifactId>json</artifactId>
			<version>${org.json.version}</version>
		</dependency>
		<!-- jackson -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-core</artifactId>
			<version>${jackson.version}</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>${jackson.version}</version>
		</dependency>
		
		<!-- pdfbox -->
		<dependency>
            <groupId>net.sf.cssbox</groupId>
            <artifactId>pdf2dom</artifactId>
            <version>${pdf2dom.version}</version> 
        </dependency>
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>pdfbox</artifactId>
            <version>${pdfbox.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>pdfbox-tools</artifactId>
            <version>${pdfbox.version}</version>
        </dependency>
        
		<!-- https://mvnrepository.com/artifact/redis.clients/jedis --> 
		<dependency>
		    <groupId>redis.clients</groupId>
		    <artifactId>jedis</artifactId>
		    <version>${jedis.version}</version>
		</dependency> 
		 
		  
        
        
		<!-- net.sf.json -->
		<dependency>
			<groupId>net.sf.json-lib</groupId>
			<artifactId>json-lib</artifactId>
			<version>${net.sf.json.version}</version>
			<classifier>jdk15</classifier>
		</dependency>
		
		<!-- jwt 官网依赖 -->
		<dependency>
			<groupId>com.auth0</groupId>
			<artifactId>java-jwt</artifactId>
			<version>${jwt.version}</version>
		</dependency>
		<!-- httpclient -->
        <dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
			<version>${httpclient.version}</version>
		</dependency>
		<!-- lombok -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		
		<!-- sqlserver -->
		<dependency>
			<groupId>com.microsoft.sqlserver</groupId>
			<artifactId>sqljdbc4</artifactId>
			<version>${sqlserver.version}</version>
		</dependency>
		
		<!-- c3p0 有自动回收空闲连接功能 -->
		<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
		<dependency>
			<groupId>com.mchange</groupId>
			<artifactId>c3p0</artifactId>
			<version>${c3p0.version}</version>
		</dependency>
		
		<!-- spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>   
        
		<!-- hibernate --> 
		<dependency>
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-core</artifactId>
		    <version>${hibernate.version}</version>
		</dependency>
		<!-- for JPA, use hibernate-entitymanager instead of hibernate-core -->
		<dependency>
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-entitymanager</artifactId>
		    <version>${hibernate.version}</version>
		</dependency> 
		<dependency>
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-osgi</artifactId>
		    <version>${hibernate.version}</version>
		</dependency>
		<dependency>
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-envers</artifactId>
		    <version>${hibernate.version}</version>
		</dependency>
		<dependency>
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-c3p0</artifactId>
		    <version>${hibernate.version}</version>
		</dependency>
		<dependency>
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-proxool</artifactId>
		    <version>${hibernate.version}</version>
		</dependency>
		<dependency>
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-infinispan</artifactId>
		    <version>${hibernate.version}</version>
		</dependency>
		<dependency>
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-ehcache</artifactId>
		    <version>${hibernate.version}</version>
		</dependency> 
		
		
		<!-- jsp相关 -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>${servlet.api.version}</version>
			<scope>provided</scope>
		</dependency> 
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>${jstl.version}</version>
		</dependency>
		<dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jsp-api</artifactId>
        </dependency>
        
        
        <!--  spring-boot -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
	    <dependency>
		   <groupId>org.springframework.boot</groupId>
		   <artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<build>
	    <finalName>nbs-sso</finalName>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
		</repository>
		<repository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
		</pluginRepository>
		<pluginRepository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</pluginRepository>
	</pluginRepositories>

</project>

相关依赖自行拷贝到你的项目。

如何整合Springmvc和Hibernate这个我就不多说,自己搞(因为我的SSO已经在用了,只能展示部分核心代码)。当然,需要完整的SSO代码也可以联系我,但要有偿喔(Q:2225629171)

 

SSO登陆模块:

@RequestMapping(value="/login",method=RequestMethod.GET)
	public String to_login(Model model,HttpServletRequest req,HttpSession session) {  
		System.err.println("================ SSO Login =============="); 
		String returnURL = "";
		try {
			returnURL = req.getParameter("returnURL");
			if(returnURL!=null && !returnURL.equals("")) {
				returnURL = URLDecoder.decode(returnURL, "UTF-8");
			} 
		}catch(Exception e) {
			e.printStackTrace();
		}
		System.err.println("returnURL:"+returnURL);
		model.addAttribute("user",new z_CF_UsersVO());  
		model.addAttribute("returnURL",returnURL);  
		return "login";
	}
	
	@RequestMapping(value="/login",method=RequestMethod.POST,produces = "application/json; charset=utf-8")
	@ResponseBody
	public String do_login(Model model,HttpServletRequest req,HttpServletResponse resp,HttpSession session) 
					throws Exception {
		JSONObject json = new JSONObject();
		String sessionId = session.getId();
		String ipv4 = ComputerInfoUtil.getIP(req);
		String login_method = req.getParameter("login_method");
		String username = req.getParameter("username");
		String password = req.getParameter("password");
	
		
		//1.校验参数
		System.err.println("执行校验参数");
		String[] params = new String[] {login_method,username,password};
		boolean paramOK = loginService.checkParam(params);
		if(!paramOK) { 
			json.put("success", false);
			json.put("info", "参数错误:"+gson.toJson(params));
			return json.toString();
		}
		//2.执行登陆校验
		PublicModel<LoginResult> result = loginService.login(sessionId,ipv4,login_method,username,password);
		if(!result.isSuccess()) {
			json.put("success", false);
			json.put("info", "执行登陆校验时发生异常:"+result.getErrorMsg());
			return json.toString();
		}
		LoginResult loginResult = result.getObj();
		if(loginResult==null) {
			json.put("success", false);
			json.put("info", "执行登陆校验时发生异常:loginResult=null");
			return json.toString();
		} 
		System.err.println("loginResult.getCode():"+loginResult.getCode());
		if(!String.valueOf(loginResult.getCode()).equals(String.valueOf(LoginCode.First_Login)) && !String.valueOf(loginResult.getCode()).equals(String.valueOf(LoginCode.Permit_Login))) {
			System.err.println("========= 返回错误信息 ==========");
			json.put("success", true);
			json.put("loginResult", gson.toJson(loginResult));
			return json.toString();
		}
		//登陆有效  
		//判断Redis服务是否已启动
		System.err.println("查询Redis服务是否已启动");
		boolean IsStarted = jedisClient.IsRedisStarted();
		if(!IsStarted) { 
			json.put("success", false);
			json.put("info", "Redis服务未启动,请及时联系管理员,谢谢您的配合!");
			return json.toString();
		} 
		System.err.println("Redis服务已启动");
		//获取新的用户登陆信息
		z_CF_UsersVO currentUser = new z_CF_UsersVO();//将部分信息封装返回
		currentUser.setFuserID(loginResult.getFuserId());
		currentUser.setUserName_EN(loginResult.getUserName_EN());
		currentUser.setUserName_CHS(loginResult.getUserName_CHS()); 
		Map<String,String> claims = new HashMap<String,String>();
		claims.put("fuserId", String.valueOf(loginResult.getFuserId()));
		claims.put("userInfo",gson.toJson(currentUser));
		String newToken =  JwtUtil.genToken(claims,Long.parseLong(NBC_SSO_SERVER_JWT_TOKEN_EXPIRE));
		currentUser.setToken(newToken);
		
		//注销该用户在其他地方的登录信息
		System.err.println("注销该用户旧的登录信息");
		String key = "";
		String old_userInfo = "";
		try {
			if(currentUser!=null) { 
				key = NBC_SSO_SERVER_JEDIS_KEY_PREFIX+String.valueOf(currentUser.getFuserID());
				if(key!=null && !key.equals("")) {
					old_userInfo = jedisClient.get(key); 
				}
				System.err.println("old_userInfo:"+old_userInfo);
				if(old_userInfo!=null && !old_userInfo.equals("")) {
					jedisClient.del(key);
				}
			}
		}catch(Exception e) {
			System.err.println("注销用户登陆信息时发生异常:"+e.getMessage());
			e.printStackTrace();
		}
		 
		//注册该用户最新的登陆信息  
        if (newToken != null && key!=null && !key.equals("")) {    
        	jedisClient.set(key , gson.toJson(currentUser));
        	jedisClient.expire(key,Integer.parseInt(NBC_SSO_SERVER_JEDIS_KEY_TIMEOUT));
        }  
        System.err.println("newToken:"+newToken);
		json.put("success", true);
		json.put("loginResult",  gson.toJson(loginResult)); 
		json.put("token", newToken);
		System.err.println("返回数据:"+json.toString());
		return json.toString(); 
 
	}

 

SSO校验中心:(SsoCheckController.java)


@Controller
@RequestMapping("/ssocheck")
public class SsoCheckController {
	
	
	private static final Gson gson = new Gson();
	private static String NBC_SSO_SERVER_REGEX_OF_IP = PropertiesUtils.getProperty("NBC_SSO_SERVER_REGEX_OF_IP");
	private static String NBC_SSO_SERVER_JEDIS_KEY_PREFIX = PropertiesUtils.getProperty("NBC_SSO_SERVER_JEDIS_KEY_PREFIX");
	 
	 
	@Autowired
	private JedisClient jedisClient; 
	@Autowired
	private LogService logService;
	
	 
	
	
	@RequestMapping(value = "/tokencheck")
	public void ssocheck(HttpServletRequest request, HttpServletResponse response,HttpSession session) { 
		System.err.println("=============== server tokencheck ===============");
		String sessionId = session.getId();
		String token = request.getHeader("token"); 
		System.err.println("tokencheck token:"+token);
		String clientIp = request.getHeader("clientIp"); 
		String returnURL = request.getHeader("returnURL");  
		String errorURL = request.getContextPath()+"/error/commonError";
		String loginURL = request.getContextPath()+"/login?returnURL="+returnURL;
		String sonSysIp = ComputerInfoUtil.getIP(request);
		JSONObject json = new JSONObject();
		List<String> event = new ArrayList<String>();
		event.add("客户端请求SSO认证中心校验Token");
		try {
			//returnURL = getDecoderString(returnURL,encoding);
			if(token==null || token.equals("")) {  
				event.add("警告:token参数为空,服务器已重定向页面至登陆页面:"+loginURL); 
				logService.ssocheckToLog(sessionId,clientIp, false, event); 
				response.sendRedirect(loginURL);
				return;
			} 
			Pattern pattern = Pattern.compile(NBC_SSO_SERVER_REGEX_OF_IP);
			if(clientIp==null || clientIp.equals("")) {
				event.add("警告:clientIp参数为空,服务器已重定向页面至登陆页面:"+loginURL); 
				logService.ssocheckToLog(sessionId,clientIp, false, event); 
				response.sendRedirect(loginURL);
				return;
			}else if(!pattern.matcher(clientIp).matches()){
				event.add("警告:clientIp参数非法,该参数源自子系统ipv4:"+sonSysIp);
				event.add("服务器已重定向页面至登陆页面:"+loginURL); 
				logService.ssocheckToLog(sessionId,clientIp, false, event); 
				response.sendRedirect(loginURL);
				return;
			} 
			String encoding = "UTF-8";
			token = getDecoderString(token,encoding); 
			event.add("请求参数Token:"+token);
			
			boolean tokenOK = JwtUtil.checkToken(token);
			System.err.println("校验Token:"+((tokenOK==true)?"有效":"无效"));
			if(!tokenOK) {
				event.add("警告:Token认证不通过,该客户端存在伪造Token的可能性,请及时做好应对措施。");
				event.add("服务器已重定向页面至登陆页面:"+loginURL); 
				logService.ssocheckToLog(sessionId,clientIp, false, event);
				response.sendRedirect(loginURL);
				return;
			}
			
			String redis_userInfo = "";
			String fuserId = JwtUtil.getClaim("fuserId", token);
			if(fuserId!=null) {
				String key = NBC_SSO_SERVER_JEDIS_KEY_PREFIX+AesUtil.decode(fuserId);
				System.err.println("key:"+key);
				redis_userInfo = jedisClient.get(key); 
			} 
			System.err.println("redis_userInfo:"+redis_userInfo);
			if(redis_userInfo==null || redis_userInfo.equals("")) {
				//token不存在于服务器redis
				event.add("警告:Token不存在于服务器,服务器已重定向页面至登陆页面:"+loginURL); 
				logService.ssocheckToLog(sessionId,clientIp, false, event); 
				response.sendRedirect(loginURL);
				return;
			} 
			//token有效并且服务器有该用户登陆信息
			//校验token和登陆信息的token是否一致
			z_CF_UsersVO user = gson.fromJson(redis_userInfo, z_CF_UsersVO.class);
			if(!token.equals(user.getToken())) {
				event.add("token有效并且服务器有该用户登陆信息,但token和服务器的token不一致(可能是该用户已在其他地方登陆)"); 
				logService.ssocheckToLog(sessionId,clientIp, false, event); 
				response.sendRedirect(loginURL);
				return;
			}
			String TokenOK1 = "true";
			String userInfo = AesUtil.decode(JwtUtil.getClaim("userInfo", token)); 
			String userInfo1 = (userInfo==null)?"":URLEncoder.encode(gson.toJson(userInfo), encoding);
			json.put("TokenOK", TokenOK1);
			json.put("userInfo", userInfo);
			event.add("Token认证通过,即将返回信息给客户端:"+json.toString()); 
			logService.ssocheckToLog(sessionId,clientIp, true, event);
			response.setHeader("TokenOK", TokenOK1);
			response.setHeader("userInfo",userInfo1);	
		}catch(Exception e) {
			try {
				String info = "SSO认证中心校验Token时发生异常:"+e.getMessage(); 
				//checktoken记录日志
				event = new ArrayList<String>();
				event.add(info);
				event.add("服务器已重定向页面至错误页面:"+errorURL);
				logService.ssocheckToLog(sessionId,clientIp, false, event);
				request.getSession().setAttribute("error", info);
				response.sendRedirect(errorURL);
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			e.printStackTrace();
		}
		 
		 
	}
	 
	private String getEncoderString(String str,String encoding) {
		String result = "";
		try {
			if(str!=null && !str.equals("") && encoding!=null && !encoding.equals("")) {
				result = URLEncoder.encode( str, encoding); 
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
		return result;
	}
	
	private String getDecoderString(String str,String encoding) {
		String result = "";
		try {
			if(str!=null && !str.equals("") && encoding!=null && !encoding.equals("")) {
				result = URLDecoder.decode( str, encoding); 
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
		return result;
	}

}

JWT工具类:


@Slf4j
public class JwtUtil {
	
 
	
//	public static void main(String[] args) {
//		long expire = 1000 * 60;
//		Map<String, String> map = new HashMap<String, String>();
//		map.put("fuserId", "2");
//		map.put("username", "admin");
//		String token = genToken(map,expire);
//		System.err.println(token);
//		Map<String, String> claims = getClaims(token);
//		for(String key : claims.keySet()) {
//			//System.err.println(key);
//			//System.err.println(key+":"+AesUtil.decode(claims.get(key)));
//		}
//		//String claim = getClaim("fuserId",token);
//		//System.err.println(AesUtil.decode(claim));
//		
//	}

	private static final Gson gson = new Gson();
	
    //密钥
    private static String SECRET = readSecret();;
    //发行人
    private static String ISSUER = "NBC_USER";
 
    public static String getClaim(String claimName, String token) { 
    	String[] params = new String[] {claimName,token};
    	for(String param : params) {
    		if(param==null || param.equals("")) {
    			return null;
    		}
    	}
    	try {
    		Algorithm algorithm = Algorithm.HMAC256(SECRET);
    		//解密
            JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER).build();
            DecodedJWT jwt =  verifier.verify(token); 
            Map<String, Claim> map = jwt.getClaims();
            Map<String, String> resultMap = new HashMap<>();
            map.forEach((k,v) -> resultMap.put(k, v.asString())); 
            return resultMap.get(claimName);
    	}catch(Exception e) {
    		e.printStackTrace();
    	}
    	return null;
    }
    public static Map<String, String> getClaims(String token) { 
    	
    	if(token==null || token.equals("")) {
    		return null;
    	}
    	try {
    		Algorithm algorithm = Algorithm.HMAC256(SECRET);
    		//解密
            JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER).build();
            DecodedJWT jwt =  verifier.verify(token); 
            Map<String, Claim> map = jwt.getClaims();
            Map<String, String> resultMap = new HashMap<>();
            map.forEach((k,v) -> resultMap.put(k,v.asString()));
            return resultMap; 
    	}catch(Exception e) {
    		e.printStackTrace();
    	}
    	return null;
    }
    /**
     * 生成jwt
     */
    public static String genToken(Map<String, String> claims,long expire) {
    	try { 
    		//过期时间
    		Date expireDate = new Date(System.currentTimeMillis() + expire);
        	//使用HMAC256进行加密
            Algorithm algorithm = Algorithm.HMAC256(SECRET); 
            //创建jwt
            JWTCreator.Builder builder =  JWT.create()
            		.withIssuer(ISSUER)//发行人
            		.withExpiresAt(expireDate); //过期时间点
            //传入参数
            if(claims!=null) {
            	claims.forEach((key,value)-> {
            		String encodeValue = AesUtil.encode(value);
                    builder.withClaim(key, encodeValue);
                });
            } 
            //签名加密
            return builder.sign(algorithm);
        } catch (Exception e) {
            //log.info("jwt 生成问题");
        } 
        return null;
    }

    /**
     * 解密jwt并验证是否正确
     */
    public static boolean checkToken (String token) {
    	if(token==null || token.equals("")) {
    		return false;
    	} 
        try {
            //使用hmac256加密算法
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET))
                    .withIssuer(ISSUER)
                    .build();
            DecodedJWT decodedJWT = verifier.verify(token);
            //获得token的头部,载荷和签名,只对比头部和载荷
            String [] headPayload = token.split("\\.");
            //获得jwt解密后头部
            String header = decodedJWT.getHeader();
            //获得jwt解密后载荷
            String payload = decodedJWT.getPayload(); 
            return (header.equals(headPayload[0]) && payload.equals(headPayload[1]));
        } catch (Exception e) {
            //log.info("jwt解密出现错误,jwt或私钥或签证人不正确");
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 读取密钥
     * @return 密钥信息
     */
    private static String readSecret() {
    	String secret = "";
    	try {
    		Properties properties = new Properties(); 
            InputStream inputStream = ClassLoader.getSystemResourceAsStream("jwt.properties");//加载resource目录下的配置文件
            properties.load(inputStream); 
            secret = properties.getProperty("JWT_SECRET");
    	}catch(Exception e) {
    		e.printStackTrace();
    	} 
        return secret;
    }
}

AesUtil.java:


@Slf4j
public class AesUtil {

	  
    //加密密钥
    private static String SECRET = readSecret();;


	/**
     * 加密
     * @return 加密后内容
     */
    public static String encode (String content) {
        Key key = getKey();
        byte[] result = null;
        try{
            //创建密码器
            Cipher cipher = Cipher.getInstance("AES");
            //初始化为加密模式
            cipher.init(Cipher.ENCRYPT_MODE,key);
            //加密
            result = cipher.doFinal(content.getBytes("UTF-8"));
           //将二进制转换成16进制
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < result.length; i++) {
                String hex = Integer.toHexString(result[i] & 0xFF);
                if (hex.length() == 1) {
                    hex = '0' + hex;
                }
                sb.append(hex.toUpperCase());
            }
            return  sb.toString();
        } catch (Exception e) {
            //log.info("aes加密出错");
        }
        return null;
    }

    /**
     * 解密
     * @return 解密后内容
     */
    public static String decode (String content) {
        //将16进制转为二进制
        if (content.length() < 1)
            return null;
        byte[] result = new byte[content.length()/2];
        for (int i = 0;i< content.length()/2; i++) {
            int high = Integer.parseInt(content.substring(i*2, i*2+1), 16);
            int low = Integer.parseInt(content.substring(i*2+1, i*2+2), 16);
            result[i] = (byte) (high * 16 + low);
        }

        Key key = getKey();
        byte[] decrypt = null;
        try{
            //创建密码器
            Cipher cipher = Cipher.getInstance("AES");
            //初始化为解密模式
            cipher.init(Cipher.DECRYPT_MODE,key);
            //解密
            decrypt = cipher.doFinal(result);
        } catch (Exception e) {
            //log.info("aes解密出错");
        }
        assert decrypt != null;
        return new String(decrypt);
    }

    /**
     * 根据私钥内容获得私钥
     */
    private static Key getKey () {
        SecretKey key = null;
        try {
            //创建密钥生成器
            KeyGenerator generator = KeyGenerator.getInstance("AES");
            //初始化密钥
            generator.init(128,new SecureRandom(SECRET.getBytes()));
            //生成密钥
            key = generator.generateKey();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return key;
    }

    /**
     * 读取密钥
     * @return 密钥信息
     */
    public static String readSecret () {
    	String secret = ""; 
        try {
        	Properties properties = new Properties();
            //加载resource目录下的配置文件
            InputStream inputStream = ClassLoader.getSystemResourceAsStream("aes.properties"); 
            properties.load(inputStream);
            secret = properties.getProperty("AES_SECRET");
        } catch (IOException e) {
            //log.info("读取密钥文件错误");
        }
        return secret;
    }

}

SSO登录成功后,跳转至SSO登陆成功处理页面loginSuccess.jsp(ajax发送命令,让子系统添加token到Cookie后,重定向页面回子系统指定的LoginOK页面)

@RequestMapping(value="/loginSuccess",method=RequestMethod.GET)
	public String loginSuccess(Model model,HttpServletRequest req,HttpSession session) {
		 
		String domains = "";
		String returnURL = (req.getParameter("returnURL")==null)?"":req.getParameter("returnURL");
		String token = (req.getParameter("token")==null)?"":req.getParameter("token");
		try { 
			List<z_CF_DomainVO> list = new ArrayList<z_CF_DomainVO>();
			List<z_CF_Domain> list0 = domainRepository.findAll();
			list0.forEach((domain)->{
				z_CF_DomainVO vo = new z_CF_DomainVO();
				BeanUtils.copyProperties(domain, vo);
				list.add(vo);
			});
			domains = ScriptDecoder.escape(gson.toJson(list));
		}catch(Exception e) {
			e.printStackTrace();
		}
		session.setAttribute("domains",domains ); 
		session.setAttribute("returnURL", returnURL); 
		session.setAttribute("token", token);
		return "public/loginSuccess";
	}
	
	
	@RequestMapping(value="/return",method=RequestMethod.POST)
	public String returnPage(Model model,HttpServletRequest req,HttpServletResponse resp,HttpSession session) {
		String returnURL = (req.getParameter("returnURL")==null)?"":req.getParameter("returnURL");
		String token = (req.getParameter("token")==null)?"":req.getParameter("token");
		String type = (req.getParameter("type")==null)?"":req.getParameter("type");
		session.setAttribute("type", type); 
		session.setAttribute("returnURL", returnURL); 
		session.setAttribute("token", token); 
		return "public/return";
	}

loginSuccess.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>loginSuccess</title>
<%
System.err.println("=============== SSO loginSuccess.jsp ===============");
%>
<!-- jquery -->
<script type="text/javascript" src="${pageContext.request.contextPath }/static/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript">
$(function(){

	var domains = $("#domains").val(); 
	var token = $("#token").val();
	var returnURL = $("#returnURL").val();

	//jsonpTest();
	sendCookie(domains,token); 
	$("#MyForm").submit();
	
	
	 
	function sendCookie(domains,token){
		if(domains!="" && domains!=undefined){
			domains = JSON.parse(unescape(domains));
			for(var i in domains){
				var domainObj = domains[i];
				//var addCookieURL = domainObj.returnURLOfLoginOK;
				var addCookieURL = domainObj.urlOfOperateCookie; 
				if(token!=null && token!=""){ 
					sendAjax(addCookieURL,token);
				} 
			}
		}
	}
	
	function sendAjax(addCookieURL,token){
		if(addCookieURL!="" && addCookieURL!=null && addCookieURL!=undefined){ 
			var url = "";
			if(addCookieURL.indexOf("?")>0){
				url = addCookieURL+"&jsonpCallback=callbackFunction&type=login&token="+token; 
			}else{
				url = addCookieURL+"?jsonpCallback=callbackFunction&type=login&token="+token; 
			}   
			$.ajax({
	            url: url,
	            type: "GET",
	            timeout:10000,//10秒超时,单位:毫秒
	            async: false,
	            processData: false, 
	            jsonp: "callback",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(默认为:callback)
 	            jsonpCallback:"callbackFunction",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名
	            dataType: "jsonp", //指定服务器返回的数据类型
	            success:function(data){
	            	
				}, 
				//ajax Exception 
				error: function(XMLHttpRequest, textStatus, errorThrown) {
					var httpStatus = XMLHttpRequest.status;
					if(httpStatus!=200){
						addCookieLog(url,httpStatus);
					}
				/*  
				alert("XMLHttpRequest.status:"+XMLHttpRequest.status+"\n"+"XMLHttpRequest.readyState:"+XMLHttpRequest.readyState+"\n"
						 +"textStatus:"+textStatus);
					*/
				}  
	        });  
		}
	}
	
	function addCookieLog(url,httpStatus){
		 $.ajax({ 
				type:"POST",
				dataType:"json",
				timeout:10000,
				url:"/log/addCookieFiledLog",  //后台url请求,处理传递的参数
				async: false,
				data:{
					url:escape(url),
					httpStatus:httpStatus
					},
				success:function(data){
				//ajax请求成功执行该函数
					//alert("添加日志成功:"+data)
				},
				 error: function(XMLHttpRequest, textStatus, errorThrown) {
					// alert("添加日志失败status:"+XMLHttpRequest.status)
					/*  alert("XMLHttpRequest.status:"+XMLHttpRequest.status+"\n"+"XMLHttpRequest.readyState:"+XMLHttpRequest.readyState+"\n"
							 +"textStatus:"+textStatus); */
				}
			});
	}
	
	function jsonpTest(){
		var ajax = $.ajax({
	        type: "GET",
	        async: false,
	        timeout:10000,
	        //url: "http://10.1.0.192:30013/KMS/Test/jsonpData.jsp?jsonpCallback=callbackFunction",
	        url: "http://10.1.8.12:8082/KMS/Jsp/public/index/cookie.jsp?jsonpCallback=callbackFunction",
	        dataType: "jsonp",
	        jsonp: "callback",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)
	        jsonpCallback:"callbackFunction",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据
	        success: function(json){
	        	alert(JSON.stringify(json)); 
	        },
	        error: function(){
	            alert('jsonp fail');
	        }
	    });  
	}
 
})
</script>
 
</head>
<body>

<jsp:include page="/public/loading.jsp"/>

<form id="MyForm" action="/return" method="POST"> 
<input type="hidden" id="type" name="type" value="login" />
<input type="hidden" id="domains" name="domains" value="${domains }" />
<input type="hidden" id="returnURL" name="returnURL" value="${returnURL }" /> 
<input type="hidden" id="token" name="token" value="${token }" />
</form>
</body>
</html>
  

return.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
System.err.println("================= return.jsp =================");
String returnURL = (session.getAttribute("returnURL")==null)?"":session.getAttribute("returnURL").toString();
String token = (session.getAttribute("token")==null)?"":session.getAttribute("token").toString();
String type = (session.getAttribute("type")==null)?"":session.getAttribute("type").toString();
if(!returnURL.equals("")){  
	String returnType = "";
	switch(type.toLowerCase()){
		case "login":
			returnType = "登陆";
			if(!token.equals("")){
				if(returnURL.indexOf("?")>0){
					returnURL = returnURL+"&token="+token;
				}else{
					returnURL = returnURL+"?token="+token;
				}
			}  
			break;
		case "logout":
			returnType = "注销"; 
			break;
		default:  
			break;
	} 
	System.err.println("类型:"+returnType);
	System.err.println("服务器即将重定向回子系统:"+returnURL);
	response.sendRedirect(returnURL);
}

%>

好了,SSO大部分核心代码已经贴出来了,接下来就看你们的编码功底了。有什么相关问题也可以找我讨论(q:2225629171)

本次SSO实现跨域单点登陆讲解完毕。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值