一、前言
小编在前一篇博客中向大家介绍了使用单点登录的演变过程,最后一步的时候小编向大家展示了分布式架构。其中就用到了单点登录系统。这篇博客继续接上一篇博客,实现一下单点登录系统。
二、环境准备
Eclipse
Redis
三、单点登录流程图
这个是简单的单点登录流程图,就那淘宝来说,当我们进步淘宝首页的时候是没有登录的,点击登录的时候,会跳转到用户登录界面。此时的用户登录界面就是咱们SSO系统的一部分,根据登录的要求,会接收用户名和密码,然后根据用户名查询密码是否正确。
如果不正确就跳转到登录页,提示不正确;
如果正确就要进行以下步骤:
1. 生成一个uuid,作为token;
2. 把用户信息序列化存储到redis,存储的key为token,存储成功后,返回token;
3. 把token存储到cookie;
4. 判断是否有回调url,如果有,跳转到指定url;如果没有,跳转到系统首页;
四、实现过程
4.1 使用到的技术
Mybatis
Spring
Springmvc
Jedis
4.2 创建项目
创建Maven项目:
4.3 依赖的jar包
pom文件:
<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>com.dmsd</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.dmsd</groupId>
<artifactId>sso</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>com.dmsd</groupId>
<artifactId>dao</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- 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-context-support</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>
<!-- Redis客户端 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>
<!-- 添加tomcat插件 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<port>8084</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.4 整合SSM框架
这里的整合可以参考小编以前写的SSM整合博客。
4.5 登录逻辑实现
DAO层:
直接使用Mybatis逆向工程产生的代码。
Service层:
接收参数:用户名、密码。
校验密码是否正确,生成token,向redis中写入用户信息,把token写入cookie,返回SystemResult实体包含token。
参数:用户名、密码、HttpServletResponse、HttpServletRequest
返回值:TaotaoResult
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private TbUserMapper userMapper;
@Autowired
private JedisClient jedisClient;
@Value("${REDIS_SESSION_KEY}")
private String REDIS_SESSION_KEY;
@Value("${SESSION_EXPIRE}")
private Integer SESSION_EXPIRE;
@Override
public SystemResult login(String username, String password, HttpServletRequest request,
HttpServletResponse response) {
//校验用户名密码是否正确
TbUserExample example = new TbUserExample();
Criteria criteria = example.createCriteria();
criteria.andUsernameEqualTo(username);
List<TbUser> list = userMapper.selectByExample(example);
//取用户信息
if (list == null || list.isEmpty()) {
return TaotaoResult.build(400, "用户名或密码错误");
}
TbUser user = list.get(0);
//校验密码
if(!user.getPassword().equals(DigestUtils.md5DigestAsHex(password.getBytes()))) {
return SystemResult.build(400, "用户名或密码错误");
}
//登录成功
//生成token
String token = UUID.randomUUID().toString();
//把用户信息写入redis
//key:REDIS_SESSION:{TOKEN}
//value:user转json
user.setPassword(null);
jedisClient.set(REDIS_SESSION_KEY + ":" + token, JsonUtils.objectToJson(user));
//设置session的过期时间
jedisClient.expire(REDIS_SESSION_KEY + ":" + token, SESSION_EXPIRE);
//写cookie
CookieUtils.setCookie(request, response, "TT_TOKEN", token);
return SystemResult.ok(token);
}
}
Controller层:
请求的url:/user/login
接收参数:username、password
调用Service,返回taotaoResult对象。
响应json数据。
@Controller
public class LoginController {
@Autowired
private LoginService loginService;
@RequestMapping(value="/user/login", method=RequestMethod.POST)
@ResponseBody
public SystemResult login(String username, String password,
HttpServletRequest request, HttpServletResponse response) {
try {
SystemResult result = loginService.login(username, password, request, response);
return result;
} catch (Exception e) {
e.printStackTrace();
return SystemResult.build(500, ExceptionUtil.getStackTrace(e));
}
}
}
4.6 通过token查询用户信息
说明:根据token到redis查询用户信息,如果用户信息不存在说明session已经过期,返回400并提示用户session已经过期。如果查询到用户,返回用户信息,并且更新一下用户的过期时间。
请求url:/user/token/{token}
需要支持jsonp
返回:SystemResult
Dao层:
使用Jedis访问redis实现。
Service层:
参数:String token
根据token查询redis,查询到结果返回用户对象,更新过期时间。如果查询不到结果,返回Session已经过期,状态码400.
返回值:SystemResult
@Override
public SystemResult getUserByToken(String token) {
// 根据token取用户信息
String json = jedisClient.get(REDIS_SESSION_KEY + ":" + token);
//判断是否查询到结果
if (StringUtils.isBlank(json)) {
return SystemResult.build(400, "用户session已经过期");
}
//把json转换成java对象
TbUser user = JsonUtils.jsonToPojo(json, TbUser.class);
//更新session的过期时间
jedisClient.expire(REDIS_SESSION_KEY + ":" + token, SESSION_EXPIRE);
return SystemResult.ok(user);
}
Controller:
请求的url:/user/token/{token}
从url中取token的内容,调用Service取用户信息,返回TaotaoResult。(json数据)
@RequestMapping("/user/token/{token}")
@ResponseBody
public Object getUserByToken(@PathVariable String token, String callback) {
try {
TaotaoResult result = loginService.getUserByToken(token);
//支持jsonp调用
if (StringUtils.isNotBlank(callback)) {
MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(result);
mappingJacksonValue.setJsonpFunction(callback);
return mappingJacksonValue;
}
return result;
} catch (Exception e) {
e.printStackTrace();
return SystemResult.build(500, ExceptionUtil.getStackTrace(e));
}
}
首页系统中登录跳转的代码:
这里使用Ajax的跨域调用,先获取到存储在浏览器的cookie,然后使用Ajax的Jsonp来跨域调用用户信息,由于返回的是SystemResult的json形式,他包含了err、msg、data三个部分,所以要获取用户信息的时候就要通过data.data.username来获取。
var TT = TAOTAO = {
checkLogin : function(){
var _ticket = $.cookie("TT_TOKEN");
if(!_ticket){
return ;
}
$.ajax({
url : "http://localhost:8084/user/token/" + _ticket,
dataType : "jsonp",
type : "GET",
success : function(data){
if(data.status == 200){
var username = data.data.username;
var html = username + ",欢迎来到!<a href=\"http://www.taotao.com/user/logout.html\" class=\"link-logout\">[退出]</a>";
$("#loginbar").html(html);
}
}
});
}
}
$(function(){
// 查看是否已经登录,如果已经登录查询登录信息
TT.checkLogin();
});
五、小结
这次的单点登录主要是使用redis完成的。redis真是一个非常好用的缓存技术。在很多方面都做了出色的表现。另外,就是这种产生分布式,把登录系统单独提取出来,这种思想有是很棒的。加油!