A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 绮丽 于 2017-8-23 08:56 编辑

基于shiro的单点登录Java实现

关于单点登录,这里不再赘述,简单地说就是在一个多系统共存的环境下,用户在一处登录后,就不用在其他系统中登录,也就是用户的一次登录能得到其他所有系统的信任。实现单点登录的思想有很多,本篇就简单实现其中一种单点登录.
如图,用户通过sso系统登录,进行shiro身份认证, 这里做了简化处理,并没有和数据库进行交互,而是选择直接放行:

@Override
   protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
       //身份认证方法:AuthRealm.doGetAuthenticationInfo()
       UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
       Map<String, Object> map = new HashMap<>();
       map.put("username", token.getUsername());
       map.put("password", token.getPassword());
       // 模拟从数据库获取对应用户名密码的用户
       User user = new User(token.getUsername(),String.valueOf(token.getPassword()));
       /*if (null == user) {
           throw new AccountException("帐号或密码不正确!");
       }*/
       SecurityUtils.getSubject().getSession().setAttribute(UserConfig.LOGIN_USER,user);
       return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
   }

当用户成功登陆后,会进行一次重定向,同时生成sid,这里的sid采用shiro的md5加密,密码作为盐值,生成用户名的散列,但是存在一个问题,相同认证用户的sid相同,导致黑客从中获取sid的话,以后都可以将其作为身份标识进行单点登录,因此我们可以使用RedisTemplate的set方法:

set void set(K key, V value, long timeout, TimeUnit unit);
使用:redisTemplate.opsForValue().set("name","tom",10, TimeUnit.SECONDS);
结果:redisTemplate.opsForValue().get("name")由于设置的是10秒失效,十秒之内查询有结果,十秒之后返回为null

可以看出来,这个方法可以将一个值设置到redisTemplate对象,并且可以设置失效时间完成缓存,这样我们每次都可以以当前时间作为盐值,随机生成md5散列,这样当失效时间一过,sid便无法使用

@RequestMapping("/main")
   public String mainPage(Model model,HttpSession session){
       User user = (User) session.getAttribute(UserConfig.LOGIN_USER);
       String sid = Encrypt.md5(user.getUsername(),user.getPassword());
       model.addAttribute("sid",sid);
       return "main";
   }

用户认证成功重定向到”/main”api,然后在返回的model中携带sid,返回main.html

<body>
    <h1>主页欢迎你</h1>
    <a th:href = "@{'http://localhost:8801/?callback=http://localhost:8800/sso&sid='+${sid}}">子系统1</a>
    <a th:href = "@{'http://localhost:8802/?callback=http://localhost:8800/sso&sid='+${sid}}">子系统2</a>
</body>

main.html动态生成了子系统的访问地址,并携带两个参数:callback回调地址和sid,当用户点击a标签之后,重定向到子系统

@RequestMapping
   public String home(@RequestParam("callback") String callback, @RequestParam("sid") String sid, RedirectAttributes attr) throws Exception {
       System.out.println(callback);
       System.out.println(sid);
       //参数直接加载url后面
       URL url=new URL(callback+"?sid="+sid);
       HttpURLConnection conn=(HttpURLConnection) url.openConnection();
       conn.setRequestMethod("GET");
       conn.setConnectTimeout(5000);
       String jsonString = "";
       if(conn.getResponseCode()==200){                //200表示请求成功
           InputStream is=conn.getInputStream();       //以输入流的形式返回
           //将输入流转换成字符串
           ByteArrayOutputStream baos=new ByteArrayOutputStream();
           byte [] buffer=new byte[1024];
           int len=0;
           while((len=is.read(buffer))!=-1){
               baos.write(buffer, 0, len);
           }
           jsonString=baos.toString();
           baos.close();
           is.close();
           System.out.println(jsonString);
           //转换成json数据处理
       }
       User user = JSON.parseObject(jsonString, User.class);
       attr.addFlashAttribute("msg","欢迎用户"+user.getUsername());
       return "redirect:/index";
   }

如上,点击a标签之后进入home方法,此时拿到回调地址可以使用HttpClient或者URLconnection访问回调接口.

@RequestMapping("/sso")   @ResponseBody   public User sso(@RequestParam("sid") String sid,HttpSession session) throws Exception{       FileInputStream in = new FileInputStream(UserConfig.FILE_LOCATION);       byte[] arr = new byte[4096];       in.read(arr);       in.close();       String str = new String(arr);       str = str.trim();       System.out.println(str);       User user = JSON.parseObject(str, User.class);       if(user ==null){           return null;       }       String mySid = Encrypt.md5(user.getUsername(), user.getPassword());       if(!sid.equals(mySid)){           return null;       }       return user;   }

如上,这里是sso系统的”/sso”接口,回调请求发出后,此接口接收响应,并从根据sid进行匹配,从数据库中取到登录用户的身份信息(这里简化,使用的是本地文件保存),然后将用户的json数据传输回去.此时子系统就可以得到登录用户的身份信息,实现自动登录.最终效果:




如果不是通过单点登录跳转子系统,则无法得到登录用户信息:



                                                                                 文章出自上海传智JavaEE就业51期(20170422面授) 万晓鹏




6 个回复

倒序浏览
不错,不错
回复 使用道具 举报
本帖最后由 q120959269 于 2020-1-2 13:03 编辑

???????????????????????????????????
回复 使用道具 举报
不错
回复 使用道具 举报
redisTemplate.opsForValue这个地方有点不明白
回复 使用道具 举报
redisTemplate.opsForValue 有点不明白
回复 使用道具 举报
redisTemplate.opsForValue 有点不明白
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马