Browse Source

1.新增spring-data-redis,并集成基于Redis的session管理器,配置开关在全局配置中
2.新增REST授权拦截器,并针对rest接口进行登录授权和校验

zhouchenglin 6 năm trước cách đây
mục cha
commit
684ad5019d
21 tập tin đã thay đổi với 1615 bổ sung9 xóa
  1. 4 0
      pom.xml
  2. 13 0
      src/main/java/net/chenlin/dp/common/annotation/RestAnon.java
  3. 69 0
      src/main/java/net/chenlin/dp/common/constant/RestApiConstant.java
  4. 28 0
      src/main/java/net/chenlin/dp/common/support/config/RedisConfig.java
  5. 19 4
      src/main/java/net/chenlin/dp/common/support/config/ShiroConfig.java
  6. 15 1
      src/main/java/net/chenlin/dp/common/support/config/WebConfig.java
  7. 106 0
      src/main/java/net/chenlin/dp/common/support/interceptor/RestApiInterceptor.java
  8. 12 0
      src/main/java/net/chenlin/dp/common/support/properties/GlobalProperties.java
  9. 646 0
      src/main/java/net/chenlin/dp/common/support/redis/RedisCacheManager.java
  10. 30 0
      src/main/java/net/chenlin/dp/common/support/shiro/listener/UserSessionListener.java
  11. 45 0
      src/main/java/net/chenlin/dp/common/support/shiro/session/SerializableUtils.java
  12. 122 0
      src/main/java/net/chenlin/dp/common/support/shiro/session/UserSession.java
  13. 158 0
      src/main/java/net/chenlin/dp/common/support/shiro/session/UserSessionDAO.java
  14. 29 0
      src/main/java/net/chenlin/dp/common/support/shiro/session/UserSessionFactory.java
  15. 48 0
      src/main/java/net/chenlin/dp/common/support/shiro/session/UserSessionManager.java
  16. 54 0
      src/main/java/net/chenlin/dp/common/utils/TokenUtils.java
  17. 119 0
      src/main/java/net/chenlin/dp/modules/api/controller/RestAuthController.java
  18. 29 0
      src/main/java/net/chenlin/dp/modules/sys/service/SysUserService.java
  19. 56 4
      src/main/java/net/chenlin/dp/modules/sys/service/impl/SysUserServiceImpl.java
  20. 12 0
      src/main/resources/application-sit.yml
  21. 1 0
      src/main/resources/application.yml

+ 4 - 0
pom.xml

@@ -154,6 +154,10 @@
             <artifactId>httpclient</artifactId>
             <version>${httpclient-version}</version>
         </dependency>
+        <dependency>
+            <groupId>redis.clients</groupId>
+            <artifactId>jedis</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 13 - 0
src/main/java/net/chenlin/dp/common/annotation/RestAnon.java

@@ -0,0 +1,13 @@
+package net.chenlin.dp.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * rest接口不需要授权注解
+ * @author zcl<yczclcn@163.com>
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RestAnon {
+}

+ 69 - 0
src/main/java/net/chenlin/dp/common/constant/RestApiConstant.java

@@ -0,0 +1,69 @@
+package net.chenlin.dp.common.constant;
+
+import net.chenlin.dp.common.entity.R;
+
+/**
+ * rest模块常量
+ * @author zcl<yczclcn@163.com>
+ */
+public class RestApiConstant {
+
+    /** 请求验证地址 **/
+    public static final String AUTH_REQUEST = "/rest/auth";
+
+    /** token有效期校验请求 **/
+    public static final String AUTH_CHECK = "/rest/authStatus";
+
+    /** 授权标识 **/
+    public static final String AUTH_TOKEN = "token";
+
+    /** token过期时间:默认7天 **/
+    public static Long TOKEN_EXPIRE = 604800000L;
+
+    /**
+     * token错误提示枚举
+     */
+    public enum TokenErrorEnum {
+        TOKEN_ENABLE(200, "验证成功"),
+        TOKEN_NOT_FOUND(1000, "验证失败:获取token为空"),
+        TOKEN_INVALID(1001, "验证失败:无效的token"),
+        TOKEN_EXPIRED(1002, "验证失败:过期的token"),
+        USER_USERNAME_NULL(1003, "验证失败:用户名为空"),
+        USER_PASSWORD_NULL(1004, "验证失败:密码为空"),
+        USER_USERNAME_INVALID(1005, "验证失败:用户名不存在"),
+        USER_PASSWORD_INVALID(1006, "验证失败:密码错误"),
+        USER_DISABLE(1007, "验证失败:用户被禁用,请联系管理员"),
+        USER_AUTH_ERROR(1008, "验证失败:登录服务暂不可用");
+
+        private int code;
+
+        private String msg;
+
+        TokenErrorEnum(int code, String msg) {
+            this.code = code;
+            this.msg = msg;
+        }
+
+        public int getCode() {
+            return code;
+        }
+
+        public void setCode(int code) {
+            this.code = code;
+        }
+
+        public String getMsg() {
+            return msg;
+        }
+
+        public void setMsg(String msg) {
+            this.msg = msg;
+        }
+
+        public R getResp() {
+            return R.error(code, msg);
+        }
+
+    }
+
+}

+ 28 - 0
src/main/java/net/chenlin/dp/common/support/config/RedisConfig.java

@@ -0,0 +1,28 @@
+package net.chenlin.dp.common.support.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+/**
+ * redis配置
+ * @author zcl<yczclcn@163.com>
+ */
+@Configuration
+public class RedisConfig {
+
+    @Bean
+    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
+        redisTemplate.setConnectionFactory(redisConnectionFactory);
+        redisTemplate.setKeySerializer(new StringRedisSerializer());
+        redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
+        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+        redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
+        return redisTemplate;
+    }
+
+}

+ 19 - 4
src/main/java/net/chenlin/dp/common/support/config/ShiroConfig.java

@@ -1,8 +1,13 @@
 package net.chenlin.dp.common.support.config;
 
+import net.chenlin.dp.common.support.properties.GlobalProperties;
+import net.chenlin.dp.common.support.shiro.listener.UserSessionListener;
+import net.chenlin.dp.common.support.shiro.session.UserSessionDAO;
+import net.chenlin.dp.common.support.shiro.session.UserSessionFactory;
 import net.chenlin.dp.modules.sys.shiro.UserFilter;
 import net.chenlin.dp.modules.sys.shiro.UserRealm;
 import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.session.SessionListener;
 import org.apache.shiro.session.mgt.SessionManager;
 import org.apache.shiro.spring.LifecycleBeanPostProcessor;
 import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
@@ -12,16 +17,16 @@ import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
 import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.DependsOn;
 
 import javax.servlet.Filter;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
+import java.util.*;
 
 /**
  * shiro配置
  * @author zcl<yczclcn@163.com>
  */
+@DependsOn("springContextUtils")
 @Configuration
 public class ShiroConfig {
 
@@ -43,11 +48,19 @@ public class ShiroConfig {
      * @return
      */
     @Bean
-    public SessionManager sessionManager(){
+    public SessionManager sessionManager(GlobalProperties globalProperties){
         DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
         sessionManager.setSessionValidationSchedulerEnabled(true);
         sessionManager.setSessionIdUrlRewritingEnabled(false);
         sessionManager.setDeleteInvalidSessions(true);
+        if (globalProperties.isRedisSessionDao()) {
+            // 开启redis会话管理器
+            sessionManager.setSessionFactory(new UserSessionFactory());
+            sessionManager.setSessionDAO(new UserSessionDAO());
+            List<SessionListener> sessionListeners = new ArrayList<>();
+            sessionListeners.add(new UserSessionListener());
+            sessionManager.setSessionListeners(sessionListeners);
+        }
         return sessionManager;
     }
 
@@ -62,6 +75,7 @@ public class ShiroConfig {
 
     /**
      * shiro过滤器
+     * /rest/**,请求采用token验证(net.chenlin.dp.common.support.interceptor.RestApiInterceptor)
      * @param securityManager
      * @return
      */
@@ -85,6 +99,7 @@ public class ShiroConfig {
         filterMap.put("/static/**", "anon");
         filterMap.put("/error/**", "anon");
         filterMap.put("/login", "anon");
+        filterMap.put("/rest/**", "anon");
         filterMap.put("/**", "user");
         shiroFilter.setFilterChainDefinitionMap(filterMap);
 

+ 15 - 1
src/main/java/net/chenlin/dp/common/support/config/WebConfig.java

@@ -1,5 +1,6 @@
 package net.chenlin.dp.common.support.config;
 
+import net.chenlin.dp.common.support.interceptor.RestApiInterceptor;
 import net.chenlin.dp.common.support.properties.GlobalProperties;
 import net.chenlin.dp.common.xss.XssFilter;
 import org.apache.commons.lang.StringUtils;
@@ -10,8 +11,10 @@ import org.springframework.boot.web.server.ErrorPageRegistry;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.DependsOn;
 import org.springframework.http.HttpStatus;
 import org.springframework.web.filter.DelegatingFilterProxy;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
@@ -22,6 +25,7 @@ import java.io.File;
  * web配置
  * @author zcl<yczclcn@163.com>
  */
+@DependsOn("springContextUtils")
 @Configuration
 public class WebConfig implements WebMvcConfigurer, ErrorPageRegistrar {
 
@@ -35,7 +39,7 @@ public class WebConfig implements WebMvcConfigurer, ErrorPageRegistrar {
     @Override
     public void addResourceHandlers(ResourceHandlerRegistry registry) {
         if (StringUtils.isBlank(globalProperties.getUploadLocation())) {
-            throw new RuntimeException("文件上传路径为空,请先在application.yml中配置{spring.http.multipart.location}路径!");
+            throw new RuntimeException("文件上传路径为空,请先在application.yml中配置{global.upload-location}路径!");
         }
         if (!globalProperties.getUploadLocation().endsWith("/")) {
             throw new RuntimeException("文件上传路径必须以 / 结束!");
@@ -48,6 +52,16 @@ public class WebConfig implements WebMvcConfigurer, ErrorPageRegistrar {
                 .addResourceLocations(globalProperties.getRegisterUploadLocation());
     }
 
+    /**
+     * 配置拦截器
+     * @param registry
+     */
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        // 注册rest拦截器
+        registry.addInterceptor(new RestApiInterceptor()).addPathPatterns("/rest/**");
+    }
+
     /**
      * shiroFilter注册
      * @return

+ 106 - 0
src/main/java/net/chenlin/dp/common/support/interceptor/RestApiInterceptor.java

@@ -0,0 +1,106 @@
+package net.chenlin.dp.common.support.interceptor;
+
+import net.chenlin.dp.common.annotation.RestAnon;
+import net.chenlin.dp.common.constant.RestApiConstant;
+import net.chenlin.dp.common.utils.JSONUtils;
+import net.chenlin.dp.common.utils.SpringContextUtils;
+import net.chenlin.dp.common.utils.TokenUtils;
+import net.chenlin.dp.common.utils.WebUtils;
+import net.chenlin.dp.modules.sys.entity.SysUserEntity;
+import net.chenlin.dp.modules.sys.entity.SysUserTokenEntity;
+import net.chenlin.dp.modules.sys.service.SysUserService;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.context.annotation.DependsOn;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * rest api拦截器
+ * @author zcl<yczclcn@163.com>
+ */
+@DependsOn("springContextUtils")
+public class RestApiInterceptor extends HandlerInterceptorAdapter {
+
+    private SysUserService userService = (SysUserService) SpringContextUtils.getBean("sysUserService");
+
+    /**
+     * 拦截
+     * @param request
+     * @param response
+     * @param handler
+     * @return
+     */
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+        // 静态资源请求拦截
+        if (handler instanceof ResourceHttpRequestHandler) {
+            return true;
+        }
+        // 有RestAnon注解的方法不拦截
+        if (handler instanceof HandlerMethod) {
+            HandlerMethod handlerMethod = (HandlerMethod) handler;
+            if (handlerMethod.hasMethodAnnotation(RestAnon.class)) {
+                return true;
+            }
+        }
+        return checkToken(request, response);
+    }
+
+    /**
+     * token校验
+     * @param request
+     * @param response
+     * @return
+     */
+    private boolean checkToken(HttpServletRequest request, HttpServletResponse response) {
+        // 登录 或 有效状态校验 请求直接通过
+        String requestPath = request.getServletPath();
+        if (RestApiConstant.AUTH_REQUEST.equals(requestPath) || RestApiConstant.AUTH_CHECK.equals(requestPath)) {
+            return true;
+        }
+        // 校验请求是否包含验证信息
+        String token = getToken(request);
+        if (StringUtils.isBlank(token)) {
+            WebUtils.write(response, JSONUtils.beanToJson(RestApiConstant.TokenErrorEnum.TOKEN_NOT_FOUND.getResp()));
+            return false;
+        }
+        // token校验
+        SysUserTokenEntity sysUserTokenEntity = userService.getUserTokenByToken(token);
+        if (sysUserTokenEntity == null) {
+            WebUtils.write(response, JSONUtils.beanToJson(RestApiConstant.TokenErrorEnum.TOKEN_INVALID.getResp()));
+            return false;
+        }
+        // token过期
+        if (TokenUtils.isExpired(sysUserTokenEntity.getGmtExpire())) {
+            WebUtils.write(response, JSONUtils.beanToJson(RestApiConstant.TokenErrorEnum.TOKEN_EXPIRED.getResp()));
+            return false;
+        }
+        // 用户校验
+        SysUserEntity sysUserEntity = userService.getUserByIdForToken(sysUserTokenEntity.getUserId());
+        if (sysUserEntity.getStatus() == 0) {
+            WebUtils.write(response, JSONUtils.beanToJson(RestApiConstant.TokenErrorEnum.USER_DISABLE.getResp()));
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 获取token
+     * @param request
+     * @return
+     */
+    private String getToken(HttpServletRequest request) {
+        // 请求头token
+        String token = request.getHeader(RestApiConstant.AUTH_TOKEN);
+        if (StringUtils.isBlank(token)) {
+            // 请求参数token
+            return request.getParameter(RestApiConstant.AUTH_TOKEN);
+        }
+        return token;
+    }
+
+}

+ 12 - 0
src/main/java/net/chenlin/dp/common/support/properties/GlobalProperties.java

@@ -20,6 +20,9 @@ public class GlobalProperties {
     /** 文件上传目录访问路径 **/
     private String uploadMapping;
 
+    /** 是否开启redis会话管理器 **/
+    private boolean redisSessionDao;
+
     /**
      * WebConfig注册上传路径
      * @return
@@ -57,4 +60,13 @@ public class GlobalProperties {
     public void setUploadMapping(String uploadMapping) {
         this.uploadMapping = uploadMapping;
     }
+
+    public boolean isRedisSessionDao() {
+        return redisSessionDao;
+    }
+
+    public void setRedisSessionDao(boolean redisSessionDao) {
+        this.redisSessionDao = redisSessionDao;
+    }
+
 }

+ 646 - 0
src/main/java/net/chenlin/dp/common/support/redis/RedisCacheManager.java

@@ -0,0 +1,646 @@
+package net.chenlin.dp.common.support.redis;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Redis Util
+ * 操作字符串:redisTemplate.opsForValue();
+ * 操作hash:redisTemplate.opsForHash();
+ * 操作list:redisTemplate.opsForList();
+ * 操作set:redisTemplate.opsForSet();
+ * 操作有序set:redisTemplate.opsForZSet();
+ * @author zcl<yczclcn@163.com>
+ */
+@Component
+public class RedisCacheManager {
+
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+
+    /**
+     * 指定缓存失效时间
+     *
+     * @param key
+     *            键
+     * @param time
+     *            时间(秒)
+     * @return
+     */
+    public boolean expire(String key, long time) {
+        try {
+            if (time > 0) {
+                redisTemplate.expire(key, time, TimeUnit.SECONDS);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 根据key 获取过期时间
+     *
+     * @param key
+     *            键 不能为null
+     * @return 时间(秒) 返回0代表为永久有效
+     */
+    public long getExpire(String key) {
+        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 判断key是否存在
+     *
+     * @param key
+     *            键
+     * @return true 存在 false不存在
+     */
+    public boolean hasKey(String key) {
+        try {
+            return redisTemplate.hasKey(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 删除缓存
+     *
+     * @param key
+     *            可以传一个值 或多个
+     */
+    @SuppressWarnings("unchecked")
+    public void del(String... key) {
+        if (key != null && key.length > 0) {
+            if (key.length == 1) {
+                redisTemplate.delete(key[0]);
+            } else {
+                redisTemplate.delete(CollectionUtils.arrayToList(key));
+            }
+        }
+    }
+
+    // ============================String=============================
+    /**
+     * 普通缓存获取
+     *
+     * @param key
+     *            键
+     * @return 值
+     */
+    public Object get(String key) {
+        return key == null ? null : redisTemplate.opsForValue().get(key);
+    }
+
+    /**
+     * 普通缓存放入
+     *
+     * @param key
+     *            键
+     * @param value
+     *            值
+     * @return true成功 false失败
+     */
+    public boolean set(String key, Object value) {
+        try {
+            redisTemplate.opsForValue().set(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+
+    }
+
+    /**
+     * 普通缓存放入并设置时间
+     *
+     * @param key
+     *            键
+     * @param value
+     *            值
+     * @param time
+     *            时间(秒) time要大于0 如果time小于等于0 将设置无限期
+     * @return true成功 false 失败
+     */
+    public boolean set(String key, Object value, long time) {
+        try {
+            if (time > 0) {
+                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
+            } else {
+                set(key, value);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 递增
+     *
+     * @param key
+     *            键
+     * @param delta
+     *            要增加几(大于0)
+     * @return
+     */
+    public long incr(String key, long delta) {
+        if (delta < 0) {
+            throw new RuntimeException("递增因子必须大于0");
+        }
+        return redisTemplate.opsForValue().increment(key, delta);
+    }
+
+    /**
+     * 递减
+     *
+     * @param key
+     *            键
+     * @param delta
+     *            要减少几(小于0)
+     * @return
+     */
+    public long decr(String key, long delta) {
+        if (delta < 0) {
+            throw new RuntimeException("递减因子必须大于0");
+        }
+        return redisTemplate.opsForValue().increment(key, -delta);
+    }
+
+    // ================================Map=================================
+    /**
+     * HashGet
+     *
+     * @param key
+     *            键 不能为null
+     * @param item
+     *            项 不能为null
+     * @return 值
+     */
+    public Object hget(String key, String item) {
+        return redisTemplate.opsForHash().get(key, item);
+    }
+
+    /**
+     * 获取hashKey对应的所有键值
+     *
+     * @param key
+     *            键
+     * @return 对应的多个键值
+     */
+    public Map<Object, Object> hmget(String key) {
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    /**
+     * HashSet
+     *
+     * @param key
+     *            键
+     * @param map
+     *            对应多个键值
+     * @return true 成功 false 失败
+     */
+    public boolean hmset(String key, Map<String, Object> map) {
+        try {
+            redisTemplate.opsForHash().putAll(key, map);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * HashSet 并设置时间
+     *
+     * @param key
+     *            键
+     * @param map
+     *            对应多个键值
+     * @param time
+     *            时间(秒)
+     * @return true成功 false失败
+     */
+    public boolean hmset(String key, Map<String, Object> map, long time) {
+        try {
+            redisTemplate.opsForHash().putAll(key, map);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 向一张hash表中放入数据,如果不存在将创建
+     *
+     * @param key
+     *            键
+     * @param item
+     *            项
+     * @param value
+     *            值
+     * @return true 成功 false失败
+     */
+    public boolean hset(String key, String item, Object value) {
+        try {
+            redisTemplate.opsForHash().put(key, item, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 向一张hash表中放入数据,如果不存在将创建
+     *
+     * @param key
+     *            键
+     * @param item
+     *            项
+     * @param value
+     *            值
+     * @param time
+     *            时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
+     * @return true 成功 false失败
+     */
+    public boolean hset(String key, String item, Object value, long time) {
+        try {
+            redisTemplate.opsForHash().put(key, item, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 删除hash表中的值
+     *
+     * @param key
+     *            键 不能为null
+     * @param item
+     *            项 可以使多个 不能为null
+     */
+    public void hdel(String key, Object... item) {
+        redisTemplate.opsForHash().delete(key, item);
+    }
+
+    /**
+     * 判断hash表中是否有该项的值
+     *
+     * @param key
+     *            键 不能为null
+     * @param item
+     *            项 不能为null
+     * @return true 存在 false不存在
+     */
+    public boolean hHasKey(String key, String item) {
+        return redisTemplate.opsForHash().hasKey(key, item);
+    }
+
+    /**
+     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
+     *
+     * @param key
+     *            键
+     * @param item
+     *            项
+     * @param by
+     *            要增加几(大于0)
+     * @return
+     */
+    public double hincr(String key, String item, double by) {
+        return redisTemplate.opsForHash().increment(key, item, by);
+    }
+
+    /**
+     * hash递减
+     *
+     * @param key
+     *            键
+     * @param item
+     *            项
+     * @param by
+     *            要减少记(小于0)
+     * @return
+     */
+    public double hdecr(String key, String item, double by) {
+        return redisTemplate.opsForHash().increment(key, item, -by);
+    }
+
+    // ============================set=============================
+    /**
+     * 根据key获取Set中的所有值
+     *
+     * @param key
+     *            键
+     * @return
+     */
+    public Set<Object> sGet(String key) {
+        try {
+            return redisTemplate.opsForSet().members(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 根据value从一个set中查询,是否存在
+     *
+     * @param key
+     *            键
+     * @param value
+     *            值
+     * @return true 存在 false不存在
+     */
+    public boolean sHasKey(String key, Object value) {
+        try {
+            return redisTemplate.opsForSet().isMember(key, value);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将数据放入set缓存
+     *
+     * @param key
+     *            键
+     * @param values
+     *            值 可以是多个
+     * @return 成功个数
+     */
+    public long sSet(String key, Object... values) {
+        try {
+            return redisTemplate.opsForSet().add(key, values);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 将set数据放入缓存
+     *
+     * @param key
+     *            键
+     * @param time
+     *            时间(秒)
+     * @param values
+     *            值 可以是多个
+     * @return 成功个数
+     */
+    public long sSetAndTime(String key, long time, Object... values) {
+        try {
+            Long count = redisTemplate.opsForSet().add(key, values);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return count;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 获取set缓存的长度
+     *
+     * @param key
+     *            键
+     * @return
+     */
+    public long sGetSetSize(String key) {
+        try {
+            return redisTemplate.opsForSet().size(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 移除值为value的
+     *
+     * @param key
+     *            键
+     * @param values
+     *            值 可以是多个
+     * @return 移除的个数
+     */
+    public long setRemove(String key, Object... values) {
+        try {
+            Long count = redisTemplate.opsForSet().remove(key, values);
+            return count;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+    // ===============================list=================================
+
+    /**
+     * 获取list缓存的内容
+     *
+     * @param key
+     *            键
+     * @param start
+     *            开始
+     * @param end
+     *            结束 0 到 -1代表所有值
+     * @return
+     */
+    public List<Object> lGet(String key, long start, long end) {
+        try {
+            return redisTemplate.opsForList().range(key, start, end);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 获取list缓存的长度
+     *
+     * @param key
+     *            键
+     * @return
+     */
+    public long lGetListSize(String key) {
+        try {
+            return redisTemplate.opsForList().size(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 通过索引 获取list中的值
+     *
+     * @param key
+     *            键
+     * @param index
+     *            索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
+     * @return
+     */
+    public Object lGetIndex(String key, long index) {
+        try {
+            return redisTemplate.opsForList().index(key, index);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key
+     *            键
+     * @param value
+     *            值
+     * @return
+     */
+    public boolean lSet(String key, Object value) {
+        try {
+            redisTemplate.opsForList().rightPush(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key
+     *            键
+     * @param value
+     *            值
+     * @param time
+     *            时间(秒)
+     * @return
+     */
+    public boolean lSet(String key, Object value, long time) {
+        try {
+            redisTemplate.opsForList().rightPush(key, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key
+     *            键
+     * @param value
+     *            值
+     * @return
+     */
+    public boolean lSet(String key, List<Object> value) {
+        try {
+            redisTemplate.opsForList().rightPushAll(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key
+     *            键
+     * @param value
+     *            值
+     * @param time
+     *            时间(秒)
+     * @return
+     */
+    public boolean lSet(String key, List<Object> value, long time) {
+        try {
+            redisTemplate.opsForList().rightPushAll(key, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 根据索引修改list中的某条数据
+     *
+     * @param key
+     *            键
+     * @param index
+     *            索引
+     * @param value
+     *            值
+     * @return
+     */
+    public boolean lUpdateIndex(String key, long index, Object value) {
+        try {
+            redisTemplate.opsForList().set(key, index, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 移除N个值为value
+     *
+     * @param key
+     *            键
+     * @param count
+     *            移除多少个
+     * @param value
+     *            值
+     * @return 移除的个数
+     */
+    public long lRemove(String key, long count, Object value) {
+        try {
+            Long remove = redisTemplate.opsForList().remove(key, count, value);
+            return remove;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+}

+ 30 - 0
src/main/java/net/chenlin/dp/common/support/shiro/listener/UserSessionListener.java

@@ -0,0 +1,30 @@
+package net.chenlin.dp.common.support.shiro.listener;
+
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.SessionListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * session监听器
+ * @author zcl<yczclcn@163.com>
+ */
+public class UserSessionListener implements SessionListener {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(UserSessionListener.class);
+
+    @Override
+    public void onStart(Session session) {
+        LOGGER.debug("会话创建:{}", session.getId());
+    }
+
+    @Override
+    public void onStop(Session session) {
+        LOGGER.debug("会话停止:{}", session.getId());
+    }
+
+    @Override
+    public void onExpiration(Session session) {
+        LOGGER.debug("会话过期:{}", session.getId());
+    }
+}

+ 45 - 0
src/main/java/net/chenlin/dp/common/support/shiro/session/SerializableUtils.java

@@ -0,0 +1,45 @@
+package net.chenlin.dp.common.support.shiro.session;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.session.Session;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * session序列化工具
+ * @author zcl<yczclcn@163.com>
+ */
+public class SerializableUtils {
+
+    public static String serialize(Session session) {
+        if (null == session) {
+            return null;
+        }
+        try {
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            ObjectOutputStream oos = new ObjectOutputStream(bos);
+            oos.writeObject(session);
+            return Base64.encodeToString(bos.toByteArray());
+        } catch (Exception e) {
+            throw new RuntimeException("serialize session error", e);
+        }
+    }
+
+    public static Session deserialize(String sessionStr) {
+        if (StringUtils.isBlank(sessionStr)) {
+            return null;
+        }
+        try {
+            ByteArrayInputStream bis = new ByteArrayInputStream(Base64.decode(sessionStr));
+            ObjectInputStream ois = new ObjectInputStream(bis);
+            return (Session) ois.readObject();
+        } catch (Exception e) {
+            throw new RuntimeException("deserialize session error", e);
+        }
+    }
+
+}

+ 122 - 0
src/main/java/net/chenlin/dp/common/support/shiro/session/UserSession.java

@@ -0,0 +1,122 @@
+package net.chenlin.dp.common.support.shiro.session;
+
+import org.apache.shiro.session.mgt.SimpleSession;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * 由于SimpleSession lastAccessTime更改后也会调用SessionDao update方法,
+ * 增加标识位,如果只是更新lastAccessTime SessionDao update方法直接返回
+ * @author zcl<yczclcn@163.com>
+ */
+public class UserSession extends SimpleSession implements Serializable {
+
+    private boolean isChanged;
+
+    public UserSession() {
+        super();
+        this.setChanged(true);
+    }
+
+    public UserSession(String host) {
+        super(host);
+        this.setChanged(true);
+    }
+
+    @Override
+    public void setId(Serializable id) {
+        super.setId(id);
+        this.setChanged(true);
+    }
+
+    @Override
+    public void setStopTimestamp(Date stopTimestamp) {
+        super.setStopTimestamp(stopTimestamp);
+        this.setChanged(true);
+    }
+
+    @Override
+    public void setExpired(boolean expired) {
+        super.setExpired(expired);
+        this.setChanged(true);
+    }
+
+    @Override
+    public void setTimeout(long timeout) {
+        super.setTimeout(timeout);
+        this.setChanged(true);
+    }
+
+    @Override
+    public void setHost(String host) {
+        super.setHost(host);
+        this.setChanged(true);
+    }
+
+    @Override
+    public void setAttributes(Map<Object, Object> attributes) {
+        super.setAttributes(attributes);
+        this.setChanged(true);
+    }
+
+    @Override
+    public void setAttribute(Object key, Object value) {
+        super.setAttribute(key, value);
+        this.setChanged(true);
+    }
+
+    @Override
+    public Object removeAttribute(Object key) {
+        this.setChanged(true);
+        return super.removeAttribute(key);
+    }
+
+    /**
+     * 停止
+     */
+    @Override
+    public void stop() {
+        super.stop();
+        this.setChanged(true);
+    }
+
+    /**
+     * 设置过期
+     */
+    @Override
+    protected void expire() {
+        this.stop();
+        this.setExpired(true);
+    }
+
+    public boolean isChanged() {
+        return isChanged;
+    }
+
+    public void setChanged(boolean isChanged) {
+        this.isChanged = isChanged;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return super.equals(obj);
+    }
+
+    @Override
+    protected boolean onEquals(SimpleSession ss) {
+        return super.onEquals(ss);
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return super.toString();
+    }
+
+}

+ 158 - 0
src/main/java/net/chenlin/dp/common/support/shiro/session/UserSessionDAO.java

@@ -0,0 +1,158 @@
+package net.chenlin.dp.common.support.shiro.session;
+
+import net.chenlin.dp.common.support.redis.RedisCacheManager;
+import net.chenlin.dp.common.utils.SpringContextUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.UnknownSessionException;
+import org.apache.shiro.session.mgt.ValidatingSession;
+import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
+import org.apache.shiro.subject.support.DefaultSubjectContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.DependsOn;
+
+import java.io.Serializable;
+
+/**
+ * 基于redis的sessionDAO
+ * @author zcl<yczclcn@163.com>
+ */
+@DependsOn("springContextUtils")
+public class UserSessionDAO extends EnterpriseCacheSessionDAO {
+
+    private static final Logger LOG = LoggerFactory.getLogger(UserSessionDAO.class);
+
+    private static final String SHIRO_SESSION_ID = "dp:session:id:";
+
+    private RedisCacheManager redisCacheManager;
+
+    public UserSessionDAO() {
+        redisCacheManager = (RedisCacheManager) SpringContextUtils.getBean("redisCacheManager");
+    }
+
+    /**
+     * 更新session的最后一次访问时间
+     * @param session
+     */
+    @Override
+    protected void doUpdate(Session session) {
+        try {
+            // 如果会话过期/停止 没必要再更新了
+            if(session instanceof ValidatingSession && !((ValidatingSession)session).isValid()) {
+                return;
+            }
+        } catch (Exception e) {
+            LOG.warn("验证session失败", e.getMessage());
+        }
+
+        try {
+            if (session instanceof UserSession) {
+                // 如果没有主要字段(除lastAccessTime以外其他字段)发生改变
+                UserSession userSession = (UserSession) session;
+                if (!userSession.isChanged()) {
+                    return;
+                }
+                String sessionKey = generateSessionKey(session);
+                setSession(sessionKey, session);
+                LOG.debug("doUpdate >>>>> sessionId:{}", session.getId());
+            }
+        } catch (Exception e) {
+            LOG.warn("更新session失败", e.getMessage());
+        }
+
+    }
+
+    /**
+     * 删除session
+     * @param session
+     */
+    @Override
+    protected void doDelete(Session session) {
+        try {
+            super.doDelete(session);
+            String sessionKey = generateSessionKey(session);
+            redisCacheManager.del(sessionKey);
+            LOG.debug("doDelete >>>>> sessionId:{}", session.getId());
+        } catch (Exception e) {
+            LOG.warn("删除session失败", e.getMessage());
+        }
+    }
+
+    /**
+     * 创建session,保存到redis
+     * @param session
+     * @return
+     */
+    @Override
+    protected Serializable doCreate(Session session) {
+        Serializable sessionId = super.doCreate(session);
+        assignSessionId(session, sessionId);
+        try {
+            String sessionKey = generateSessionKey(sessionId);
+            setSession(sessionKey, session);
+            LOG.debug("doCreate >>>>> sessionId:{}", sessionId);
+        } catch (Exception e) {
+            LOG.warn("创建session失败", e.getMessage());
+        }
+        return sessionId;
+    }
+
+    /**
+     * 如果Session中没有登陆信息就调用doReadSession方法从Redis中重读
+     * @param sessionId
+     * @return
+     */
+    @Override
+    public Session readSession(Serializable sessionId) {
+        Session session = getCachedSession(sessionId);
+        if (session == null
+                || session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) == null) {
+            session = this.doReadSession(sessionId);
+            if (session == null) {
+                throw new UnknownSessionException("There is no session with id: {" + sessionId + "}");
+            } else {
+                cache(session, session.getId());
+            }
+        }
+        return session;
+    }
+
+    /**
+     * 获取session
+     * @param sessionId
+     * @return
+     */
+    @Override
+    protected Session doReadSession(Serializable sessionId) {
+        Session session = null;
+        try {
+            session = getSession(generateSessionKey(sessionId));
+            LOG.debug("doReadSession >>>>> sessionId:{}", session.getId());
+        } catch (Exception e) {
+            LOG.warn("读取session失败", e.getMessage());
+        }
+        return session;
+    }
+
+    private Session getSession(String key) {
+        String session = String.valueOf(redisCacheManager.get(key));
+        if (StringUtils.isNotEmpty(session)) {
+            return SerializableUtils.deserialize(session);
+        }
+        return null;
+    }
+
+    private void setSession(String key, Session session) {
+        redisCacheManager.set(key, SerializableUtils.serialize(session), session.getTimeout() / 1000);
+    }
+
+    private String generateSessionKey(Session session) {
+        return SHIRO_SESSION_ID + session.getId();
+    }
+
+    private String generateSessionKey(Serializable sessionId) {
+        return SHIRO_SESSION_ID + sessionId;
+    }
+
+}

+ 29 - 0
src/main/java/net/chenlin/dp/common/support/shiro/session/UserSessionFactory.java

@@ -0,0 +1,29 @@
+package net.chenlin.dp.common.support.shiro.session;
+
+import net.chenlin.dp.common.utils.WebUtils;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.SessionContext;
+import org.apache.shiro.session.mgt.SessionFactory;
+import org.apache.shiro.web.session.mgt.DefaultWebSessionContext;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * session工厂
+ * @author zcl<yczclcn@163.com>
+ */
+public class UserSessionFactory implements SessionFactory{
+
+    @Override
+    public Session createSession(SessionContext initData) {
+        UserSession session = new UserSession();
+        HttpServletRequest request = (HttpServletRequest)initData.get(DefaultWebSessionContext.class.getName() + ".SERVLET_REQUEST");
+        session.setHost(getIpAddress(request));
+        return session;
+    }
+
+    public static String getIpAddress(HttpServletRequest request) {
+        return WebUtils.getIpAddr(request);
+    }
+
+}

+ 48 - 0
src/main/java/net/chenlin/dp/common/support/shiro/session/UserSessionManager.java

@@ -0,0 +1,48 @@
+package net.chenlin.dp.common.support.shiro.session;
+
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.UnknownSessionException;
+import org.apache.shiro.session.mgt.SessionKey;
+import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
+import org.apache.shiro.web.session.mgt.WebSessionKey;
+
+import javax.servlet.ServletRequest;
+import java.io.Serializable;
+
+/**
+ * UserSessionManager
+ * @author zcl<yczclcn@163.com>
+ */
+public class UserSessionManager extends DefaultWebSessionManager {
+
+    /**
+     * 获取session
+     * 优化单次请求需要多次访问redis的问题
+     * @param sessionKey
+     * @return
+     * @throws UnknownSessionException
+     */
+    @Override
+    protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
+        Serializable sessionId = getSessionId(sessionKey);
+
+        ServletRequest request = null;
+        if (sessionKey instanceof WebSessionKey) {
+            request = ((WebSessionKey) sessionKey).getServletRequest();
+        }
+
+        if (request != null && null != sessionId) {
+            Object sessionObj = request.getAttribute(sessionId.toString());
+            if (sessionObj != null) {
+                return (Session) sessionObj;
+            }
+        }
+
+        Session session = super.retrieveSession(sessionKey);
+        if (request != null && null != sessionId) {
+            request.setAttribute(sessionId.toString(), session);
+        }
+        return session;
+    }
+
+}

+ 54 - 0
src/main/java/net/chenlin/dp/common/utils/TokenUtils.java

@@ -0,0 +1,54 @@
+package net.chenlin.dp.common.utils;
+
+import net.chenlin.dp.common.exception.RRException;
+
+import java.security.MessageDigest;
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * token工具类
+ * @author zcl<yczclcn@163.com>
+ */
+public class TokenUtils {
+
+    public static String generateValue() {
+        return generateValue(UUID.randomUUID().toString());
+    }
+
+    private static final char[] hexCode = "0123456789abcdef".toCharArray();
+
+    public static String toHexString(byte[] data) {
+        if(data == null) {
+            return null;
+        }
+        StringBuilder r = new StringBuilder(data.length*2);
+        for ( byte b : data) {
+            r.append(hexCode[(b >> 4) & 0xF]);
+            r.append(hexCode[(b & 0xF)]);
+        }
+        return r.toString();
+    }
+
+    public static String generateValue(String param) {
+        try {
+            MessageDigest algorithm = MessageDigest.getInstance("MD5");
+            algorithm.reset();
+            algorithm.update(param.getBytes());
+            byte[] messageDigest = algorithm.digest();
+            return toHexString(messageDigest);
+        } catch (Exception e) {
+            throw new RRException("生成Token失败", e);
+        }
+    }
+
+    /**
+     * token是否过期
+     * @param tokenExpireTime
+     * @return
+     */
+    public static boolean isExpired(Date tokenExpireTime) {
+        return tokenExpireTime.getTime() < System.currentTimeMillis();
+    }
+
+}

+ 119 - 0
src/main/java/net/chenlin/dp/modules/api/controller/RestAuthController.java

@@ -0,0 +1,119 @@
+package net.chenlin.dp.modules.api.controller;
+
+import net.chenlin.dp.common.annotation.RestAnon;
+import net.chenlin.dp.common.constant.RestApiConstant;
+import net.chenlin.dp.common.entity.R;
+import net.chenlin.dp.common.utils.MD5Utils;
+import net.chenlin.dp.common.utils.TokenUtils;
+import net.chenlin.dp.modules.sys.controller.AbstractController;
+import net.chenlin.dp.modules.sys.entity.SysUserEntity;
+import net.chenlin.dp.modules.sys.entity.SysUserTokenEntity;
+import net.chenlin.dp.modules.sys.service.SysUserService;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * rest授权controller
+ * @author zcl<yczclcn@163.com>
+ */
+@RestController
+public class RestAuthController extends AbstractController {
+
+    @Autowired
+    private SysUserService sysUserService;
+
+    /**
+     * 登录授权校验
+     * @return
+     */
+    @RequestMapping(RestApiConstant.AUTH_REQUEST)
+    public R auth() {
+        String username = getParam("username").trim();
+        String password = getParam("password").trim();
+        // 用户名为空
+        if (StringUtils.isBlank(username)) {
+            return RestApiConstant.TokenErrorEnum.USER_USERNAME_NULL.getResp();
+        }
+        // 密码为空
+        if (StringUtils.isBlank(password)) {
+            return RestApiConstant.TokenErrorEnum.USER_PASSWORD_NULL.getResp();
+        }
+        // 用户名不存在
+        SysUserEntity sysUserEntity = sysUserService.getByUserName(username);
+        if (sysUserEntity == null) {
+            return RestApiConstant.TokenErrorEnum.USER_USERNAME_INVALID.getResp();
+        }
+        // 密码错误
+        String checkPassword = MD5Utils.encrypt(username, password);
+        if (!sysUserEntity.getPassword().equals(checkPassword)) {
+            return RestApiConstant.TokenErrorEnum.USER_PASSWORD_INVALID.getResp();
+        }
+        // 用户被锁定
+        if (sysUserEntity.getStatus() == 0) {
+            return RestApiConstant.TokenErrorEnum.USER_DISABLE.getResp();
+        }
+        // 保存或者更新token
+        String token = TokenUtils.generateValue();
+        int count = sysUserService.saveOrUpdateToken(sysUserEntity.getUserId(), token);
+        if (count > 0) {
+            R success = RestApiConstant.TokenErrorEnum.TOKEN_ENABLE.getResp();
+            success.put(RestApiConstant.AUTH_TOKEN, token);
+            return success;
+        }
+        return RestApiConstant.TokenErrorEnum.USER_AUTH_ERROR.getResp();
+    }
+
+    /**
+     * 异步校验token,用于接口异步校验登录状态
+     * @return
+     */
+    @RequestMapping(RestApiConstant.AUTH_CHECK)
+    public R authStatus() {
+        String token = getParam(RestApiConstant.AUTH_TOKEN);
+        // token为空
+        if (StringUtils.isBlank(token)) {
+            return RestApiConstant.TokenErrorEnum.TOKEN_NOT_FOUND.getResp();
+        }
+        SysUserTokenEntity sysUserTokenEntity = sysUserService.getUserTokenByToken(token);
+        // 无效的token:token不存在
+        if (sysUserTokenEntity == null) {
+            return RestApiConstant.TokenErrorEnum.TOKEN_INVALID.getResp();
+        }
+        // 无效token:用户不存在
+        SysUserEntity sysUserEntity = sysUserService.getUserByIdForToken(sysUserTokenEntity.getUserId());
+        if (sysUserEntity == null) {
+            return RestApiConstant.TokenErrorEnum.TOKEN_INVALID.getResp();
+        }
+        // token过期
+        if (TokenUtils.isExpired(sysUserTokenEntity.getGmtExpire())) {
+            return RestApiConstant.TokenErrorEnum.TOKEN_EXPIRED.getResp();
+        }
+        // 用户是否禁用
+        if (sysUserEntity.getStatus() == 0) {
+            return RestApiConstant.TokenErrorEnum.USER_DISABLE.getResp();
+        }
+        return RestApiConstant.TokenErrorEnum.TOKEN_ENABLE.getResp();
+    }
+
+    /**
+     * 验证拦截
+     * @return
+     */
+    @RequestMapping("/rest/testAuth")
+    public String test() {
+        return "auth token";
+    }
+
+    /**
+     * 匿名调用:@RestAnon
+     * @return
+     */
+    @RequestMapping("/rest/testAnon")
+    @RestAnon
+    public String testAnon() {
+        return "rest anon";
+    }
+
+}

+ 29 - 0
src/main/java/net/chenlin/dp/modules/sys/service/SysUserService.java

@@ -3,6 +3,7 @@ package net.chenlin.dp.modules.sys.service;
 import net.chenlin.dp.common.entity.Page;
 import net.chenlin.dp.common.entity.R;
 import net.chenlin.dp.modules.sys.entity.SysUserEntity;
+import net.chenlin.dp.modules.sys.entity.SysUserTokenEntity;
 
 import java.util.List;
 import java.util.Map;
@@ -104,5 +105,33 @@ public interface SysUserService {
 	 * @return
 	 */
 	List<Long> listAllOrgId(Long userId);
+
+	/**
+	 * 保存用户token
+	 * @param userId
+	 * @return
+	 */
+	int saveOrUpdateToken(Long userId, String token);
+
+	/**
+	 * 根据token查询
+	 * @param token
+	 * @return
+	 */
+	SysUserTokenEntity getUserTokenByToken(String token);
+
+	/**
+	 * 根据userId查询
+	 * @param userId
+	 * @return
+	 */
+	SysUserTokenEntity getUserTokenByUserId(Long userId);
+
+	/**
+	 * 根据userId查询:用于token校验
+	 * @param userId
+	 * @return
+	 */
+	SysUserEntity getUserByIdForToken(Long userId);
 	
 }

+ 56 - 4
src/main/java/net/chenlin/dp/modules/sys/service/impl/SysUserServiceImpl.java

@@ -1,16 +1,15 @@
 package net.chenlin.dp.modules.sys.service.impl;
 
+import net.chenlin.dp.common.constant.RestApiConstant;
 import net.chenlin.dp.common.constant.SystemConstant;
 import net.chenlin.dp.common.entity.Page;
 import net.chenlin.dp.common.entity.Query;
 import net.chenlin.dp.common.entity.R;
 import net.chenlin.dp.common.utils.CommonUtils;
 import net.chenlin.dp.common.utils.MD5Utils;
-import net.chenlin.dp.modules.sys.dao.SysMenuMapper;
-import net.chenlin.dp.modules.sys.dao.SysRoleMapper;
-import net.chenlin.dp.modules.sys.dao.SysUserMapper;
-import net.chenlin.dp.modules.sys.dao.SysUserRoleMapper;
+import net.chenlin.dp.modules.sys.dao.*;
 import net.chenlin.dp.modules.sys.entity.SysUserEntity;
+import net.chenlin.dp.modules.sys.entity.SysUserTokenEntity;
 import net.chenlin.dp.modules.sys.service.SysUserService;
 import org.apache.commons.lang.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -37,6 +36,9 @@ public class SysUserServiceImpl implements SysUserService {
 	@Autowired
 	private SysUserRoleMapper sysUserRoleMapper;
 
+	@Autowired
+	private SysUserTokenMapper sysUserTokenMapper;
+
 	/**
 	 * 分页查询用户列表
 	 * @param params
@@ -225,4 +227,54 @@ public class SysUserServiceImpl implements SysUserService {
 		return CommonUtils.msg(count);
 	}
 
+	/**
+	 * 保存用户token
+	 * @param userId
+	 * @return
+	 */
+	@Override
+	public int saveOrUpdateToken(Long userId, String token) {
+		Date now = new Date();
+		Date expire = new Date(now.getTime() + RestApiConstant.TOKEN_EXPIRE);
+		SysUserTokenEntity sysUserTokenEntity = new SysUserTokenEntity();
+		sysUserTokenEntity.setUserId(userId);
+		sysUserTokenEntity.setGmtModified(now);
+		sysUserTokenEntity.setGmtExpire(expire);
+		sysUserTokenEntity.setToken(token);
+		int count = sysUserTokenMapper.update(sysUserTokenEntity);
+		if (count == 0) {
+			return sysUserTokenMapper.save(sysUserTokenEntity);
+		}
+		return count;
+	}
+
+	/**
+	 * 根据token查询
+	 * @param token
+	 * @return
+	 */
+	@Override
+	public SysUserTokenEntity getUserTokenByToken(String token) {
+		return sysUserTokenMapper.getByToken(token);
+	}
+
+	/**
+	 * 根据userId查询
+	 * @param userId
+	 * @return
+	 */
+	@Override
+	public SysUserTokenEntity getUserTokenByUserId(Long userId) {
+		return sysUserTokenMapper.getByUserId(userId);
+	}
+
+	/**
+	 * 根据userId查询:用于token校验
+	 * @param userId
+	 * @return
+	 */
+	public SysUserEntity getUserByIdForToken(Long userId) {
+		return sysUserMapper.getObjectById(userId);
+	}
+
 }

+ 12 - 0
src/main/resources/application-sit.yml

@@ -36,3 +36,15 @@ spring:
         wall:
           config:
             multi-statement-allow: true
+  reids:
+    host: 127.0.0.1
+    port: 6379
+    password:
+    database: 0
+    timeout: 1000
+    jedis:
+      pool:
+        max-active: 6000
+        max-wait: 1000
+        max-idle: 400
+

+ 1 - 0
src/main/resources/application.yml

@@ -10,6 +10,7 @@ server:
 global:
   upload-location: /Users/zhouchenglin/dev/dp-boot/ #文件上传目录
   upload-mapping: /upload/ #文件上传目录访问路径
+  redis-session-dao: false #是否使用使用redis会话管理器,true为开启,false为关闭
 
 spring:
   # 环境 dev:开发环境|test:测试环境|prod:生成环境