1、Client1-创建客户端client1项目(java web项目)
对应pom.xml引入
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.2.3.RELEASE</version>
</dependency>
</dependencies>
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过滤器,拦截所有的请求
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<filter>
<filter-name>loginFilter</filter-name>
<filter-class>com.sso.client1.filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>loginFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
4、Client1-添加一个index.jsp作为首页,直接放在webapp目录下(不要放WEB-INF目录)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>客户端1</title>
</head>
<body>
<h3>客户端1的首页</h3>
<h3>客户端1端口号为8011</h3>
<h3>点击下面按钮退出登录</h3>
<a href="http://localhost:8080/logOut" >退出</a>
</body>
</html>
5、Client1-配置client1项目的tomcat,将http端口号设置为8011,为防止后面项目端口冲突,把JMX PORT端口号也改了
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<String,String> params = new HashMap<String,String>();
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<String,String> 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<String,String> 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,会检查到未登录,然后跳转到认证中心去检查是否有登录状态,这里因为还没写认证中心的项目,所有访问会出错,但是能看到已经跳转,并且带上了重定向地址
10、到此已经完成client1项目登录的代码(还有部分登出注销的逻辑,后面补上)
11、同样的方法创建server项目,项目结构如下
12、附上web.xml文件,以及pom.xml依赖部分、applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.2.3.RELEASE</version>
</dependency>
</dependencies>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.sso.server.controller"/>
<mvc:annotation-driven/>
<mvc:default-servlet-handler/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
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<Map<String, String>>());
//把令牌带回到客户端
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<Map<String, String>> 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<Map<String, String>> clients = SSOServerUtil.map.remove(token);
for (Map<String, String> 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、源码丢了,改天再写一个?