springboot redis shiro 实现 单点登录

大家好,我是烤鸭:

    今天给大家分享简单的单点登录的实现方式。

    环境及jar包:

    springboot     1.5.10   

    redis    2.9.0    (可以用tomcat的session,但是无法解决多个tomcat共享session的问题)

    shiro    1.4.0    

    lombok    1.16.10    (可选,编译的时候自动生成get,set,toString()方法)

登录方法:

    主要是用户完成登录,登录信息写在cookie,

    服务器端存缓存(redis)中。

LoginService:

package xxx.xxx.system.service.impl;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSONObject;
import xxx.xxx.common.constant.IMsgEnum;
import xxx.xxx.common.resp.BaseMsgResp;
import xxx.xxx.common.utils.MD5Utils;
import xxx.xxx.entity.sys.UserDO;
import xxx.xxx.redis.cache.RedisManager;
import xxx.xxx.system.service.LoginService;
import xxx.xxx.system.service.UserService;

@Service
public class LoginServiceImpl implements LoginService {
	@Autowired
	private UserService userService;
	
	@Override
	public BaseMsgResp login(String username, String password) throws AuthenticationException{
		BaseMsgResp resp = new BaseMsgResp();
		password = MD5Utils.encrypt(username, password);
		UsernamePasswordToken token = new UsernamePasswordToken(username, password);
		Subject subject = SecurityUtils.getSubject();
		subject.login(token);
		//获取用户
		Map<String, Object> params = new HashMap<>();
		params.put("username", username);
		List<UserDO> list = userService.list(params);
		if(list.size() > 1) {
			resp = new BaseMsgResp(IMsgEnum.SYSTEM_ANOMALY.getMsgCode()+"",IMsgEnum.SYSTEM_ANOMALY.getMsgText());
			return resp;
		}
		//token
		Serializable id = subject.getSession().getId();
		//将token放入redis
		RedisManager manager = RedisManager.getRedisSingleton();
		manager.set("sys:login:user_token_"+id.toString(),list.get(0).getUserId()+"",60*30);
		//防止同一个账号同时登录
		manager.set("sys:user:id_"+list.get(0).getUserId(), id.toString(),60*30);
		//用户信息
		manager.set("sys:login:user_info_"+list.get(0).getUserId(), JSONObject.toJSONString(list.get(0)),60*30);
		return resp;
	}
}

RedisManager.java

    用于redis初始化,和一些基本方法:

packagexxx.xxx.redis.cache;


import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;


/**
 * @author 
 * @version V1.0
 */


import java.util.Set;
import java.util.concurrent.TimeUnit;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;


import com.alibaba.fastjson.JSONObject;
import xxx.xxx.redis.config.RedisConstant;


import lombok.Data;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;


/**
 *
 */
@Configuration
@ConfigurationProperties(value="jedis.pool")
@Data
public class RedisManager {
	private final Logger logger = LoggerFactory.getLogger(this.getClass());
	
	volatile static RedisManager redisSingleton;
	
    private String host;
    private int port;
    private int expire;
    private int timeout;
    private String password = "";
    private static JedisPool jedisPool = null;
    //第几个仓库
    private String database;
    
	public String getDatabase() {
		if(null == database || "".equals(database)) return "0";
		return database;
	}


	public void setDatabase(String database) {
		this.database = database;
	}
	
    public RedisManager() {


    }
    


	public static RedisManager getRedisSingleton() {
		if(redisSingleton == null) {
			synchronized (RedisManager.class) {
				if(redisSingleton == null) return new RedisManager();
			}
		}
		return redisSingleton;
	}


    /**
     * 初始化方法
     */
    public void init() {
        if (jedisPool == null) {
            if (password != null && !"".equals(password)) {
                jedisPool = new JedisPool(new JedisPoolConfig(), host, port, timeout, password);
            } else if (timeout != 0) {
                jedisPool = new JedisPool(new JedisPoolConfig(), host, port, timeout);
            } else {
                jedisPool = new JedisPool(new JedisPoolConfig(), host, port);
            }


        }
    }




	/**
	 * 给Redis中Set集合中某个key值设值
	 * 
	 * @param key
	 * @param value
	 */
	public void set(String key, String value) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.select(Integer.parseInt(getDatabase()));
			jedis.set(key, value);
		} catch (Exception e) {
			logger.error("Jedis set 异常" + e.getMessage());
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}


	/**
	 * 从Redis中Set集合中获取key对应value值
	 * 
	 * @param key
	 */
	public String get(String key) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.select(Integer.parseInt(getDatabase()));
			return jedis.get(key);
		} catch (Exception e) {
			logger.error("Jedis get 异常" + e.getMessage());
			return null;
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}






	/**
	 * 给Redis中Set集合中某个key值设值
	 * 
	 * @param key
	 * @param value
	 */
	public void set(String key, String value, long time) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.select(Integer.parseInt(getDatabase()));
			jedis.set(key, value);
			jedis.set(key, value, "XX", "EX", time);
		} catch (Exception e) {
			logger.error("Jedis set 异常" + e.getMessage());
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}


	
	/**
	 * 
	* @Title: expire 
	* @Description: 设置指定key的生存时间   
	* @return Long    成功 返回 1 
	* @throws
	 */
	public Long expire(String key,int seconds) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.select(Integer.parseInt(getDatabase()));
			return jedis.expire(key, seconds);
		} catch (Exception e) {
			logger.error("Jedis expire 异常" + e.getMessage());
			return null;
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}


	}
	/**
	 * 
	* @Title: exist 
	* @Description: 判断key是否存在   
	* @return Boolean    返回类型 
	* @throws
	 */
	public Boolean exist(String key) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.select(Integer.parseInt(getDatabase()));
			return jedis.exists(key);
		} catch (Exception e) {
			logger.error("Jedis exist 异常" + e.getMessage());
			return null;
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}


	}
	
}

LoginInterceptor:

    每次请求都会经过拦截器,校验用户是否登录或者多个浏览器(客户端)登录。

    如果用户已登录,用这次请求cookie中的token和缓存中的比较,如果不一致,就把之前缓存中的用户信息清空。

    这样之前的用户如果再次操作,就会跳转到登陆页面。

package xxx.xxx.system.interceptor;

import java.io.IOException;
import java.io.Serializable;
import java.lang.ProcessBuilder.Redirect;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.alibaba.fastjson.JSONObject;
import xxx.xxx.common.utils.LogUtils;
import xxx.xxx.redis.cache.RedisManager;
import xxx.xxx.system.utils.ShiroUtils;

/**
 * ClassName: PlatformInterceptor date: 2015年12月30日 下午2:13:24 Description: 拦截器
 * 
 * @author 
 * @version
 * @since JDK 1.8
 */
@Component
@EnableAutoConfiguration(exclude = ErrorMvcAutoConfiguration.class)
public class LoginInterceptor implements HandlerInterceptor {

	private static final Log logger = LogFactory.getLog(LoginInterceptor.class);
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
		try {
			logger.info(LogUtils.getRequestLog(request));
			//校验用户是否已经登录,如果登录过,将之前用户踢掉,同时更新缓存中用户信息
			Subject subject = SecurityUtils.getSubject();
			Serializable token = subject.getSession().getId();
			RedisManager redisManager = RedisManager.getRedisSingleton();
			//获取用户id
			String userId = redisManager.get("sys:login:user_token_"+token.toString());
			if(StringUtils.isNotBlank(userId)) {
				String tokenPre = redisManager.get("sys:user:id_"+userId);
				if(!token.equals(tokenPre)) {
					 //重定向到login.html
		            redirect(request, response); 
					return false;
				}else {
					Long expire = redisManager.ttl("sys:login:user_token_"+token.toString());
					//过期时间小于1分钟的,更新token
					if(expire < 1 * 60 * 1000) {
						redisManager.expire("sys:login:user_token_"+token.toString(), 60*30);
					}
				}
			}else {
				redirect(request, response); 
				return false;
			}
		} catch (Exception e) {
			logger.info("preHandle="+e.getMessage());
		}
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {

	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
	}
	  //对于请求是ajax请求重定向问题的处理方法
    public void redirect(HttpServletRequest request, HttpServletResponse response) throws IOException{
        //获取当前请求的路径
        String basePath = request.getScheme() + "://" + request.getServerName() + ":"  + request.getServerPort()+request.getContextPath();
//        response.getOutputStream().write("账号在别处登录。".getBytes("UTF-8"));
        //如果request.getHeader("X-Requested-With") 返回的是"XMLHttpRequest"说明就是ajax请求,需要特殊处理 否则直接重定向就可以了
        if("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))){
            //告诉ajax我是重定向
            response.setHeader("REDIRECT", "REDIRECT");
            //告诉ajax我重定向的路径
            response.setHeader("CONTENTPATH", basePath+"/login");
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        }else{
            response.sendRedirect(basePath + "/login");
        }
    }
}

ShiroUtils.java

    shiro 工具类,获取subject对象。

package xxx.xxx.system.utils;


import java.security.Principal;
import java.util.Collection;
import java.util.List;


import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;


import xxx.xxx.entity.sys.UserDO;


public class ShiroUtils {
    @Autowired
    private static SessionDAO sessionDAO;


    public static Subject getSubjct() {
        return SecurityUtils.getSubject();
    }
    public static UserDO getUser() {
        Object object = getSubjct().getPrincipal();
        return (UserDO)object;
    }
    public static Integer getUserId() {
        return getUser().getUserId();
    }
    public static void logout() {
        getSubjct().logout();
    }


    public static List<Principal> getPrinciples() {
        List<Principal> principals = null;
        Collection<Session> sessions = sessionDAO.getActiveSessions();
        return principals;
    }
}

UserDO.java

    用户对象。

package xxx.xxx.entity.sys;

import org.springframework.format.annotation.DateTimeFormat;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

public class UserDO implements Serializable {
    private static final long serialVersionUID = 1L;
    //用户id
    private Integer userId;
    // 用户名
    private String username;
    // 用户真实姓名
    private String name;
    // 密码
    private String password;
}

application.yml

jedis :
  pool :
    host : 127.0.0.1
    port : 9001
    password: abcd123
    maxTotal: 100
    maxIdle: 10
    maxWaitMillis : 100000

猜你喜欢

转载自blog.csdn.net/angry_mills/article/details/80111780