单点登陆解决方案以及代码分析与实现

本文介绍单点登录(SSO)的概念及其实现方式,包括如何通过Redis存储用户信息来提升查询效率,并利用令牌机制实现跨应用的身份验证。

1.什么是单点登陆

同一家公司不同应用使用同一套登陆系统叫做单点登陆系统

2.单点登陆的引出

 比如QQ号可以登陆很多游戏,而且这些游戏都是腾讯公司的,所以不再开发新的登陆模块,QQ就可以登陆多个应用.在写的过程中,我们注重用户体验,考虑用户体验的时候我们的用户名和密码的判断都是很精准的。没有用户名或者密码字样的出现。

3.代码实现

/**
* 登陆
* @param username 帐号
* @param password 密码
* @param ReturnUrl  回调地址
* @param request   浏览器--服务器
* @param response  服务器--浏览器
* @param model  springmvc模型
* @return  请求转发至登陆页面或重定向到回调地址
*/
@RequestMapping(value = "/Login.aspx", method = RequestMethod.POST)
    public String login(String username, String password, String ReturnUrl, HttpServletRequest request,HttpServletResponse response, Model model) {
        // 用户名不能为空
        if (null != username) {
            // 密码不能为空
            if (null != password) {
                // 用户名必须正确
                Buyer buyer = buyerService.selectBuyerByUserName(username);
                if (buyer != null) {
                    // 密码必须正确
                    if (buyer.getPassword().equals(encodingPassword(password))) {
                        // TODO 密码不正确3次之后出现  验证码 不能为空 验证码 必须正确
                        // 保存用户信息到远程session 单点登陆解决方案
                        sessionProviderService.setAttributeForUserName    
            (RequestUtils.getCSESSIONID(request,response),buyer.getUsername());
                        return "redirect:" + ReturnUrl;
                    } else {
                        model.addAttribute("error", "用户名必须正确");
                    }
                } else {
                    model.addAttribute("error", "用户名必须正确");
                }
            } else {
                model.addAttribute("error", "密码不能为空");
            }
        } else {
            model.addAttribute("error", "用户名不能为空");
        }
        return "login";
    }

同样在分布式架构中 在数据库中查询用户名也不是一个好的选择 ,因为不再是一个数据库,后期会搭建数据库集群,数据的存放是根据id存放的,在数据库中直接查询 用户名必须正确 buyerService.selectBuyerByUserName(username); 这一条SQL语句是十分浪费性能的,用户姓名是一个varchar字段,首先是不能建立比较好的索引策略,同时前台也无法传入正确的用户id。我们应该根据用户的id去查询用户,这样只会查询相应的数据库而不会全库扫描。
用户名查询解决方案


    @Override
    public Buyer selectBuyerByUserName(String username) {
        Buyer buyer = null;
        // redis 中查找用户id
        String id = jedis.hget("user", username);
        if (id != username) {
            // 用户查找对象
            buyer = buyerDao.selectByPrimaryKey(Long.parseLong(id));
        }
        return buyer;
    }

redis 中存放的是用户的类型是哈希 key 是用户名称 value 是用户的id 因为redis 是单线程并且是一个nosql数据库,查询速度非常的快,因此将用户存入redis 是在用户注册时候进行的。
redis 中命令如下 hmset username zhangsan 1

/**
 * 令牌工具类
 * @author ZhuPengWei
 * @date    2017年11月7日
 */
public class RequestUtils {

    /**
     * 生成令牌
     * @param request    浏览器-服务器
     * @param response 服务器-浏览器  
     * @return  令牌
     */
    public static String getCSESSIONID(HttpServletRequest request, HttpServletResponse response) {
        // 浏览器中有没有对应cookie CSSESIONID
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            if (Constant.COOKIE_CSSESIONID.equals(cookie.getName())) {
                // 存在cookie
                return cookie.getValue();
            }
        }
        // 没有 创建一个令牌
        String csession = UUID.randomUUID().toString().replaceAll("-", "");
        Cookie cookie = new Cookie(Constant.COOKIE_CSSESIONID, csession);
        cookie.setPath("/");
        // 0立即失效 -1关闭浏览器失效
        cookie.setMaxAge(-1);
        // 写在浏览器之中
        response.addCookie(cookie);
        return csession;
    }
}

和大多公司单点登录解决方案一样,模拟request.getSession(),做出了一个假的session 存入了cookie中,
这样 服务器可以通过这个令牌在redis中获取数据或者设置数据了

/**
 * Session业务层接口
 *     提供session
 *     获取session
 * @author ZhuPengWei
 * @date    2017年11月7日
 */
public interface SessionProviderService {
    /**
     * 设置远程session 
     * @param key  令牌
     * @param userName 用户名
     */
    public void setAttributeForUserName(String key, String userName);
    /**
     * 从redis中取出用户名
     * @param key 令牌
     */
    public String getAttributeUserNameByRedis(String key);
}
package open.shopping.service.user;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import open.shopping.common.web.Constant;
import redis.clients.jedis.Jedis;

/**
 * Session业务层实现类
 *     提供session
 *     获取session
 * @author ZhuPengWei
 * @date    2017年11月7日
 */
@Service("sessionProviderService")
public class SessionProviderServiceImpl implements SessionProviderService {

    @Autowired
    private Jedis jedis;

    @Override
    public void setAttributeForUserName(String key, String userName) {
        jedis.set(Constant.USER_SESSION + key, userName);
        // 设置存活时间为30分钟
        jedis.expire(Constant.USER_SESSION + key, 60 * 30);
        jedis.close();
    }

    @Override
    public String getAttributeUserNameByRedis(String key) {
        String userName = jedis.get(Constant.USER_SESSION + key);
        if (null != userName) {
            // 设置存活时间为30分钟
            jedis.expire(Constant.USER_SESSION + key, 60 * 30);
        }
        jedis.close();
        return userName;
    }
}

是不是觉得单点登录其实也很简单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值