单点登录多点注销shiro的简单实现
程序员文章站
2022-03-25 09:52:01
...
方式一 多次登录方式
- 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登录或用户名密码登录,原理一样)。
多点注销
每一个子项目注销一遍:
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信息!");
}
}
}
}
上一篇: 路由的hash和history模式
下一篇: Vue的hash模式和history模式