利用spring-session+redis 实现 简单 SSO

在上一篇搭建基础框架基础上,实现一下简单的单点登录。

    简单思路:用户访问客户端时发现用户未登录,跳转到服务器的统一登录中心,在统一登录中心登录后,从服务器自动跳回客户端的访问地址,此时客户端发现用户已经登录,即可访问。

    准备:redis 数据库, 上一篇中利用简单基础框架搭建的项目(可命名为 sso_client 和 sso_server 两个项目)

一 。 sso_server 服务端

 1.既然要用spring-session 代替系统自带的session 和 利用 redis 存储session的信息,那么首先还是要导入依赖,在 sso_client 和 sso_server  pom中加入 :  

        <!-- spring-session  替代 原有session-->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session</artifactId>
        </dependency>

        <!-- redis  连接redis-->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>

         <!-- redis  自动存取数据-->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

 2.在 sso_client 和 sso_server  的配置文件中加入配置信息 ,将sso_server的端口改为 8088, sso_client 端口为 8089

#redis配置:
spring.session.store-type=redis
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379

#是否开启基于session的统一登录
session.mode=remote
#sso统一登录地址 (本地host文件中加入 127.0.0.1 buc.sso.com)
sso.address=http://buc.sso.com:8088/ssoLogin.htm

3. 在 sso_server 统一登录中心中 加入 cookie的 配置文件,存储session对象信息的 Operator 文件 , 统一登录中心的实现方法 LoginController ,统一登录中心 vm文件 四个文件

                                        

代码分别如下:

    1) cookie 配置 主要配置了统一的cookie 域名

@Configuration
public class CookieConfiguration {

    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        serializer.setDomainName("sso.com");
        serializer.setCookiePath("/");
        return serializer;
    }

}

 2 )  Operator 文件 注意要实现序列化,利用redis  存储信息时要 对key,value 进行序列化与反序列化

   

public class Operator implements Serializable {

    /**  */
    private static final long serialVersionUID = 1L;

    private long              id;
    private String            operatorId;
    private String            operatorName;

      ..... get/set方法

}

3 )  统一登录中心实现方法 ,主要为登录页面,及登录的校验方法

@Controller
public class LoginController {

    private Logger logger = LoggerFactory.getLogger(LoginController.class);

    @RequestMapping(value = "/ssoLogin.htm", method = RequestMethod.GET)
    public String login(HttpServletRequest request, HttpServletResponse response) {
        return "login";
    }

    @RequestMapping(value = "/ssoLogin.json", method = RequestMethod.POST)
    @ResponseBody
    public ModelMap login(String userName, String password, HttpServletRequest request) {
        ModelMap model = new ModelMap();

        if ("zlf".equals(userName) && "123456".equals(password)) {
            HttpSession session = request.getSession();
            Operator operator = new Operator();
            operator.setOperatorId("111");
            operator.setOperatorName(userName);
            session.setAttribute("OPERATOR_SESSION_ID", operator);
            logger.info("----共享session-----");
            model.addAttribute("code", "true");
            model.addAttribute("message", "登录成功");
            return model;
        } else {
            model.addAttribute("code", "false");
            model.addAttribute("message", "账号或者密码有误");
            return model;
        }
    }

}
 4 ) login.vm

<!DOCTYPE html>
<html>
<head>
<title>测试sso统一登录中心</title>
</head>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js" ></script>
<body>
    <div class="container">
        <!-- 登录框 -->
        <div class="login">
            <div class="usernameDiv">
                <input class="input username" type="text" value="" />
            </div>
            <div class="passwordDiv">
                <input class="input pwd" type="password" value=""/>
            </div>
            <div class="loginButton">
                <input class="inputLogin" type="button" value="登&nbsp;录" />
            </div>
        </div>
    </div>
</body>
<script type="text/javascript">
$(document).ready(function(){
   //登录的点击事件
   $(".inputLogin").click(function(){
        var username = $(".username").val();
        var password = $(".pwd").val();
        var callbackURL = "";
        $.post("/ssoLogin.json",
      {
        "userName": username,
        "password": password
      },
      function(data,status){
          callbackURL = getUrlParam("callbackURL");
        if(data.code=="true"){
            window.location.href=callbackURL;
        }else{
            alert("账号或密码错误!");
        }
      });
   });
   //function方法
   function getUrlParam(name) {
    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象
    var r = window.location.search.substr(1).match(reg);  //匹配目标参数
    if (r != null) return unescape(r[2]); return null; //返回参数值
  } 
 });
</script>

</html>

    

二 。 sso_client 客户端

    1.  加入与sso_server相同的 pom依赖 和配置文件后 , 添加 如下文件 ,其中 CookieConfiguration 与 sso_server中相同,  添加的Operator 与sso_server相同,且其就是sso_server中的 类文件( 此处我踩了个坑,如果Operator 包名为 test_sso_client_content 客户端自己的 Operator 文件,那么会出现 Operator 对象的反序列化的问题 , 网上看了很多办法,有 在SpringBootApplication中加入 bean 修改 redis 的key,value 序列化方法,有自定义系列化工具的 , 但好像都没有用 , 如果有人发现可以,欢迎指导。),

                                        

SessionConfiguration 和 SSOConfiguration 文件代码如下 :

    

@Configuration
public class SSoConfiguration {

    @Autowired
    private Environment env;

    @Bean
    @ConditionalOnProperty(name = "session.mode", havingValue = "remote")
    public FilterRegistrationBean getFilterRegistrationBean() {
        String mode = env.getProperty("session.mode");
        String SSO_SERVER_URL = env.getProperty("sso.address");
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        if (StringUtils.equals(mode, "remote")) {
            //共享session
            //自定义过滤器
            SessionConfiguration myFilter = new SessionConfiguration();
            filterRegistrationBean.setFilter(myFilter);
            //设置参数
            filterRegistrationBean.addInitParameter("SSO_SERVER_URL", SSO_SERVER_URL);
            //过滤地址
            List<String> urlPatterns = new ArrayList<String>();
            urlPatterns.add("/*");
            filterRegistrationBean.setUrlPatterns(urlPatterns);
        }
        return filterRegistrationBean;
    }

}

@EnableRedisHttpSession
public class SessionConfiguration implements Filter {

    public static Logger logger         = LoggerFactory.getLogger(SessionConfiguration.class);

    private String       sso_server_url = "";

    /** 
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.sso_server_url = filterConfig.getInitParameter("SSO_SERVER_URL");
        if (StringUtils.isBlank(sso_server_url)) {
            throw new IllegalArgumentException("SSOFilter中SSO_SERVER_URL参数初始化异常");
        }
    }

    /** 
     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
     */
    @Override
    public void doFilter(ServletRequest req, ServletResponse res,
                         FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        String urL = request.getRequestURL().toString();
        HttpSession session = request.getSession();
        if (session != null && session.getAttribute("OPERATOR_SESSION_ID") != null) {
            chain.doFilter(req, res);
        } else {
            //客户端发现未登录重定向
            //跳转至sso认证中心
            String callbackURL = request.getRequestURL().toString();
            StringBuilder url = new StringBuilder();
            url.append(sso_server_url).append("?callbackURL=").append(callbackURL);
            response.sendRedirect(url.toString());
        }
    }

    /** 
     * @see javax.servlet.Filter#destroy()
     */
    @Override
    public void destroy() {
    }

}

三。测试

   1. 启动本地redis ,连接本地redis 

  

 2.启动sso_server 和sso_client ,浏览器中访问 客户端地址  http://buc.sso.com:8089/query/querySample.htm   跳转到服务器统一登录中心

    在redis中执行 keys * 查看发现已经存入了浏览器的 session ,接着输入hgetall  "存储的键值"  发现没有我们要的 SESSION_OPERATOR_ID,说明还没有用户登录信息。

 3. 输入账号密码: zlf 123456 ,跳转回来客户端并且成功访问客户端

    

    再次输入 keys * 查看 键值 ,输入hgetall "键值" ,发现redis 中已经存入了登录信息

4. 关闭浏览器.重新输入地址: http://buc.sso.com:8089/query/querySample.htm,发现不用登录,直接进入客户端,因为session 信息存在redis 中 默认有效30分钟

四。总结:

    1. 如果在 sso_client 和 sso_server 中分别建立 Operator 对象,会出现redis 数据反序列化问题.

需要在sso_client 中 使用 sso_server 的 Operator

   2. 遇到问题如果百度不到相似的经历,没有人出现过这种问题,那多半是自己粗心大意 代码写的不对,(纯属个人意见)

     最后,觉得还过得去的请点个赞,欢迎大家交流指正!

猜你喜欢

转载自my.oschina.net/u/3820486/blog/1810142
今日推荐