SSO系统
什么是SSO系统
SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一。
为什么要有单点登录系统
传统的登录实现方式
此方式在只有一个web工程时没有问题
集群环境下
多台服务器负载均衡, 用户刷新页面会导致要求多次登录
解决方案:
1、配置tomcat集群。配置tomcatSession复制。节点数不要超过5个。
2、可以使用Session服务器,保存Session信息,使每个节点是无状态。需要模拟Session。
单点登录系统是使用redis模拟Session,实现Session的统一管理。
搭建单点登录系统工的程
创建服务层
在*-sso里创建两个模块
*-sso-interface
*-sso-service
添加依赖
*-sso
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.e3mall</groupId>
<artifactId>e3-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.e3mall</groupId>
<artifactId>e3-sso</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>e3-sso-interface</module>
<module>e3-sso-service</module>
</modules>
<dependencies>
<dependency>
<groupId>cn.e3mall</groupId>
<artifactId>e3-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 配置tomcat插件 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<port>8087</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
*-sso-interface
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.e3mall</groupId>
<artifactId>e3-sso</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>e3-sso-interface</artifactId>
<dependencies>
<dependency>
<groupId>cn.e3mall</groupId>
<artifactId>e3-manager-pojo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
*-sso-interface
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.e3mall</groupId>
<artifactId>e3-sso</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>e3-sso-service</artifactId>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>cn.e3mall</groupId>
<artifactId>e3-manager-dao</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.e3mall</groupId>
<artifactId>e3-sso-interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- spring的依赖 -->
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!-- dubbo相关 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
<exclusion>
<groupId>org.jboss.netty</groupId>
<artifactId>netty</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
</dependency>
<!-- Redis客户端 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
</project>
添加框架整合的文件
创建表现层
添加依赖
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.e3mall</groupId>
<artifactId>e3-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.e3mall</groupId>
<artifactId>e3-sso-web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>cn.e3mall</groupId>
<artifactId>e3-sso-interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- JSP相关 -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- spring的依赖 -->
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!-- dubbo相关 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
<exclusion>
<groupId>org.jboss.netty</groupId>
<artifactId>netty</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 配置Tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<path>/</path>
<port>8088</port>
</configuration>
</plugin>
</plugins>
</build>
</project>
添加框架整合的文件
用户注册
展示登录页面
@RequestMapping("/page/register")
public String showRegister() {
return "register";
}
用户注册数据的有效性校检
功能分析
请求的url:/user/check/{param}/{type}
参数:从url中取参数 1、String param(要校验的数据) 2、Integer type(校验的数据类型)
响应的数据:json数据。e3Result,封装的数据校验的结果true:成功false:失败。
业务逻辑:
1、从tb_user表中查询数据
2、查询条件根据参数动态生成。
3、判断查询结果,如果查询到数据返回false。
4、如果没有返回true。
5、使用e3Result包装,并返回
Dao层
可以使用逆向工程。
服务层
定义接口
接口的实现类
package cn.e3mall.sso.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import cn.e3mall.common.util.E3Result;
import cn.e3mall.mapper.TbUserMapper;
import cn.e3mall.pojo.TbUser;
import cn.e3mall.pojo.TbUserExample;
import cn.e3mall.pojo.TbUserExample.Criteria;
import cn.e3mall.sso.service.RegisterService;
/**
* 用户注册处理service
* @author 11501
*
*/
@Service
public class RegisterServiceImpl implements RegisterService {
@Autowired
private TbUserMapper tbUserMapper;
/**
* 检查用户名手机号..
*/
@Override
public E3Result checkData(String param, int type) {
//根据不同的type生成不同的查询条件
TbUserExample example = new TbUserExample();
Criteria criteria = example.createCriteria();
//1.用户名 2.手机号 3.邮箱
if (type == 1) {
criteria.andUsernameEqualTo(param);
} else if(type == 2){
criteria.andPhoneEqualTo(param);
} else if(type == 3) {
criteria.andEmailEqualTo(param);
} else {
return E3Result.build(400, "数据类型错误");
}
//执行查询
List<TbUser> list = tbUserMapper.selectByExample(example);
//判断结果中是否包含数据
if (list != null && list.size() > 0) {
//如果有数据返回false
return E3Result.ok(false);
}
//如果没有数据返回true
return E3Result.ok(true);
}
}
发布服务
表现层
引用服务
记得添加对服务层interface的依赖(前面已经加过了)
package cn.e3mall.sso.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import cn.e3mall.common.util.E3Result;
import cn.e3mall.sso.service.RegisterService;
/**
* 注册功能Controller
* @author 11501
*
*/
@Controller
public class RegisterController {
@Autowired
private RegisterService registerService;
/**
* 检查用户名手机号
*/
@RequestMapping("/user/check/{param}/{type}")
@ResponseBody
public E3Result checkData(@PathVariable String param, @PathVariable Integer type) {
E3Result result = registerService.checkData(param, type);
return result;
}
}
用户注册功能
功能分析
请求的url:/user/register
参数:表单的数据:username、password、phone、email
返回值:json数据。e3Result
接收参数:使用TbUser对象接收。
请求的方法:post
业务逻辑:
1、使用TbUser接收提交的请求。
2、补全TbUser其他属性。
3、密码要进行MD5加密。
4、把用户信息插入到数据库中。
5、返回e3Result。
Dao层
可以使用逆向工程。
服务层
定义接口
接口的实现类
package cn.e3mall.sso.service.impl;
import java.util.Date;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import cn.e3mall.common.util.E3Result;
import cn.e3mall.mapper.TbUserMapper;
import cn.e3mall.pojo.TbUser;
import cn.e3mall.sso.service.RegisterService;
/**
* 用户注册处理service
* @author 11501
*
*/
@Service
public class RegisterServiceImpl implements RegisterService {
@Autowired
private TbUserMapper tbUserMapper;
/**
* 注册
*/
@Override
public E3Result register(TbUser tbUser) {
//数据有效性校验
if (StringUtils.isBlank(tbUser.getUsername()) || StringUtils.isBlank(tbUser.getPassword())
|| StringUtils.isBlank(tbUser.getPhone())) {
return E3Result.build(400, "数据不完整,注册失败");
}
//1.用户名 2.手机号 3.邮箱
E3Result result = checkData(tbUser.getUsername(), 1);
if (!(boolean) result.getData()) {
return E3Result.build(400, "此用户名已经被占用");
}
result = checkData(tbUser.getPhone(), 2);
if (!(boolean) result.getData()) {
return E3Result.build(400, "此手机号已经被占用");
}
//补全pojo属性
tbUser.setCreated(new Date());
tbUser.setUpdated(new Date());
//对password进行md5加密
String md5Password = DigestUtils.md5DigestAsHex(tbUser.getPassword().getBytes());
tbUser.setPassword(md5Password);
//把用户数据插入到数据库
tbUserMapper.insert(tbUser);
//返回添加成功
return E3Result.ok();
}
}
发布服务
表现层
引用服务
package cn.e3mall.sso.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import cn.e3mall.common.util.E3Result;
import cn.e3mall.pojo.TbUser;
import cn.e3mall.sso.service.RegisterService;
/**
* 注册功能Controller
* @author 11501
*
*/
@Controller
public class RegisterController {
@Autowired
private RegisterService registerService;
/**
* 注册
*/
@RequestMapping(value = "/user/register", method = RequestMethod.POST)
@ResponseBody
public E3Result regist(TbUser tbUser) {
E3Result result = registerService.register(tbUser);
return result;
}
}
用户登录
展示登录页面
@RequestMapping("/page/login")
public String showLogin() {
return "login";
}
功能分析
请求的url:/user/login
请求的方法:POST
参数:username、password,表单提交的数据。可以使用方法的形参接收。
返回值:json数据,使用e3Result包含一个token。
登录的业务流程:
登录的处理流程:
1、登录页面提交用户名密码。
2、登录成功后生成token。Token相当于原来的jsessionid,字符串,可以使用uuid。
3、把用户信息保存到redis。Key就是token,value就是TbUser对象转换成json。
4、使用String类型保存Session信息。可以使用“前缀:token”为key
5、设置key的过期时间。模拟Session的过期时间。一般半个小时。
6、把token写入cookie中。
7、Cookie需要跨域。例如www.e3.com\sso.e3.com\order.e3.com,可以使用工具类。
8、Cookie的有效期。关闭浏览器失效。
9、登录成功。
Dao层
查询tb_user表。单表查询。可以使用逆向工程。
服务层
定义接口
接口的实现类
package cn.e3mall.sso.service.impl;
import java.util.List;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import cn.e3mall.common.jedis.JedisClient;
import cn.e3mall.common.util.E3Result;
import cn.e3mall.common.util.JsonUtils;
import cn.e3mall.mapper.TbUserMapper;
import cn.e3mall.pojo.TbUser;
import cn.e3mall.pojo.TbUserExample;
import cn.e3mall.pojo.TbUserExample.Criteria;
import cn.e3mall.sso.service.LoginService;
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private TbUserMapper tbUserMapper;
@Autowired
private JedisClient jedisClient;
@Value("${SRSSION_EXPIRE}")
private Integer SRSSION_EXPIRE;
@Override
public E3Result userLogin(String username, String password) {
// 1.判断用户名和密码是否正确
//根据用户名查询用户信息
TbUserExample example = new TbUserExample();
Criteria criteria = example.createCriteria();
criteria.andUsernameEqualTo(username);
List<TbUser> list = tbUserMapper.selectByExample(example);
//执行查询
if (list == null || list.size() == 0) {
// 2.如果不正确返回登录失败
return E3Result.build(400, "用户名或密码错误");
}
//取用户信息
TbUser tbUser = list.get(0);
//判断密码是否正确
if (!DigestUtils.md5DigestAsHex(password.getBytes()).equals(tbUser.getPassword())) {
// 2.如果不正确返回登录失败
return E3Result.build(400, "用户名或密码错误");
}
// 3.如果正确生成token
String token = UUID.randomUUID().toString();
// 4.把用户信息写入redis, key:token value:用户信息
tbUser.setPassword(null);
jedisClient.set("SESSION:" + token , JsonUtils.objectToJson(tbUser));
// 5.设置session的过期时间
jedisClient.expire("SESSION:" + token , SRSSION_EXPIRE);
// 6.把token返回
return E3Result.ok(token);
}
}
SRSSION_EXPIRE
发布服务
表现层
引用服务
package cn.e3mall.sso.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import cn.e3mall.common.util.CookieUtils;
import cn.e3mall.common.util.E3Result;
import cn.e3mall.sso.service.LoginService;
/**
* 用户登录处理
* @author 11501
*
*/
@Controller
public class LoginController {
@Autowired
private LoginService loginService;
@Value("${TOKEN_KEY}")
private String TOKEN_KEY;
@RequestMapping("/page/login")
public String showLogin() {
return "login";
}
@RequestMapping(value = "/user/login" , method = RequestMethod.POST)
@ResponseBody
private E3Result login(String username, String password,
HttpServletRequest request, HttpServletResponse response) {
E3Result result = loginService.userLogin(username, password);
//判断是否登录成功
if (result.getStatus() == 200) {
String token = result.getData().toString();
//如果登录成功需要把token写入cookie
CookieUtils.setCookie(request, response, TOKEN_KEY, token);
}
return result;
}
}
TOKEN_KEY
CookieUtils
//如果登录成功需要把token写入cookie
使用CookieUtils工具类 下载页面
报错添加依赖
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
</dependency>
通过token查询用户信息
功能分析
请求的url:/user/token/{token}
参数:String token需要从url中取。
返回值:json数据。使用e3Result包装Tbuser对象。
业务逻辑:
1、从url中取参数。
2、根据token查询redis。
3、如果查询不到数据。返回用户已经过期。
4、如果查询到数据,说明用户已经登录。
5、需要重置key的过期时间。
6、把json数据转换成TbUser对象,然后使用e3Result包装并返回。
Dao层
使用JedisClient对象。
服务层
定义接口
接口的实现类
package cn.e3mall.sso.service.impl;
import org.apache.commons.lang3.StringUtils;
import org.jboss.netty.util.internal.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import cn.e3mall.common.jedis.JedisClient;
import cn.e3mall.common.util.E3Result;
import cn.e3mall.common.util.JsonUtils;
import cn.e3mall.pojo.TbUser;
import cn.e3mall.sso.service.TokenService;
/**
* 根据token获取用户信息
* @author 11501
*
*/
@Service
public class TokenServiceImpl implements TokenService {
@Autowired
private JedisClient jedisClient;
@Value("${SRSSION_EXPIRE}")
private Integer SRSSION_EXPIRE;
@Override
public E3Result getUserbyToken(String token) {
//根据token到redis中获取用户信息
String json = jedisClient.get("SESSION:" + token);
//获取不到用户信息,说明登录过期
if (StringUtils.isBlank(json)) {
return E3Result.build(201, "用户登录已经过期");
}
//取到用户信息更新token的过期时间
jedisClient.expire("SESSION:" + token, SRSSION_EXPIRE);
//返回结果,E3Result其中包含TbUser对象
TbUser tbUser = JsonUtils.jsonToPojo(json, TbUser.class);
return E3Result.ok(tbUser);
}
}
SRSSION_EXPIRE
发布服务
表现层
引用服务
package cn.e3mall.sso.controller;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import cn.e3mall.common.util.E3Result;
import cn.e3mall.common.util.JsonUtils;
import cn.e3mall.sso.service.TokenService;
/**
*
* @author 11501
*
*/
@Controller
public class TokenController {
@Autowired
private TokenService tokenService;
// @RequestMapping(value = "user/token/{token}",
// produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
// @ResponseBody
// public String getUserByToken(@PathVariable String token, String callback) {
// E3Result result = tokenService.getUserbyToken(token);
// //响应结果之前,判断是否为callback请求
// if (StringUtils.isNotBlank(callback)) {
// //把结果封装成js语句响应
// return callback + "(" + JsonUtils.objectToJson(result) + ");" ;
// }
// return JsonUtils.objectToJson(result);
// }
@RequestMapping(value = "user/token/{token}")
@ResponseBody
public Object getUserByToken(@PathVariable String token, String callback) {
E3Result result = tokenService.getUserbyToken(token);
//响应结果之前,判断是否为callback请求
if (StringUtils.isNotBlank(callback)) {
//把结果封装成js语句响应
MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(result);
mappingJacksonValue.setJsonpFunction(callback);
return mappingJacksonValue;
}
return result;
}
}