sso单点登录系统

1 课程计划

第十一天:

  1. sso注册功能实现
  2. sso登录功能实现
  3. 通过token获得用户信息
  4. Ajax跨域请求(jsonp)
     

2 Sso系统工程搭建

需要创建一个sso服务工程,可以参考e3-manager创建。

e3-sso(pom聚合工程)

   |--e3-sso-interface(jar)

   |--e3-sso-Service(war)

e3-sso-web

复制pom文件依赖到e3-sso工程:

 <dependencies>
        <dependency>
            <groupId>cn.e3mall</groupId>
            <artifactId>e3-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
  </dependencies>
  <!-- 配置tomcat插件 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <configuration>
                    <path>/</path>
                    <port>8087</port>
                </configuration>
            </plugin>
        </plugins>
    </build>

复制pom文件依赖到e3-sso-interface工程:

<dependencies>
        <dependency>
            <groupId>cn.e3mall</groupId>
            <artifactId>e3-manager-pojo</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

复制pom文件依赖到e3-sso-service工程:

<dependencies>
        <dependency>
            <groupId>cn.e3mall</groupId>
            <artifactId>e3-manager-dao</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>cn.e3mall</groupId>
            <artifactId>e3-sso-interface</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- spring的依赖 -->
        <!-- 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-jms</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <!-- dubbo相关 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <exclusions>
                <!-- 排除dubbo自带的spring 防止冲突 -->
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring</artifactId>
                </exclusion>
                <!-- 排除dubbo自带的netty 防止冲突 -->
                <exclusion>
                    <groupId>org.jboss.netty</groupId>
                    <artifactId>netty</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
        </dependency>
    </dependencies>

复制配置文件到e3-sso-service工程:

1.清空resource.properties内容

2.修改applicationContext-service.xml配置文件:

3.创建包:

4.修改applicationContext-trans.xml事务配置文件:

5.复制web.xml文件到e3-sso-service工程:

复制pom文件依赖到e3-sso-web工程:

  <dependencies>
          <dependency>
            <groupId>cn.e3mall</groupId>
            <artifactId>e3-sso-interface</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
          <!-- JSP相关 -->
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</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>
        <!-- spring的依赖 -->
        <!-- 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-jms</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <!-- dubbo相关 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.jboss.netty</groupId>
                    <artifactId>netty</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
  </dependencies>
  <build>
        <plugins>
            <!-- 配置Tomcat插件 -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <configuration>
                    <path>/</path>
                    <port>8088</port>
                </configuration>
            </plugin>
        </plugins>
    </build>

1.复制配置文件到e3-sso-web工程:

2.清空resource.properties配置文件

3.修改springmvc.xml配置文件:

4.创建包:

5复制web.xml文件:

加入登录注册静态页面到e3-sso-web工程:

根绝页面请求方式修改web.xml拦截方式:

修改springmvc.xml配置静态资源映射:

安装e3-sso工程

3 服务接口实现

3.1 检查数据是否可用

3.1.1 功能分析

请求的url:/user/check/{param}/{type}

参数:从url中取参数1、String param(要校验的数据)2、Integer type(校验的数据类型)

响应的数据:json数据。e3Result,封装的数据校验的结果true:成功false:失败。

业务逻辑:

  1. 从tb_user表中查询数据
  2. 查询条件根据参数动态生成。
  3. 判断查询结果,如果查询到数据返回false。
  4. 如果没有返回true。
  5. 使用e3Result包装,并返回。

3.1.2 Dao层

从tb_user表查询。可以使用逆向工程。

3.1.3 Service

参数:

  1. 要校验的数据:String param
  2. 数据类型:int type(1、2、3分别代表username、phone、email)

返回值:e3Result

@Service

public class UserServiceImpl implements UserService {

 

      @Autowired

      private TbUserMapper userMapper;

     

      @Override

      public e3Result checkData(String param, int type) {

            // 1、从tb_user表中查询数据

            TbUserExample example = new TbUserExample();

            Criteria criteria = example.createCriteria();

            // 2、查询条件根据参数动态生成。

            //1、2、3分别代表username、phone、email

            if (type == 1) {

                  criteria.andUsernameEqualTo(param);

            } else if (type == 2) {

                  criteria.andPhoneEqualTo(param);

            } else if (type == 3) {

                  criteria.andEmailEqualTo(param);

            } else {

                  return e3Result.build(400, "非法的参数");

            }

            //执行查询

            List<TbUser> list = userMapper.selectByExample(example);

            // 3、判断查询结果,如果查询到数据返回false。

            if (list == null || list.size() == 0) {

                  // 4、如果没有返回true。

                  return e3Result.ok(true);

            }

            // 5、使用e3Result包装,并返回。

            return e3Result.ok(false);

      }

 

}

发布服务

3.1.4 表现层

需要在e3-sso-web中实现。

引用服务

Controller

请求的url:/user/check/{param}/{type}

参数:从url中取参数1、String param(要校验的数据)2、Integer type(校验的数据类型)

响应的数据:json数据。e3Result,封装的数据校验的结果true:成功false:失败。

@Controller

public class UserController {

 

      @Autowired

      private UserService userService;

     

      @RequestMapping("/user/check/{param}/{type}")

      @ResponseBody

      public e3Result checkData(@PathVariable String param, @PathVariable Integer type) {

            e3Result e3Result = userService.checkData(param, type);

            return e3Result;

      }

}

3.2 用户注册

3.2.1 功能分析

请求的url:/user/register

参数:表单的数据:username、password、phone、email

返回值:json数据。e3Result

接收参数:使用TbUser对象接收。

请求的方法:post

业务逻辑:

  1. 使用TbUser接收提交的请求。
  2. 补全TbUser其他属性。
  3. 密码要进行MD5加密。
  4. 把用户信息插入到数据库中。
  5. 返回e3Result。

3.2.2 Dao层

可以使用逆向工程。

3.2.3 Service层

参数:TbUser

返回值:e3Result

@Override

      public e3Result createUser(TbUser user) {

            // 1、使用TbUser接收提交的请求。

           

            if (StringUtils.isBlank(user.getUsername())) {

                  return e3Result.build(400, "用户名不能为空");

            }

            if (StringUtils.isBlank(user.getPassword())) {

                  return e3Result.build(400, "密码不能为空");

            }

            //校验数据是否可用

            e3Result result = checkData(user.getUsername(), 1);

            if (!(boolean) result.getData()) {

                  return e3Result.build(400, "此用户名已经被使用");

            }

            //校验电话是否可以

            if (StringUtils.isNotBlank(user.getPhone())) {

                  result = checkData(user.getPhone(), 2);

                  if (!(boolean) result.getData()) {

                        return e3Result.build(400, "此手机号已经被使用");

                  }

            }

            //校验email是否可用

            if (StringUtils.isNotBlank(user.getEmail())) {

                  result = checkData(user.getEmail(), 3);

                  if (!(boolean) result.getData()) {

                        return e3Result.build(400, "此邮件地址已经被使用");

                  }

            }

            // 2、补全TbUser其他属性。

            user.setCreated(new Date());

            user.setUpdated(new Date());

            // 3、密码要进行MD5加密。

            String md5Pass = DigestUtils.md5DigestAsHex(user.getPassword().getBytes());

            user.setPassword(md5Pass);

            // 4、把用户信息插入到数据库中。

            userMapper.insert(user);

            // 5、返回e3Result

            return e3Result.ok();

      }

发布服务

3.2.4 表现层

引用服务。

 

Controller:

请求的url:/user/register

参数:表单的数据:username、password、phone、email

返回值:json数据。e3Result

接收参数:使用TbUser对象接收。

请求的方法:post

@RequestMapping(value="/user/register", method=RequestMethod.POST)

      @ResponseBody

      public e3Result register(TbUser user) {

            e3Result result = userService.createUser(user);

            return result;

      }

3.2.5 测试

可以使用restclient-ui-3.5-jar-with-dependencies.jar测试接口。

表单提交的content-type:application/x-www-form-urlencoded

3.3 用户登录

3.3.1 功能分析

请求的url:/user/login

请求的方法:POST

参数:username、password,表单提交的数据。可以使用方法的形参接收。

返回值:json数据,使用e3Result包含一个token。

业务逻辑:

登录的业务流程:

登录的处理流程:

  1. 登录页面提交用户名密码。
  2. 登录成功后生成token。Token相当于原来的jsessionid,字符串,可以使用uuid。
  3. 把用户信息保存到redis。Key就是token,value就是TbUser对象转换成json。
  4. 使用String类型保存Session信息。可以使用“前缀:token”为key
  5. 设置key的过期时间。模拟Session的过期时间。一般半个小时。
  6. 把token写入cookie中。
  7. Cookie需要跨域。例如www.e3.com\sso.e3.com\order.e3.com,可以使用工具类。
  8. Cookie的有效期。关闭浏览器失效。
  9. 登录成功。

注册成功后去登录:

手动选择去登录:

LoginController:

不能使用userid作为redis缓存的key,换台电脑userid相同,但因为换了电脑,用户并没有登录。不能判断出用户是否登录。

应该和tomcat session一样,使用sessionid作为key,把sessionid存入cookie中。每次服务端从cookie中取sessionid去redis中查询,判断用户是否登录和是否过期,如果没过期需要重置过期时间。key是sessionid(图中的token),使用uuid生成key不会重复,value是用户信息。需要和session一样设置过期时间。

加入redis依赖:

添加redis配置文件:

3.3.2 Dao层

查询tb_user表。单表查询。可以使用逆向工程。

3.3.3 Service层

resource.properties:

参数:

  1. 用户名:String username
  2. 密码:String password

返回值:e3Result,包装token。

业务逻辑:

1、判断用户名密码是否正确。

2、登录成功后生成token。Token相当于原来的jsessionid,字符串,可以使用uuid。

3、把用户信息保存到redis。Key就是token,value就是TbUser对象转换成json。

4、使用String类型保存Session信息。可以使用“前缀:token”为key

5、设置key的过期时间。模拟Session的过期时间。一般半个小时。

6、返回e3Result包装token。

@Override

      public e3Result login(String username, String password) {

            // 1、判断用户名密码是否正确。

            TbUserExample example = new TbUserExample();

            Criteria criteria = example.createCriteria();

            criteria.andUsernameEqualTo(username);

            //查询用户信息

            List<TbUser> list = userMapper.selectByExample(example);

            if (list == null || list.size() == 0) {

                  return e3Result.build(400, "用户名或密码错误");

            }

            TbUser user = list.get(0);

            //校验密码

            if (!user.getPassword().equals(DigestUtils.md5DigestAsHex(password.getBytes()))) {

                  return e3Result.build(400, "用户名或密码错误");

            }

            // 2、登录成功后生成token。Token相当于原来的jsessionid,字符串,可以使用uuid

            String token = UUID.randomUUID().toString();

            // 3、把用户信息保存到redis。Key就是token,value就是TbUser对象转换成json

            // 4、使用String类型保存Session信息。可以使用“前缀:token”为key

            user.setPassword(null);

            jedisClient.set(USER_INFO + ":" + token, JsonUtils.objectToJson(user));

            // 5、设置key的过期时间。模拟Session的过期时间。一般半个小时。

            jedisClient.expire(USER_INFO + ":" + token, SESSION_EXPIRE);

            // 6、返回e3Result包装token。

            return e3Result.ok(token);

      }

发布服务

3.3.4 表现层

引用服务:

Controller

请求的url:/user/login

请求的方法:POST

参数:username、password,表单提交的数据。可以使用方法的形参接收。

HttpServletRequest、HttpServletResponse

返回值:json数据,使用e3Result包含一个token。

业务逻辑:

      1.接收两个参数。

      2.调用Service进行登录。

      3.从返回结果中取token,写入cookie。Cookie要跨域。

Cookie二级域名跨域需要设置:

1)setDomain,设置一级域名:

.itcatst.cn

.e3.com

.e3.com.cn

2)setPath。设置为“/”

 

工具类放到e3-common工程中(工具类的编码指的是中文转码)。

      4.响应数据。Json数据。e3Result,其中包含Token。

@RequestMapping(value="/user/login", method=RequestMethod.POST)

      @ResponseBody

      public e3Result login(String username, String password,

                  HttpServletRequest request, HttpServletResponse response) {

            // 1、接收两个参数。

            // 2、调用Service进行登录。

            e3Result result = userService.login(username, password);

            // 3、从返回结果中取token,写入cookie。Cookie要跨域。

            String token = result.getData().toString();

            CookieUtils.setCookie(request, response, COOKIE_TOKEN_KEY, token);

            // 4、响应数据。Json数据。e3Result,其中包含Token。

            return result;

           

      }

安装e3-common工程和e3-sso-interface工程

实现首页登录注册:

3.4 通过token查询用户信息

3.4.1 功能分析

请求的url:/user/token/{token}

参数:String token需要从url中取。

返回值:json数据。使用e3Result包装Tbuser对象。

业务逻辑:

  1. 从url中取参数。
  2. 根据token查询redis。
  3. 如果查询不到数据。返回用户已经过期。
  4. 如果查询到数据,说明用户已经登录。
  5. 需要重置key的过期时间。
  6. 把json数据转换成TbUser对象,然后使用e3Result包装并返回。

3.4.2 Dao层

定义接口。

3.4.3 Service层

参数:String token

返回值:e3Result

@Override

      public e3Result getUserByToken(String token) {

            // 2、根据token查询redis

            String json = jedisClient.get(USER_INFO + ":" + token);

            if (StringUtils.isBlank(json)) {

                  // 3、如果查询不到数据。返回用户已经过期。

                  return e3Result.build(400, "用户登录已经过期,请重新登录。");

            }

            // 4、如果查询到数据,说明用户已经登录。

            // 5、需要重置key的过期时间。

            jedisClient.expire(USER_INFO + ":" + token, SESSION_EXPIRE);

            // 6、把json数据转换成TbUser对象,然后使用e3Result包装并返回。

            TbUser user = JsonUtils.jsonToPojo(json, TbUser.class);

            return e3Result.ok(user);

      }

发布服务:

3.4.4 表现层

请求的url:/user/token/{token}

参数:String token需要从url中取。

返回值:json数据。使用e3Result包装Tbuser对象。

      @RequestMapping("/user/token/{token}")

      @ResponseBody

      public e3Result getUserByToken(@PathVariable String token) {

            e3Result result = userService.getUserByToken(token);

            return result;

      }

引用服务:

安装e3-sso-interface工程

3.4.5 安全退出

作业

需要根据token删除redis中的key。

4 登录注册页面实现

4.1 注册功能

第一步:把静态页面添加到工程中。

第二步:展示页面。

请求的url:

登录:/page/login

注册:/page/register

参数:无

返回结果:逻辑视图String

@Controller

public class PageController {

 

      @RequestMapping("/page/register")

      public String showRegister() {

            return "register";

      }

     

      @RequestMapping("/page/login")

      public String showLogin() {

            return "login";

      }

}

第三步:js处理。

4.2 登录功能

参考login.jsp

5 登录注册页面整合首页

5.1 首页跳转到登录、注册页面

5.2 首页展示用户名

  1. 当用户登录成功后,在cookie中有token信息。
  2. 从cookie中取token根据token查询用户信息。
  3. 把用户名展示到首页。

 

方案一:在Controller中取cookie中的token数据,调用sso服务查询用户信息。

方案二:当页面加载完成后使用js取token的数据,使用ajax请求查询用户信息。

 

 

问题:服务接口在sso系统中。Sso.e3.com(localhost:8088),在首页显示用户名称,首页的域名是www.e3.com(localhost:8082),使用ajax请求跨域了。

 

Js不可以跨域请求数据。

什么是跨域:

  1. 域名不同
  2. 域名相同端口不同。

 

解决js的跨域问题可以使用jsonp。

 

Jsonp不是新技术,跨域的解决方案。使用js的特性绕过跨域请求。Js可以跨域加载js文件。

5.3 Jsonp原理

把mycall前台的方法名传到后台,给callback字段。后台接受callback字段。写成js语句,调用前端方法。

5.4 Json实现

5.4.1 客户端

使用jQuery。

jquery.cookie.js:取cookie数据

e3mall.js:

 

5.4.2 服务端

  1. 接收callback参数,取回调的js的方法名。
  2. 业务逻辑处理。
  3. 响应结果,拼接一个js语句。

spring4.1前的版本:

spring4.2以后的版本:

猜你喜欢

转载自blog.csdn.net/etna_hh/article/details/82943982