方式一 屡次登陆方式
- MVC Controller 的 sso 方法
@RequestMapping(value = "ssoLogin.do") public String sso(String userName, String password,HttpServletRequest request, HttpServletResponse response) { AuthUser user = userDao.getUserByname(userName); UsernamePasswordToken token = new UsernamePasswordToken(username, password); //获取当前的Subject Subject currentUser = SecurityUtils.getSubject(); currentUser.login(token); currentUser.getSession("true").setAttribute("userName",userName);//shrio session currentUser.getSession().setAttribute("loginName",userName);//http session return "xxx"; //页面 }
- 调用
String path = "osdp";//request.getContextPath();//项目地址 String basePath = request.getScheme() + ":" + request.getServerName() + ":" + request.getServerPort() + path;//url String userName = UserHolder.getUserName(); String password = UserHolder.getPassword(); String data = "userName=" + userName + "&password=" + password; return "redirect:" + basePath + "/ssoLogin" + "?" + data;
方式二 自定义Token令牌
- MVC Controller 映射 sso 方法
/** * 单点登陆(如已经登陆,则直接跳转) * @param userCode 登陆用户编码 * @param token 登陆令牌,令牌组成:sso密钥+用户名+日期,进行md5加密,举例: * String secretKey = Global.getConfig("shiro.sso.secretKey"); * String token = Digests.md5(secretKey + userCode + DateUtils.getDate("yyyyMMdd")); * @param url 登陆成功后跳转的url地址。 * @param relogin 是否从新登陆,须要从新登陆传递true * 例如:http://localhost/project/sso/{token}?url=xxx&relogin=true */ @RequestMapping(value = "sso/{userCode}/{token}") public String sso(@PathVariable String userCode, @PathVariable String token,@RequestParam(required=true) String url, String relogin, Model model) { Principal principal = SecurityUtils.getSubject().getPrincipal(); // 若是已经登陆 if(principal != null){ // 若是设置强制从新登陆,则从新登陆 if (BooleanUtils.toBoolean(relogin)){ SecurityUtils.getSubject().logout(); } // 不然,直接跳转到目标页 else{ return "redirect:" + Encodes.urlDecode2(url); } } // 进行单点登陆 if (token != null){ UsernamePasswordToken upt = new UsernamePasswordToken(); try { upt.setUsername(userCode); // 登陆用户名 upt.setPassword(token.toCharArray()); // 密码组成:sso密钥+用户名+日期,进行md5加密,举例: Digests.md5(secretKey+username+20150101)) upt.setParams(upt.toString()); // 单点登陆识别参数,see: AuthorizingRealm.assertCredentialsMatch } catch (Exception ex){ if (!ex.getMessage().startsWith("msg:")){ ex = new AuthenticationException("msg:受权令牌错误,请联系管理员。"); } model.addAttribute("exception", ex); } try { SecurityUtils.getSubject().login(upt); return "redirect:" + Encodes.urlDecode2(url); } catch (AuthenticationException ae) { if (!ae.getMessage().startsWith("msg:")){ ae = new AuthenticationException("msg:受权错误,请检查用户配置,若不能解决,请联系管理员。"); } model.addAttribute("exception", ae); } } return "error/403"; }
- 重载org.apache.shiro.realm.AuthorizingRealm类的assertCredentialsMatch方法
/** * 认证密码匹配调用方法 */ @Override protected void assertCredentialsMatch(AuthenticationToken authcToken, AuthenticationInfo info) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authcToken; // 若单点登陆,则使用单点登陆受权方法。 if (token.toString().equals(token.getParams())){ // sso密钥+用户名+日期,进行md5加密,举例: Digests.md5(secretKey+username+20150101)) String secretKey = Global.getConfig("shiro.sso.secretKey"); String password = Digests.md5(secretKey + token.getUsername() + DateUtils.getDate("yyyyMMdd")); if (password.equals(String.valueOf(token.getPassword()))){ return; } } super.assertCredentialsMatch(token, info); }
- 实现Shiro无状态访问,如经过传递sessionid参数便可实现会话访问
public class SessionManager extends DefaultWebSessionManager { public SessionManager() { super(); } @Override protected Serializable getSessionId(ServletRequest request, ServletResponse response) { // 若是参数中包含“__sid”参数,则使用此sid会话。 例如:http://localhost/project?__sid=xxx&__cookie=true // 其实这里还可使用以下参数:cookie中的session名称:如:JSESSIONID=xxx,路径中的 ;JESSIONID=xxx,但建议仍是使用 __sid参数。 String sid = request.getParameter("__sid"); if (StringUtils.isNotBlank(sid)) { // 是否将sid保存到cookie,浏览器模式下使用此参数。 if (WebUtils.isTrue(request, "__cookie")){ HttpServletRequest rq = (HttpServletRequest)request; HttpServletResponse rs = (HttpServletResponse)response; Cookie template = getSessionIdCookie(); Cookie cookie = new SimpleCookie(template); cookie.setValue(sid); cookie.saveTo(rq, rs); } // 设置当前session状态 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, ShiroHttpServletRequest.URL_SESSION_ID_SOURCE); // session来源与url request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sid); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); return sid; }else{ return super.getSessionId(request, response); } } }
- 调用
String path = "osdp";//request.getContextPath();//项目地址 String basePath = request.getScheme() + ":" + request.getServerName() + ":" + request.getServerPort() + path;//url String userName = UserHolder.getUserName(); String secretKey = "SSO"; String token = DigestUtils.md5Hex(secretKey + userName + CommonUtil.getToday()); String data = "userName=" + userName + "&token=" + token; return "redirect:" + basePath + "/ssoLogin" + "?" + data;
注意:shiro配置放开单点请求:/sso** =anon
以上两种方式能够实现简单的单点登陆,基本思想就是多个子系统登陆一遍(自定义token登陆或用户名密码登陆,原理同样)。java
多点注销
每个子项目注销一遍:apache
final String loginName = session.getAttribute("loginName");//当前登陆账号 new Thread(new Runnable(){ @Override public void run(){ HttpUtil.get("xxx/ssoLogout?loginName=" + loginName);//接口调用:注销其余项目 } }).start(); //注销 SecurityUtils.getSubject().logout(); void ssoLogout(HttpServletRequest request, HttpServletResponse response){ String loginName = request.getParameter("loginName"); //注销账号 // 1.获取当前用户sessionId String currentUserSessionId = SecurityUtils.getSubject().getSession().getId().toString(); // 2.获取shiro的sessionManager DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager(); DefaultWebSessionManager sessionManager = (DefaultWebSessionManager)securityManager.getSessionManager(); // 3.获取全部已登陆用户的session列表 Collection<Session> sessions = sessionManager.getSessionDAO().getActiveSessions(); //清除session if(!CollectionUtils.isEmpty(sessions)&&sessions.size() > 1){ User user = null; for(Session session:sessions){ // 4. 获取已登陆用户的session的key值 SimplePrincipalCollection simplePrincipalCollection = (SimplePrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); // 5. 获取new SimpleAuthenticationInfo(user, pwd, this.getName())中放进去的第一个参数 user = (User) simplePrincipalCollection.getPrimaryPrincipal(); // 6. 清除当前用户及之前登陆时保存的session会话 if (loginName.equals(user.getUserName()) && session.getId().equals(currentUserSessionId)) { sessionManager.getSessionDAO().delete(session); System.out.println("清理用户["+loginName+"],SessionId为["+session.getId()+"]的Session信息!"); } } } }