【SSO】单点登录系统实现

一、前言

      小编在前一篇博客中向大家介绍了使用单点登录的演变过程,最后一步的时候小编向大家展示了分布式架构。其中就用到了单点登录系统。这篇博客继续接上一篇博客,实现一下单点登录系统。

二、环境准备

  • 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真是一个非常好用的缓存技术。在很多方面都做了出色的表现。另外,就是这种产生分布式,把登录系统单独提取出来,这种思想有是很棒的。加油!


感谢博主,本文转自https://blog.csdn.net/kisscatforever/article/details/76468326

猜你喜欢

转载自blog.csdn.net/albenxie/article/details/80266348