sso单点登录系统
sso注册功能实现
sso登录功能实现
通过token获得用户信息
Ajax跨域请求(jsonp)
Sso系统工程搭建
需要创建一个sso服务工程,可以参考e3-manager创建。
e3-sso(pom聚合工程)
|--e3-sso-interface(jar)
|--e3-sso-Service(war)
e3-sso-web
复制pom文件依赖到e3-sso工程:
<dependencies>
<dependency>
<groupId>cn.e3mall</groupId>
<artifactId>e3-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- 配置tomcat插件 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<path>/</path>
<port>8087</port>
</configuration>
</plugin>
</plugins>
</build>
复制pom文件依赖到e3-sso-interface工程:
<dependencies>
<dependency>
<groupId>cn.e3mall</groupId>
<artifactId>e3-manager-pojo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
复制pom文件依赖到e3-sso-service工程:
<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>
<!-- 排除dubbo自带的spring 防止冲突 -->
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
<!-- 排除dubbo自带的netty 防止冲突 -->
<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>
</dependencies>
复制配置文件到e3-sso-service工程:
1.清空resource.properties内容
2.修改applicationContext-service.xml配置文件:
3.创建包:
4.修改applicationContext-trans.xml事务配置文件:
5.复制web.xml文件到e3-sso-service工程:
复制pom文件依赖到e3-sso-web工程:
<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>
1.复制配置文件到e3-sso-web工程:
2.清空resource.properties配置文件
3.修改springmvc.xml配置文件:
4.创建包:
5复制web.xml文件:
加入登录注册静态页面到e3-sso-web工程:
根绝页面请求方式修改web.xml拦截方式:
修改springmvc.xml配置静态资源映射:
安装e3-sso工程
3 服务接口实现
3.1 检查数据是否可用
3.1.1 功能分析
请求的url:/user/check/{param}/{type}
参数:从url中取参数1、String param(要校验的数据)2、Integer type(校验的数据类型)
响应的数据:json数据。e3Result,封装的数据校验的结果true:成功false:失败。
业务逻辑:
- 从tb_user表中查询数据
- 查询条件根据参数动态生成。
- 判断查询结果,如果查询到数据返回false。
- 如果没有返回true。
- 使用e3Result包装,并返回。
3.1.2 Dao层
从tb_user表查询。可以使用****。
3.1.3 Service
参数:
- 要校验的数据:String param
- 数据类型:int type(1、2、3分别代表username、phone、email)
返回值:e3Result
@Service |
public class UserServiceImpl implements UserService {
@Autowired
private TbUserMapper userMapper;
@Override
public e3Result checkData(String param, int type) {
// 1、从tb_user表中查询数据
TbUserExample example = new TbUserExample();
Criteria criteria = example.createCriteria();
// 2、查询条件根据参数动态生成。
//1、2、3分别代表username、phone、email
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 = userMapper.selectByExample(example);
// 3、判断查询结果,如果查询到数据返回false。
if (list == null || list.size() == 0) {
// 4、如果没有返回true。
return e3Result.ok(true);
}
// 5、使用e3Result包装,并返回。
return e3Result.ok(false);
}
}
发布服务
3.1.4 表现层
需要在e3-sso-web中实现。
引用服务
Controller
请求的url:/user/check/{param}/{type}
参数:从url中取参数1、String param(要校验的数据)2、Integer type(校验的数据类型)
响应的数据:json数据。e3Result,封装的数据校验的结果true:成功false:失败。
@Controller |
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/user/check/{param}/{type}")
@ResponseBody
public e3Result checkData(@PathVariable String param, @PathVariable Integer type) {
e3Result e3Result = userService.checkData(param, type);
return e3Result;
}
}
3.2 用户注册
3.2.1 功能分析
请求的url:/user/register
参数:表单的数据:username、password、phone、email
返回值:json数据。e3Result
接收参数:使用TbUser对象接收。
请求的方法:post
业务逻辑:
- 使用TbUser接收提交的请求。
- 补全TbUser其他属性。
- 密码要进行MD5加密。
- 把用户信息插入到数据库中。
- 返回e3Result。
3.2.2 Dao层
可以使用****。
3.2.3 Service层
参数:TbUser
返回值:e3Result
@Override |
public e3Result createUser(TbUser user) {
// 1、使用TbUser接收提交的请求。
if (StringUtils.isBlank(user.getUsername())) {
return e3Result.build(400, "用户名不能为空");
}
if (StringUtils.isBlank(user.getPassword())) {
return e3Result.build(400, "密码不能为空");
}
//校验数据是否可用
e3Result result = checkData(user.getUsername(), 1);
if (!(boolean) result.getData()) {
return e3Result.build(400, "此用户名已经被使用");
}
//校验电话是否可以
if (StringUtils.isNotBlank(user.getPhone())) {
result = checkData(user.getPhone(), 2);
if (!(boolean) result.getData()) {
return e3Result.build(400, "此手机号已经被使用");
}
}
//校验email是否可用
if (StringUtils.isNotBlank(user.getEmail())) {
result = checkData(user.getEmail(), 3);
if (!(boolean) result.getData()) {
return e3Result.build(400, "此邮件地址已经被使用");
}
}
// 2、补全TbUser其他属性。
user.setCreated(new Date());
user.setUpdated(new Date());
// 3、密码要进行MD5加密。
String md5Pass = DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
user.setPassword(md5Pass);
// 4、把用户信息插入到数据库中。
userMapper.insert(user);
// 5、返回e3Result。
return e3Result.ok();
}
发布服务
3.2.4 表现层
引用服务。
Controller:
请求的url:/user/register
参数:表单的数据:username、password、phone、email
返回值:json数据。e3Result
接收参数:使用TbUser对象接收。
请求的方法:post
@RequestMapping(value="/user/register", method=RequestMethod.POST) |
@ResponseBody
public e3Result register(TbUser user) {
e3Result result = userService.createUser(user);
return result;
}
3.2.5 测试
可以使用restclient-ui-3.5-jar-with-dependencies.jar测试接口。
表单提交的content-type:application/x-www-form-urlencoded
3.3 用户登录
3.3.1 功能分析
请求的url:/user/login
请求的方法:POST
参数:username、password,表单提交的数据。可以使用方法的形参接收。
返回值:json数据,使用e3Result包含一个token。
业务逻辑:
登录的业务流程:
登录的处理流程:
- 登录页面提交用户名密码。
- 登录成功后生成token。Token相当于原来的jsessionid,字符串,可以使用uuid。
- 把用户信息保存到redis。Key就是token,value就是TbUser对象转换成json。
- 使用String类型保存Session信息。可以使用“前缀:token”为key
- 设置key的过期时间。模拟Session的过期时间。一般半个小时。
- 把token写入cookie中。
- Cookie需要跨域。例如www.e3.com\sso.e3.com\order.e3.com,可以使用工具类。
- Cookie的有效期。关闭浏览器失效。
- 登录成功。
注册成功后去登录:
手动选择去登录:
LoginController:
不能使用userid作为redis缓存的key,换台电脑userid相同,但因为换了电脑,用户并没有登录。不能判断出用户是否登录。
应该和tomcat session一样,使用sessionid作为key,把sessionid存入cookie中。每次服务端从cookie中取sessionid去redis中查询,判断用户是否登录和是否过期,如果没过期需要重置过期时间。key是sessionid(图中的token),使用uuid生成key不会重复,value是用户信息。需要和session一样设置过期时间。
加入redis依赖:
添加redis配置文件:
3.3.2 Dao层
查询tb_user表。单表查询。可以使用****。
3.3.3 Service层
resource.properties:
参数:
- 用户名:String username
- 密码:String password
返回值:e3Result,包装token。
业务逻辑:
1、判断用户名密码是否正确。
2、登录成功后生成token。Token相当于原来的jsessionid,字符串,可以使用uuid。
3、把用户信息保存到redis。Key就是token,value就是TbUser对象转换成json。
4、使用String类型保存Session信息。可以使用“前缀:token”为key
5、设置key的过期时间。模拟Session的过期时间。一般半个小时。
6、返回e3Result包装token。
@Override |
public e3Result login(String username, String password) {
// 1、判断用户名密码是否正确。
TbUserExample example = new TbUserExample();
Criteria criteria = example.createCriteria();
criteria.andUsernameEqualTo(username);
//查询用户信息
List<TbUser> list = userMapper.selectByExample(example);
if (list == null || list.size() == 0) {
return e3Result.build(400, "用户名或密码错误");
}
TbUser user = list.get(0);
//校验密码
if (!user.getPassword().equals(DigestUtils.md5DigestAsHex(password.getBytes()))) {
return e3Result.build(400, "用户名或密码错误");
}
// 2、登录成功后生成token。Token相当于原来的jsessionid,字符串,可以使用uuid。
String token = UUID.randomUUID().toString();
// 3、把用户信息保存到redis。Key就是token,value就是TbUser对象转换成json。
// 4、使用String类型保存Session信息。可以使用“前缀:token”为key
user.setPassword(null);
jedisClient.set(USER_INFO + ":" + token, JsonUtils.objectToJson(user));
// 5、设置key的过期时间。模拟Session的过期时间。一般半个小时。
jedisClient.expire(USER_INFO + ":" + token, SESSION_EXPIRE);
// 6、返回e3Result包装token。
return e3Result.ok(token);
}
发布服务
3.3.4 表现层
引用服务:
Controller
请求的url:/user/login
请求的方法:POST
参数:username、password,表单提交的数据。可以使用方法的形参接收。
HttpServletRequest、HttpServletResponse
返回值:json数据,使用e3Result包含一个token。
业务逻辑:
1.接收两个参数。
2.调用Service进行登录。
3.从返回结果中取token,写入cookie。Cookie要跨域。
Cookie二级域名跨域需要设置:
1)setDomain,设置一级域名:
.itcatst.cn
.e3.com
.e3.com.cn
2)setPath。设置为“/”
工具类放到e3-common工程中(工具类的编码指的是中文转码)。
4.响应数据。Json数据。e3Result,其中包含Token。
@RequestMapping(value="/user/login", method=RequestMethod.POST) |
@ResponseBody
public e3Result login(String username, String password,
HttpServletRequest request, HttpServletResponse response) {
// 1、接收两个参数。
// 2、调用Service进行登录。
e3Result result = userService.login(username, password);
// 3、从返回结果中取token,写入cookie。Cookie要跨域。
String token = result.getData().toString();
CookieUtils.setCookie(request, response, COOKIE_TOKEN_KEY, token);
// 4、响应数据。Json数据。e3Result,其中包含Token。
return result;
}
安装e3-common工程和e3-sso-interface工程
实现首页登录注册:
3.4 通过token查询用户信息
3.4.1 功能分析
请求的url:/user/token/{token}
参数:String token需要从url中取。
返回值:json数据。使用e3Result包装Tbuser对象。
业务逻辑:
- 从url中取参数。
- 根据token查询redis。
- 如果查询不到数据。返回用户已经过期。
- 如果查询到数据,说明用户已经登录。
- 需要重置key的过期时间。
- 把json数据转换成TbUser对象,然后使用e3Result包装并返回。
3.4.2 Dao层
定义接口。
3.4.3 Service层
参数:String token
返回值:e3Result
@Override |
public e3Result getUserByToken(String token) {
// 2、根据token查询redis。
String json = jedisClient.get(USER_INFO + ":" + token);
if (StringUtils.isBlank(json)) {
// 3、如果查询不到数据。返回用户已经过期。
return e3Result.build(400, "用户登录已经过期,请重新登录。");
}
// 4、如果查询到数据,说明用户已经登录。
// 5、需要重置key的过期时间。
jedisClient.expire(USER_INFO + ":" + token, SESSION_EXPIRE);
// 6、把json数据转换成TbUser对象,然后使用e3Result包装并返回。
TbUser user = JsonUtils.jsonToPojo(json, TbUser.class);
return e3Result.ok(user);
}
发布服务:
3.4.4 表现层
请求的url:/user/token/{token}
参数:String token需要从url中取。
返回值:json数据。使用e3Result包装Tbuser对象。
@RequestMapping("/user/token/{token}") |
@ResponseBody
public e3Result getUserByToken(@PathVariable String token) {
e3Result result = userService.getUserByToken(token);
return result;
}
引用服务:
安装e3-sso-interface工程
3.4.5 安全退出
作业
需要根据token删除redis中的key。
4 登录注册页面实现
4.1 注册功能
第一步:把静态页面添加到工程中。
‘
第二步:展示页面。
请求的url:
登录:/page/login
注册:/page/register
参数:无
返回结果:逻辑视图String
@Controller |
public class PageController {
@RequestMapping("/page/register")
public String showRegister() {
return "register";
}
@RequestMapping("/page/login")
public String showLogin() {
4.2 登录功能
参考login.jsp
5 登录注册页面整合首页
5.1 首页跳转到登录、注册页面
5.2 首页展示用户名
- 当用户登录成功后,在cookie中有token信息。
- 从cookie中取token根据token查询用户信息。
- 把用户名展示到首页。
方案一:在Controller中取cookie中的token数据,调用sso服务查询用户信息。
方案二:当页面加载完成后使用js取token的数据,使用ajax请求查询用户信息。
问题:服务接口在sso系统中。Sso.e3.com(localhost:8088),在首页显示用户名称,首页的域名是www.e3.com(localhost:8082),使用ajax请求跨域了。
Js不可以跨域请求数据。
什么是跨域:
- 域名不同
- 域名相同端口不同。
解决js的跨域问题可以使用jsonp。
Jsonp不是新技术,跨域的解决方案。使用js的特性绕过跨域请求。Js可以跨域加载js文件。
5.3 Jsonp原理
把mycall前台的方法名传到后台,给callback字段。后台接受callback字段。写成js语句,调用前端方法。
5.4 Json实现
5.4.1 客户端
使用jQuery。
jquery.cookie.js:取cookie数据
e3mall.js:
5.4.2 服务端
- 接收callback参数,取回调的js的方法名。
- 业务逻辑处理。
- 响应结果,拼接一个js语句。
spring4.1前的版本:
spring4.2以后的版本: