kisso 是一个中间件,提供 cookie 搭建 java web sso 的组件式解决方案。
shiro是目前比较常用的轻量权限控制框架。
SpringBoot集成Shiro的文章比较多,我不多赘述。下面介绍SpringBoot集成kisso。
思路
- Spring集成Shiro
- Shiro托管项目session,使用Redis保存Shiro缓存信息
- SpringBoot集成kisso
- kisso单点登录同步shiro授权信息
步骤
1.SpringBoot集成Shiro
略。
2.Shiro托管项目session,使用Redis保存Shiro缓存信息
这个的详细操作过程在这个博文里介绍过了 https://my.oschina.net/qwzhang01/blog/1620339
3.SpringBoot集成kisso
1)添加配置文件sso.properties
sso.encoding=UTF-8 sso.secretkey=Kisso4SpringBootK80mAS sso.cookie.name=uid sso.cookie.domain=canyou.xyz sso.cookie.maxage=7200 sso.login.url=http://canyou.xyz:8088/login
2)注入kisso组件
@Configuration public class WebAppConfig extends WebMvcConfigurerAdapter { /** * 注入kisso拦截器 * * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SSOSpringInterceptor()).addPathPatterns("/**").excludePathPatterns("/login/**"); super.addInterceptors(registry); } /** * 注入Kisso * * @return */ @Bean(initMethod = "initKisso") public WebKissoConfigurer webKissoConfigurer() { WebKissoConfigurer webKissoConfigurer = new WebKissoConfigurer("sso.properties"); return webKissoConfigurer; } }
3)修改登录方法,登录成功,调用kisso,将登陆信息保存在cookie中
@RequestMapping(value = "/login/check", method = RequestMethod.POST) public String login(@RequestParam(value = "ReturnURL", defaultValue = "") String retuenUrl, LoginUserVo user, RedirectAttributes redirectAttributes, HttpServletRequest request, HttpServletResponse response) { if (StrUtil.hasBlank(user.getUsername(), user.getPassword(), user.getSafecode_iput())) { redirectAttributes.addFlashAttribute("msg", "用户名或密码验证码都均能为空"); return "redirect:/login"; } if (!CaptchaKit.validate(user.getSafecode_iput(), request)) { redirectAttributes.addFlashAttribute("msg", "验证码错误"); return "redirect:/login"; } User checkUser = userService.getByUserName(user.getUsername()); if (checkUser == null) { redirectAttributes.addFlashAttribute("msg", "用户不存在,请核对后重新登录"); return "redirect:/login"; } String encrypt = ShiroCrypt.encrypt(user.getPassword()); if (!checkUser.getPassword().equals(encrypt)) { redirectAttributes.addFlashAttribute("msg", "密码错误,请核对后重新登录"); return "redirect:/login"; } if (userService.login(checkUser.getUsername(), checkUser.getPassword())) { HttpSession session = request.getSession(); session.setAttribute("username", checkUser.getUsername()); session.setAttribute("fullName", checkUser.getRealname()); // 单点登录核心代码 SSOToken ssoToken = new SSOToken(); ssoToken.setUid(user.getUsername()); ssoToken.setApp("oms"); ssoToken.setIp(IpHelper.getIpAddr(request)); // 将sessionid保存在kisso的cookie里面 ssoToken.setData(request.getSession().getId()); SSOHelper.setSSOCookie(request, response, ssoToken, false); return "redirect:/"; } redirectAttributes.addFlashAttribute("msg", "登陆失败"); return "redirect:/login"; }
至此,kisso集成完成。符合配置在sso.properties中的domain的域名cookie中,都会同步登陆信息。
但是目前只是把登陆信息同步在kisso里面了,对于shiro来说,不同应用之间的认证、授权信息并没有同步,因此需要在所有系统里面根据kisso,添加shiro的认证授权信息。
4.kisso单点登录同步shiro授权信息
上面已经实现了应用session由shiro托管,shiro缓存实现共享。给shiro添加filter,通过filter,对于符合kisso的单点登录条件的应用,通过kisso获取原来已经登录项目的sessionid,将其写入新应用的cookie里面,即实现将原已经授权认证的项目的shiro信息同步至新项目中。
import cn.hutool.core.util.StrUtil; import com.baomidou.kisso.SSOHelper; import com.baomidou.kisso.SSOToken; import org.apache.shiro.web.filter.AccessControlFilter; import org.springframework.stereotype.Component; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component public class KissoFilter extends AccessControlFilter { @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String username = (String) httpServletRequest.getSession().getAttribute("username"); return StrUtil.isNotBlank(username); } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { SSOToken ssoToken = SSOHelper.getToken((HttpServletRequest) request); if (ssoToken != null && ssoToken.getApp().equals("oms")) { Cookie cookie = new Cookie("csid", ssoToken.getData()); cookie.setPath("/"); ((HttpServletResponse) response).addCookie(cookie); } return true; } }
把filter注册为shiro的过滤器,过滤所有请求
@Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, Filter> filterMap = new HashMap<>(); filterMap.put("authc_kisso", new KissoFilter()); filterMap.put("shiro_kisso", new KiSsoShiroAuthFilter()); filterMap.put("logout_kisso", new KiSsoShiroLogoutFilter()); shiroFilterFactoryBean.setFilters(filterMap); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); filterChainDefinitionMap.put("/js/**", "anon"); filterChainDefinitionMap.put("/css/**", "anon"); filterChainDefinitionMap.put("/fonts/**", "anon"); filterChainDefinitionMap.put("/login/logout", "logout_kisso"); filterChainDefinitionMap.put("/login/**", "anon"); filterChainDefinitionMap.put("/**", "authc_kisso, shiro_kisso"); shiroFilterFactoryBean.setLoginUrl("/login"); shiroFilterFactoryBean.setSuccessUrl("/index"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; }
传送门