SSO单点登录

2022-10-13,,

单点登录系统介绍

单点登录(single sign on),简称为 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;
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

《SSO单点登录.doc》

下载本文的Word格式文档,以方便收藏与打印。