大家好,我是烤鸭:
今天给大家分享简单的单点登录的实现方式。
环境及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
- 转载地址:https://blog.csdn.net/Angry_Mills/article/details/80111780