欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

SSO单点登录

程序员文章站 2023-10-28 23:07:58
单点登录系统介绍 单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。 单点登录的实现原理 实现单点登录,就是解决如何产生和存储那个信任,再就是其他系统如何验证这个 ......

单点登录系统介绍

单点登录(single sign on),简称为 sso,是目前比较流行的企业业务整合的解决方案之一。sso 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

 

SSO单点登录

 

 

单点登录的实现原理

实现单点登录,就是解决如何产生和存储那个信任,再就是其他系统如何验证这个信任的有效性。因此,也就需要解决以下两点:

  • 存储信任
  • 验证信任

SSO单点登录

 

 

项目配置文件(ssm+redis+dubbo)

<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
       xsi:schemalocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://code.alibabatech.com/schema/dubbo
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <!-- 添加服务消费者的标志 -->
    <dubbo:application name="ego-sso-web-consumer"/>

    <!-- 指定注册中心,有两个地址192.168.1.171、172.18.25.171 -->
    <dubbo:registry address="172.18.25.171:2181,172.18.25.171:2182,172.18.25.171:2183" protocol="zookeeper" />
    <!--<dubbo:registry address="192.168.1.171:2181,192.168.1.171:2182,192.168.1.171:2183" protocol="zookeeper" />-->

    <!-- spring容器中存在一个远程服务的代理对象 -->
    <dubbo:reference interface="com.soldier.ego.rpc.service.userservice" id="userserviceproxy"></dubbo:reference>
</beans>
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
       xsi:schemalocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 加载cache.properties-->
    <!--<context:property-placeholder location="classpath:cache.properties"></context:property-placeholder>-->

    <!-- 实例化jediscluster,连接redis集群,有两个地址192.168.1.174、172.18.25.174-->
    <bean id="jediscluster" class="redis.clients.jedis.jediscluster">
        <constructor-arg name="nodes">
            <set>
                <bean class="redis.clients.jedis.hostandport">
                    <constructor-arg name="host" value="172.18.25.174"></constructor-arg>
                    <constructor-arg name="port" value="6380"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.hostandport">
                    <constructor-arg name="host" value="172.18.25.174"></constructor-arg>
                    <constructor-arg name="port" value="6381"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.hostandport">
                    <constructor-arg name="host" value="172.18.25.174"></constructor-arg>
                    <constructor-arg name="port" value="6382"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.hostandport">
                    <constructor-arg name="host" value="172.18.25.174"></constructor-arg>
                    <constructor-arg name="port" value="6383"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.hostandport">
                    <constructor-arg name="host" value="172.18.25.174"></constructor-arg>
                    <constructor-arg name="port" value="6384"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.hostandport">
                    <constructor-arg name="host" value="172.18.25.174"></constructor-arg>
                    <constructor-arg name="port" value="6385"></constructor-arg>
                </bean>
            </set>
        </constructor-arg>
    </bean>
</beans>
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
       xsi:schemalocation="http://www.springframework.org/schema/beans
                  http://www.springframework.org/schema/beans/spring-beans.xsd
                  http://www.springframework.org/schema/context
                  http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.soldier.ego.sso.service.impl" />
</beans>
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
       xsi:schemalocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 扫描controller -->
    <context:component-scan base-package="com.soldier.ego.sso.controller" />

    <!-- mvc:annotation-driven -->
    <mvc:annotation-driven />

    <!-- 静态资源映射 -->
    <!-- location:表示资源在项目的真正位置 -->
    <!-- mapping:访问路径 -->
    <!-- /css/** -->
    <!-- http://localhost:8080/css/a/b/c/hello.css -->
    <!-- / = http://localhost:8080/ -->
    <mvc:resources location="/css/" mapping="/css/**"></mvc:resources>
    <mvc:resources location="/js/" mapping="/js/**"></mvc:resources>
    <mvc:resources location="/images/" mapping="/images/**"></mvc:resources>

    <!-- 视图解析器 -->
    <bean id="viewresovler"
          class="org.springframework.web.servlet.view.internalresourceviewresolver">
        <!-- 表示使用的视图技术是jsp -->
        <property name="viewclass"
                  value="org.springframework.web.servlet.view.jstlview"></property>
        <!-- 前缀 -->
        <property name="prefix" value="/web-inf/jsp/"></property>
        <!-- 后缀 -->
        <property name="suffix" value=".jsp"></property>
    </bean>
</beans>
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemalocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
  <display-name>ego-sso-web</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>

  <!--<servlet-mapping>-->
  <!--<servlet-name>default</servlet-name>-->
  <!--<url-pattern>/favicon.ico</url-pattern>-->
  <!--</servlet-mapping>-->

  <!-- 以监听器的方式启动spring容器 -->
  <listener>
    <listener-class>org.springframework.web.context.contextloaderlistener</listener-class>
  </listener>

  <!-- 指定spring的配置文件 -->
  <context-param>
    <param-name>contextconfiglocation</param-name>
    <param-value>classpath:spring/applicationcontext-*.xml</param-value>
  </context-param>

  <!-- post请求的乱码过滤器 -->
  <filter>
    <filter-name>encodingfilter</filter-name>
    <filter-class>org.springframework.web.filter.characterencodingfilter</filter-class>
    <!-- 指定编码方式 -->
    <init-param>
      <param-name>encoding</param-name>
      <param-value>utf-8</param-value>
    </init-param>
  </filter>
  <!-- 映射filter -->
  <filter-mapping>
    <filter-name>encodingfilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!-- springmvc的servlet -->
  <servlet>
    <servlet-name>ego-sso-web</servlet-name>
    <servlet-class>org.springframework.web.servlet.dispatcherservlet</servlet-class>
    <!-- 指定springmvc的配置文件 -->
    <init-param>
      <param-name>contextconfiglocation</param-name>
      <param-value>classpath:spring/springmvc.xml</param-value>
    </init-param>
    <!-- 让springmvc随系统启动而启动 -->
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>ego-sso-web</servlet-name>
    <!-- 不需要伪静态化-->
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

 

 

实现用户名唯一性验证

1、唯一性验证接口开发规范

请求方法 get
ur http://sso.egou.com/user/check/{param}/{type}
参数说 格式如:zhangsan/1,其中 zhangsan 是校验的数据,type 为类型,可
选参数 1、2、3 分别代表 username、phone、email
可选参数 callback:如果有此参数表示此方法为 jsonp 请求,需要支 持 jsonp。
示例 http://sso.egou.com/user/check/zhangsan/1
返回 {
status: 200 //200 成功
msg: "ok"// 返回信息消息
data: false // 返回数据,true:数据可用,false:数据不可用
}

2、实现

  /**
     * 处理用户名唯一性验证请求
     * @param param     验证数据
     * @param type      验证类型
     * @param callback  回到函数
     *      mappingjacksonvalue --> 返回json,支持jsonp(其实是解决js跨域调用数据的一种方案)
     *      required = false --> 非必须
     *      @responsebody 异步的,不会进行跳转
     */
    @requestmapping("/user/check/{param}/{type}")
    @responsebody
    public mappingjacksonvalue loadpage(@pathvariable string param, @pathvariable integer type,
                                        @requestparam(required = false) string callback) {
        egoresult result = ssouserservice.selectuserbycond(param, type);

        //  处理json响应数据格式
        mappingjacksonvalue jacksonvalue = new mappingjacksonvalue(result);
        if (!stringutils.isempty(callback)) jacksonvalue.setjsonpfunction(callback);

        return jacksonvalue;
    }
@service
public class ssouserserviceimpl implements ssouserservice {

    //注入的是远程服务的代理对象
    @autowired
    private userservice userserviceproxy;

    @override
    public egoresult selectuserbycond(string cond, integer type) {
        return userserviceproxy.selectuserbycondservice(cond, type);
    }
}
@service
public class userserviceimpl implements userservice {

    @autowired
    private tbusermapper tbusermapper;

    @override
    public egoresult selectuserbycondservice(string cond, integer type) {

        //动态产生where条件
        tbuserexample example = new tbuserexample();
        tbuserexample.criteria criteria = example.createcriteria();

        //封装查询条件
        if (type.equals(1)) {
            criteria.andusernameequalto(cond);
        } else if (type.equals(2)) {
            criteria.andphoneequalto(cond);
        } else if (type.equals(3)) {
            criteria.andemailequalto(cond);
        }

        list<tbuser> userlist = tbusermapper.selectbyexample(example);

        //  创建egoresult对象
        egoresult result = new egoresult();
        result.setstatus(200);
        result.setmsg("ok");
        if (userlist!=null && userlist.size()>0) result.setdata(false);
        else result.setdata(true);  //  用户名可用

        return result;
    }
}

 

实现用户注册

1、用户注册接口开发规范

请求方法 post
url http://sso.egou.com/user/register
参数说明 username //用户名
password //密码
phone //手机号
email //邮箱
示例 http://sso.egou.com/user/register
返回值 {
status: 400
msg: "注册失败. 请校验数据后请再提交数据."
data: null
}

 

2、实现

    /**
     * 实现用户注册
     * @param user  用户信息
     *      @responsebody 异步的,不会进行跳转
     */
    @requestmapping(value = "/user/register", method = requestmethod.post)
    @responsebody
    public egoresult insertuser(tbuser user) {
        return ssouserservice.insertuserservice(user);
    }
@service
public class ssouserserviceimpl implements ssouserservice {

    //注入的是远程服务的代理对象
    @autowired
    private userservice userserviceproxy;

    @override
    public egoresult insertuserservice(tbuser user) {
        //  md5加密
        string pwd = user.getpassword();
        string md5 = digestutils.md5digestashex(pwd.getbytes());
        user.setpassword(md5);
        return userserviceproxy.insertuserservice(user);
    }
}
@service
public class userserviceimpl implements userservice {

    @autowired
    private tbusermapper tbusermapper;

    @override
    public egoresult insertuserservice(tbuser user) {

        egoresult result = new egoresult();

        try {
            date date = new date();
            user.setcreated(date);
            user.setupdated(date);
            tbusermapper.insert(user);
            result.setstatus(200);
            result.setmsg("注册成功.");
        } catch (exception e) {
            result.setstatus(400);
            result.setmsg("注册失败. 请校验数据后请再提交数据.");
            e.printstacktrace();
        }

        return result;
    }
}

 

实现用户登录

1、用户登录接口开发规范

请求方式 post
url htt://sso.eou.com/user/l
参数说明 username //用户名
password //密码
示例 http://sso.egou.com/user/loginusername=zhangsan&password=123
返回值 {
status: 200
msg: "ok"
data: "fe5cb546aeb3ce1bf37abcb08a40493e"//登录成功,返
回 token
}

 

 2、实现

    /**
     * 实现用户登录
     * @param username  用户名
     * @param password  密码
     *      @responsebody 异步的,不会进行跳转
     */
    @requestmapping(value = "/user/login", method = requestmethod.post)
    @responsebody
    public egoresult userlogin(string username, string password) {
        return ssouserservice.userlogin(username, password);
    }
@service
public class userserviceimpl implements userservice {

    @autowired
    private tbusermapper tbusermapper;

    @override
    public tbuser selectuserbyusernameservice(string username) {

        //动态产生where条件
        tbuserexample example = new tbuserexample();
        tbuserexample.criteria criteria = example.createcriteria();

        //封装查询条件
        criteria.andusernameequalto(username);

        // where username=?
        list<tbuser> userlist = tbusermapper.selectbyexample(example);

        //  因为用户名唯一
        if (userlist!=null && userlist.size()==1) return userlist.get(0);

        return null;
    }
}
@service
public class ssouserserviceimpl implements ssouserservice {

    //注入的是远程服务的代理对象
    @autowired
    private userservice userserviceproxy;

    // 注入jediscluster集群访问对象
    @autowired
    private jediscluster jediscluster;

    @override
    public egoresult userlogin(string username, string password) {

        egoresult result = new egoresult();
        result.setstatus(400);
        result.setdata(null);
        result.setmsg("用户名或密码错误.");
        tbuser tbuser = userserviceproxy.selectuserbyusernameservice(username);

        if (tbuser != null) {
            //对前端提交的密码进行加密
            password = digestutils.md5digestashex(password.getbytes());
            if (password.equals(tbuser.getpassword())) {
                //  将当前登录用户对象,转为json字符串,保存到redis数据库
                string userjson = jsonutils.objecttojson(tbuser);
                string token = uuid.randomuuid().tostring();
                // 将用户信息保存到redis数据库
                jediscluster.set(token, userjson);

                result.setstatus(200);
                result.setmsg("登录成功.");
                result.setdata(token);
            }
        }

        return result;
    }
}