单点登录系统的实现
不同于传统的单机用户信息存放在session域中,单点登录系统创建专门的服务处理用户登录,相关信息存储在Redis中。
1.定义服务的接口
查询值是否可用 http://YOURHOST/user/check/{param}/{type} type 可以是1,2,3,分别代表username,phone,email 该接口主要目的是查询要注册的信息是否可用,get方法。 例子 http://YOURHOST/user/check/zhangsan/1 { status: 200 //200 成功 msg: "OK" // 返回信息消息 data: false // 返回数据,true:数据可用,false:数据不可用 } 用户注册 http://YOURHOST/user/register POST方法,参数username,password,phone,email 返回值 { status: 400 msg: "注册失败. 请校验数据后请再提交数据." data: null } 用户登录 http://YOURHOST/user/login POST方法:参数:username,password 返回值: { status: 200 msg: "OK" data: "fe5cb546aeb3ce1bf37abcb08a40493e" //登录成功,返回token } 通过token查询用户信息 http://YOURHOST/user/token/{token} 方法:GET,返回值 { status: 200 msg: "OK" data: "{"id":1,"username":"zhangzhijun","phone":"15800807944", "email":"420840806@qq.com","created":1414119176000,"updated":1414119179000}" } 安全退出: http://YOURHOST/user/logout/{token} 返回值 { status: 200 msg: "OK" data: "" }
Controller层代码
@Controller @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @RequestMapping("/check/{param}/{type}") @ResponseBody public Object checkData(@PathVariable String param, @PathVariable Integer type, String callback) { TaotaoResult result = null; //参数有效性校验 if (StringUtils.isBlank(param)) { result = TaotaoResult.build(400, "校验内容不能为空"); } if (type == null) { result = TaotaoResult.build(400, "校验内容类型不能为空"); } if (type != 1 && type != 2 && type != 3 ) { result = TaotaoResult.build(400, "校验内容类型错误"); } //校验出错 if (null != result) { if (null != callback) { MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(result); mappingJacksonValue.setJsonpFunction(callback); return mappingJacksonValue; } else { return result; } } //调用服务 try { result = userService.checkData(param, type); } catch (Exception e) { result = TaotaoResult.build(500, ExceptionUtil.getStackTrace(e)); } if (null != callback) { MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(result); mappingJacksonValue.setJsonpFunction(callback); return mappingJacksonValue; } else { return result; } } @RequestMapping(value="/register", method=RequestMethod.POST) @ResponseBody public TaotaoResult createUser(TbUser user) { try { TaotaoResult result = userService.createUser(user); return result; } catch (Exception e) { return TaotaoResult.build(500, ExceptionUtil.getStackTrace(e)); } } @RequestMapping(value="/login", method=RequestMethod.POST) @ResponseBody public TaotaoResult userLogin(String username, String password, HttpServletRequest request,HttpServletResponse response) { try { TaotaoResult result = userService.userLogin(username, password,request,response); return result; } catch (Exception e) { e.printStackTrace(); return TaotaoResult.build(500, ExceptionUtil.getStackTrace(e)); } } @RequestMapping("/token/{token}") @ResponseBody public Object getUserByToken(@PathVariable String token, String callback) { TaotaoResult result = null; try { result = userService.getUserByToken(token); } catch (Exception e) { e.printStackTrace(); result = TaotaoResult.build(500, ExceptionUtil.getStackTrace(e)); } //判断是否为jsonp调用 if (StringUtils.isBlank(callback)) { return result; } else { MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(result); mappingJacksonValue.setJsonpFunction(callback); return mappingJacksonValue; } } }
Service层
@Service public class UserServiceImpl implements UserService { @Autowired private TbUserMapper userMapper; @Override public TaotaoResult checkData(String content, Integer type) { //创建查询条件 TbUserExample example = new TbUserExample(); Criteria criteria = example.createCriteria(); //对数据进行校验:1、2、3分别代表username、phone、email //用户名校验 if (1 == type) { criteria.andUsernameEqualTo(content); //电话校验 } else if ( 2 == type) { criteria.andPhoneEqualTo(content); //email校验 } else { criteria.andEmailEqualTo(content); } //执行查询 List<TbUser> list = userMapper.selectByExample(example); if (list == null || list.size() == 0) { return TaotaoResult.ok(true); } return TaotaoResult.ok(false); } @Override public TaotaoResult createUser(TbUser user) { user.setUpdated(new Date()); user.setCreated(new Date()); //md5加密 user.setPassword(DigestUtils.md5DigestAsHex(user.getPassword().getBytes())); userMapper.insert(user); return TaotaoResult.ok(); } @Autowired private JedisClient jedisClient; @Value("${REDIS_USER_SESSION_KEY}") private String REDIS_USER_SESSION_KEY; @Override public TaotaoResult userLogin(String username, String password, HttpServletRequest request,HttpServletResponse response) { // TODO Auto-generated method stub TbUserExample example = new TbUserExample(); Criteria criteria = example.createCriteria(); criteria.andUsernameEqualTo(username); List<TbUser> list = userMapper.selectByExample(example); //如果没有此用户名 if (null == list || list.size() == 0) { return TaotaoResult.build(400, "用户名或密码错误"); } TbUser user = list.get(0); //比对密码 if (!DigestUtils.md5DigestAsHex(password.getBytes()).equals(user.getPassword())) { return TaotaoResult.build(400, "用户名或密码错误"); } //生成token String token = UUID.randomUUID().toString(); //保存用户之前,把用户对象中的密码清空。 user.setPassword(null); //把用户信息写入redis jedisClient.set(REDIS_USER_SESSION_KEY + ":" + token, JsonUtils.objectToJson(user)); //设置session的过期时间 jedisClient.expire(REDIS_USER_SESSION_KEY + ":" + token, 1800); CookieUtils.setCookie(request, response, "TT_TOKEN", token); //返回token return TaotaoResult.ok(token); } //51d72eac-712d-46ed-8cfd-4b27a5eaf268 @Override public TaotaoResult getUserByToken(String token) { //根据token从redis中查询用户信息 String json = jedisClient.get(REDIS_USER_SESSION_KEY + ":" + token); //判断是否为空 if (StringUtils.isBlank(json)) { return TaotaoResult.build(400, "此session已经过期,请重新登录"); } //更新过期时间 jedisClient.expire(REDIS_USER_SESSION_KEY + ":" + token, 1800); //返回用户信息 return TaotaoResult.ok(JsonUtils.jsonToPojo(json, TbUser.class)); } }
Dao层
MySQL使用MyBatis生成的代码
Redis的DAO层代码如下
public class JedisClientImpl implements JedisClient { @Autowired private JedisPool jedisPool; @Override public String get(String key) { Jedis jedis = jedisPool.getResource(); String string = jedis.get(key); jedis.close(); return string; } @Override public String set(String key, String value) { Jedis jedis = jedisPool.getResource(); String string = jedis.set(key, value); jedis.close(); return string; } @Override public String hget(String hkey, String key) { Jedis jedis = jedisPool.getResource(); String string = jedis.hget(hkey, key); jedis.close(); return string; } @Override public Long hset(String hkey, String key, String value) { Jedis jedis = jedisPool.getResource(); Long result = jedis.hset(hkey, key, value); jedis.close(); return result; } @Override public long incr(String key) { Jedis jedis = jedisPool.getResource(); Long result = jedis.incr(key); jedis.close(); return result; } @Override public long expire(String key, int second) { Jedis jedis = jedisPool.getResource(); Long result = jedis.expire(key,second); jedis.close(); return result; } @Override public long ttl(String key) { Jedis jedis = jedisPool.getResource(); Long result = jedis.ttl(key); jedis.close(); return result; } @Override public long del(String key) { // TODO Auto-generated method stub Jedis jedis=jedisPool.getResource(); long result=jedis.del(key); jedis.close(); return result; } }
除了服务,用户系统还需要单独提供注册页面和登录页面的服务。
访问相关服务会请求转发到register.jsp和login.jsp。
@Controller @RequestMapping("/page") public class PageController { @RequestMapping("/register") public String showRegister() { return "register"; } @RequestMapping("/login") public String showLogin(String redirect,Model model) { model.addAttribute("redirect",redirect); return "login"; } }
在门户系统点击登录连接跳转到登录页面。
登录成功后,跳转到门户系统的首页,在门户系统中需要从cookie中 把token取出来。
所以必须在登录成功后把token写入cookie。并且cookie的值必须在系统之间能共享。
设置Cookie:
比如设置你的Cookie的domain为 .yourdomain.com
设置Cookie的路径:/
这里 使用的是locoalhost,就不需要设置domain了。
上面UserServiceImpl中的userLogin方法将token和对应的用户信息放到Redis数据库中。
并且设置Cookie,“TT_TOKEN"值就是token。
其他用户可以登录的地方如何知道已经登录。
如下面代码,页面加载的时候,获取Cookie中”TT_TOKEN“的值,也就是用户token,使用jsonp方法
访问token服务。
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(); });
使用拦截器拦截必须登录才可以使用的页面
springmvc.xml中配置需要拦截的路径
<!-- 拦截器配置 --> <mvc:interceptors> <mvc:interceptor> <!-- 拦截订单类请求 --> <mvc:mapping path="/item/**"/> <bean class="com.taotao.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors>
拦截器代码
@Component public class LoginInterceptor implements HandlerInterceptor { @Autowired private UserService userService; @Override public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception { // TODO Auto-generated method stub } @Override public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) throws Exception { // TODO Auto-generated method stub } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2) throws Exception { // 在Handler执行之前处理 // 判断用户是否登录 // 从cookie中取token String token = CookieUtils.getCookieValue(request, "TT_TOKEN"); // 根据token换取用户信息,调用sso系统的接口。 TbUser user = userService.getUserByToken(token); // 取不到用户信息 if (null == user) { // 跳转到登录页面,把用户请求的url作为参数传递给登录页面。 response.sendRedirect( "http://localhost:8084/page/login" + "?redirect=" + request.getRequestURL()); // 返回false return false; } // 取到用户信息,放行 // 返回值决定handler是否执行。true:执行,false:不执行。 return true; } }