TT_单点登录系统SSO实现
1. 计划
1、 实现单点登录系统
2、 实现用户的登录功能
3、 实现用户的注册功能
2. 单点登录系统分析
2.1. 什么是SSO
SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一。
2.2. 原来的登录逻辑实现
2.2.1. 问题
单台tomcat,以上实现是没有任何问题的,但是我们现在是集群的tomcat,就会存在session共享问题。只要解决session共享问题,登录问题即可解决。
每个系统都有自己的session,不能统一。
2.2.2. 解决session共享问方案
1、 tomcat的session复制
优点:不需要额外开发,只需要搭建tomcat集群即可。
缺点:tomcat 是全局session复制,集群内每个tomcat的session完全同步(也就是任何时候都完全一样的) 在大规模应用的时候,用户过多,集群内tomcat数量过多,session的全局复制会导致集群性能下降, 因此,tomcat的数量不能太多,5个以下为好。
2、 实现单点登录系统,提供服务接口。把session数据存放在redis。
Redis可以设置key的生存时间、访问速度快效率高。
优点:redis存取速度快,不会出现多个节点session复制的问题。效率高。
缺点:需要程序员开发。
2.3. 单点登录系统的流程
3. SSO开发
3.1. 系统架构
3.2. 开发SSO服务
3.2.1. 创建sso服务工程
3.2.2. Pom.xml
Pom.xml文件参考taotao-rest工程的pom文件。
3.2.3. 服务开发
a) 登录接口
b) 注册接口
c) 查询接口
d) 退出登录接口
开发的流程:
1、 确定流程、确定接口内容
2、 提供接口文档
a) 接口地址
b) 入参说明
c) 接口访问方式,get、post
d) 结果输出,说明格式
e) 接口调用的示例
3、 约定联合测试的时间点
4、 发布上线
参考:SSO接口文档.docx
3.3. 开发注册接口
1、 需要对用户提交的数据做校验
2、 对密码做md5加密
3、 对报错异常要做处理
3.3.1. 数据校验接口
1. Mapper
对tb_user表进行单表查询。使用****生成的mapper即可。
2. Service
- @Service
- public class UserRegisterServiceImpl implements UserRegisterService {
- @Autowired
- private TbUserMapper userMapper;
- @Override
- public TaotaoResult checkInfo(String value, String type) throws Exception {
- boolean result = false;
- //type为类型,可选参数1、2、3分别代表username、phone、email
- if ("1".equals(type)) {
- result = checkUserName(value);
- } else if ("2".equals(type)) {
- result = checkPhone(value);
- } else if ("3".equals(type)) {
- result = checkEmail(value);
- }
- //返回结果
- if (result) {
- return TaotaoResult.ok(result);
- }
- return TaotaoResult.build(201, "此数值已经存在");
- }
- private boolean checkUserName(String userName) throws Exception {
- //创建查询条件
- TbUserExample example = new TbUserExample();
- Criteria criteria = example.createCriteria();
- criteria.andUsernameEqualTo(userName);
- List<TbUser> list = userMapper.selectByExample(example);
- //判断结果中是否存在
- if (list == null || list.isEmpty()) {
- return true;
- }
- return false;
- }
- private boolean checkPhone(String phone) throws Exception {
- //创建查询条件
- TbUserExample example = new TbUserExample();
- Criteria criteria = example.createCriteria();
- criteria.andPhoneEqualTo(phone);
- List<TbUser> list = userMapper.selectByExample(example);
- //判断结果中是否存在
- if (list == null || list.isEmpty()) {
- return true;
- }
- return false;
- }
- private boolean checkEmail(String email) throws Exception {
- //创建查询条件
- TbUserExample example = new TbUserExample();
- Criteria criteria = example.createCriteria();
- criteria.andEmailEqualTo(email);
- List<TbUser> list = userMapper.selectByExample(example);
- //判断结果中是否存在
- if (list == null || list.isEmpty()) {
- return true;
- }
- return false;
- }
- }
3. Controller
- @Controller
- @RequestMapping("/user")
- public class UserRegisterController {
- @Autowired
- private UserRegisterService userRegisterService;
- @RequestMapping("/check/{param}/{type}")
- @ResponseBody
- public Object checkInfo(@PathVariable String param, @PathVariable String type, String callback) {
- TaotaoResult result = null;
- try {
- result = userRegisterService.checkInfo(param, type);
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- result = TaotaoResult.build(500, ExceptionUtil.getStackTrace(e));
- }
- //支持jsonp
- if (!StringUtils.isBlank(callback)) {
- MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(result);
- mappingJacksonValue.setJsonpFunction(callback);
- return mappingJacksonValue;
- }
- return result;
- }
- }
3.3.2. 用户注册接口
1. service
- @Override
- public TaotaoResult register(TbUser user) throws Exception {
- //有效性验证
- if (StringUtils.isBlank(user.getUsername())) {
- return TaotaoResult.build(400, "用户名不能为空");
- }
- if (StringUtils.isBlank(user.getPassword())) {
- return TaotaoResult.build(400, "密码不能为空");
- }
- if (StringUtils.isBlank(user.getPhone())) {
- return TaotaoResult.build(400, "手机不能为空");
- }
- //转换md5
- user.setPassword(DigestUtils.md5DigestAsHex(user.getPassword().getBytes()));
- //完善user信息
- user.setCreated(new Date());
- user.setUpdated(new Date());
- //添加到数据库
- userMapper.insert(user);
- return TaotaoResult.ok();
- }
2. Controller
- @RequestMapping(value="/register", method=RequestMethod.POST)
- public TaotaoResult register(TbUser user) {
- TaotaoResult taotaoResult = null;
- try {
- taotaoResult = userRegisterService.register(user);
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- return TaotaoResult.build(500, ExceptionUtil.getStackTrace(e));
- }
- return taotaoResult;
- }
3.4. 开发登录接口
3.4.1. Service
- @Service
- public class UserLoginServiceImpl implements UserLoginService {
- @Autowired
- private TbUserMapper userMapper;
- @Autowired
- private JedisCluster jedisCluster;
- @Value("${USER_TOKEN_KEY}")
- private String USER_TOKEN_KEY;
- @Value("${SESSION_EXPIRE_TIME}")
- private Integer SESSION_EXPIRE_TIME;
- @Override
- public TaotaoResult login(String username, String password) throws Exception {
- //根据用户名查询用户信息
- TbUserExample example = new TbUserExample();
- Criteria criteria = example.createCriteria();
- criteria.andUsernameEqualTo(username);
- List<TbUser> list = userMapper.selectByExample(example);
- if (null == list || list.isEmpty()) {
- return TaotaoResult.build(400, "用户不存在");
- }
- //核对密码
- TbUser user = list.get(0);
- if (!DigestUtils.md5DigestAsHex(password.getBytes()).equals(user.getPassword())) {
- return TaotaoResult.build(400, "密码错误");
- }
- //登录成功,把用户信息写入redis
- //生成一个用户token
- String token = UUID.randomUUID().toString();
- jedisCluster.set(USER_TOKEN_KEY + ":" + token, JsonUtils.objectToJson(user));
- //设置session过期时间
- jedisCluster.expire(USER_TOKEN_KEY + ":" + token, SESSION_EXPIRE_TIME);
- return TaotaoResult.ok(token);
- }
- }
3.4.2. Controller
- @Controller
- @RequestMapping("/user")
- public class UserLoginController {
- @Autowired
- private UserLoginService userLoginService;
- @RequestMapping(value="/login", method=RequestMethod.POST)
- @ResponseBody
- public TaotaoResult login(String username, String password) {
- TaotaoResult result = null;
- try {
- result = userLoginService.login(username, password);
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- return TaotaoResult.build(500, ExceptionUtil.getStackTrace(e));
- }
- return result;
- }
- }
3.5. 根据token查询用户
3.5.1. Service
- @Service
- public class UserTokenServiceImpl implements UserTokenService {
- @Autowired
- private JedisCluster jedisCluster;
- @Value("${USER_TOKEN_KEY}")
- private String USER_TOKEN_KEY;
- @Value("${SESSION_EXPIRE_TIME}")
- private Integer SESSION_EXPIRE_TIME;
- /**
- * 根据token取用户信息
- * <p>Title: getUserByToken</p>
- * <p>Description: </p>
- * @param token
- * @return
- * @throws Exception
- * @see com.taotao.sso.service.UserTokenService#getUserByToken(java.lang.String)
- */
- @Override
- public TaotaoResult getUserByToken(String token) throws Exception {
- //从redis中取用户信息
- String userJson = jedisCluster.get(USER_TOKEN_KEY + ":" + token);
- if (StringUtils.isBlank(userJson)) {
- return TaotaoResult.build(400, "该用户已过期");
- }
- //把json转换成user对象
- TbUser user = JsonUtils.jsonToPojo(userJson, TbUser.class);
- //更新用户有效期
- jedisCluster.expire(USER_TOKEN_KEY + ":" + token, SESSION_EXPIRE_TIME);
- return TaotaoResult.ok(user);
- }
- }
3.5.2. Controller
- @Controller
- @RequestMapping("/user")
- public class UserTokenController {
- @Autowired
- private UserTokenService userTokenService;
- @RequestMapping("/token/{token}")
- @ResponseBody
- public Object getUserByToken(@PathVariable String token, String callback) {
- TaotaoResult result = null;
- try {
- result = userTokenService.getUserByToken(token);
- } catch (Exception e) {
- e.printStackTrace();
- result = TaotaoResult.build(500, ExceptionUtil.getStackTrace(e));
- }
- //判断是否为jsonp调用
- if (!StringUtils.isBlank(callback)) {
- MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(result);
- mappingJacksonValue.setJsonpFunction(callback);
- return mappingJacksonValue;
- }
- return result;
- }
- }
4. 用户注册
4.1. 需求分析
用户注册时需要调用taotao-sso的服务检查用户的有效性以及用户、手机是否存在。检查通过后提交表单至taotao-portal的controller,由taotao-portal调用taotao-sso的服务注册用户。
4.2. 有效性检查
4.3. 用户注册实现
4.3.1. Service
- @Service
- public class UserServiceImpl implements UserService {
- @Value("${SSO_BASE_URL}")
- private String SSO_BASE_URL;
- @Value("${REGISTER_USER_URL}")
- private String REGISTER_USER_URL;
- @Override
- public TaotaoResult register(TbUser user) {
- //请求参数
- Map<String, String> param = new HashMap<>();
- param.put("username", user.getUsername());
- param.put("password", user.getPassword());
- param.put("phone", user.getPhone());
- param.put("email", user.getEmail());
- //提交用户信息
- String stringResult = HttpClientUtil.doPost(SSO_BASE_URL + REGISTER_USER_URL, param);
- TaotaoResult result = TaotaoResult.format(stringResult);
- return result;
- }
- }
4.3.2. Controller
- @RequestMapping("/doregister")
- @ResponseBody
- public TaotaoResult doRegister(TbUser user) throws Exception {
- TaotaoResult result = userService.register(user);
- return result;
- }
5. 用户登录
5.1. 需求
前台页面提交用户名、密码至taotao-portal,由taotao-poratl调用sso系统的服务做登录处理。登录成功后返回用户token数据。taotao-portal把token写入cookie,返回登录成功。页面接收到成功信息后,跳转至商品列表页面。商品列表页面从cookie中取出token信息,根据token查询用户并显示到首页。
5.2. Service
- public TaotaoResult login(String username, String password,
- HttpServletRequest request, HttpServletResponse response) {
- //请求参数
- Map<String, String> param = new HashMap<>();
- param.put("username", username);
- param.put("password", password);
- //登录处理
- String stringResult = HttpClientUtil.doPost(REGISTER_USER_URL + USER_LOGIN_URL, param);
- TaotaoResult result = TaotaoResult.format(stringResult);
- //登录出错
- if (result.getStatus() != 200) {
- return result;
- }
- //登录成功后把取token信息,并写入cookie
- String token = (String) result.getData();
- //写入cookie
- CookieUtils.setCookie(request, response, "TT_TOKEN", token);
- //返回成功
- return result;
- }
5.3. Controller
- /**
- * 登录处理
- * <p>Title: doLogin</p>
- * <p>Description: </p>
- * @param username
- * @param password
- * @param request
- * @param response
- * @return
- */
- @RequestMapping("/dologin")
- @ResponseBody
- public TaotaoResult doLogin(String username, String password,
- HttpServletRequest request, HttpServletResponse response) {
- TaotaoResult result = userService.login(username, password, request, response);
- return result;
- }
5.4. 显示当前的用户名
Taotao.js
- 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://localhost:8084/user/logout.html\" class=\"link-logout\">[退出]</a>";
- $("#loginbar").html(html);
- }
- }
- });
- }
- }
- $(function(){
- // 查看是否已经登录,如果已经登录查询登录信息
- TT.checkLogin();
- });
6. 拦截器
6.1. 拦截器流程
6.2. 根据token取用户信息
需要在UserSevice中添加一个根据token取用户对象的方法。
- @Override
- public TbUser getUserByToken(String token) {
- //根据token取用户信息
- String stringResult = HttpClientUtil.doGet(SSO_BASE_URL + GET_USER_BY_TOKEN_URL + token);
- //把返回结果转换成java对象
- TaotaoResult taotaoResult = TaotaoResult.formatToPojo(stringResult, TbUser.class);
- //判断返回状态
- if (taotaoResult.getStatus() == 200) {
- //取User对象
- TbUser user = (TbUser) taotaoResult.getData();
- return user;
- }
- return null;
- }
6.3. 用户登录功能实现登录后跳转
6.3.1. 接收跳转url参数
6.3.2. 页面保存此url
login.jsp
6.4. 拦截器实现
- /**
- * 在提交订单之前拦截,判断用户是否登录
- * <p>Title: OrderInterceptor</p>
- * <p>Description: </p>
- * <p>Company: www.itcast.com</p>
- * @author入云龙
- * @date2015年8月6日上午10:43:04
- * @version 1.0
- */
- public class OrderInterceptor implements HandlerInterceptor {
- @Autowired
- private UserService userService;
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
- throws Exception {
- //取用户token
- String token = CookieUtils.getCookieValue(request, "TT_TOKEN");
- //判断是否为空
- if (StringUtils.isBlank(token)) {
- //如果为空就是未登录状态
- //跳转到登录页面
- response.sendRedirect(userService.getLoginUrl() + "?redirect=" + getBaseURL(request));
- return false;
- } else {
- //如果能取到token说明用户可能已经登录
- //从sso中取用户信息,判断用户是否登录
- TbUser user = userService.getUserByToken(token);
- //判断用户是否过期
- if (user == null) {
- //跳转到登录页面
- response.sendRedirect(userService.getLoginUrl() + "?redirect=" + getBaseURL(request));
- return false;
- } else {
- //用户已经登录,把用户信息放到request中
- request.setAttribute("user", user);
- }
- }
- //放行
- return true;
- }
- @Override
- public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
- ModelAndView modelAndView) throws Exception {
- // TODO Auto-generated method stub
- }
- @Override
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
- throws Exception {
- // TODO Auto-generated method stub
- }
- private String getBaseURL(HttpServletRequest request) {
- String url = request.getScheme()
- + "://"
- + request.getServerName()
- + ":"
- + request.getServerPort()
- + request.getContextPath()
- + request.getRequestURI();
- return url;
- }
- }
6.5. 配置拦截器
在springmvc中添加如下配置:
- <!-- 拦截器配置 -->
- <mvc:interceptors>
- <mvc:interceptor>
- <!-- 拦截订单类请求 -->
- <mvc:mapping path="/order/**"/>
- <bean class="com.taotao.portal.interceptor.OrderInterceptor"/>
- </mvc:interceptor>
- </mvc:interceptors>