一、前言
1.在大型互联网公司下,一个公司有多个平台系统项目,而且有可能每个项目开发语言都不同,同时每个平台也会分别有APP、PC(前后端分离)、小程序端等,而且服务部署在分布式环境下,要实现支持PC、APP(ios、android)、小程序等多端的会话共享,本文章采取的方案是spring+springmvc+Interceptor+jwt+redis实现sso单点登录。假如用传统方式session、cookie实现不太好,第一如果用session实现,会面临集群环境下session共享问题(可以用springsession来解决),但是session存过多数据会给服务器带来压力。第二如果用cookie实现,会面临域名跨域问题,而且不适合用在native app里面:native app不好管理cookie,毕竟它不是浏览器。
2.通俗来讲,JWT是一个含签名并携带用户相关信息的加密串,页面请求校验登录接口时,请求头中携带JWT串到后端服务,后端通过签名加密串匹配校验,保证信息未被篡改。校验通过则认为是可靠的请求,将正常返回数据,同时JSON解析成对象。
优点:在非跨域环境下使用JWT机制是一个非常不错的选择,实现方式简单,操作方便,能够快速实现。由于服务端不存储用户状态信息,因此大用户量,对后台服务也不会造成压力;
缺点:跨域实现相对比较麻烦,安全性也有待探讨。因为JWT令牌返回到页面中,可以使用js获取到,如果遇到XSS攻击令牌可能会被盗取,在JWT还没超时的情况下,就会被获取到敏感数据信息(敏感信息最好别放置JWT中)。
但是我们可以针对令牌盗取,做一些防范:比如前后端传输参数RSA验签防止篡改,采取https加密参数传输。没有一个互联网平台做到绝对安全的,只能说提高安全性,把风险降到最低。
二、实现代码
2.1.引入jwt的相关jar包,在项目pom.xml中引入:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>2.2.0</version>
</dependency>
2.2 JWT工具类
public class JWT {
private static final String SECRET = "填写你的秘钥";
private static final String EXP = "exp";
private static final String PAYLOAD = "payload";
//加密,传入一个对象和有效期
public static <T> String sign(T object, long maxAge) {
try {
final JWTSigner signer = new JWTSigner(SECRET);
final Map<String, Object> claims = new HashMap<String, Object>();
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(object);
claims.put(PAYLOAD, jsonString);
claims.put(EXP, System.currentTimeMillis() + maxAge);
return signer.sign(claims);
} catch(Exception e) {
return null;
}
}
//解密,传入一个加密后的token字符串和解密后的类型
public static<T> T unsign(String jwt, Class<T> classT) {
final JWTVerifier verifier = new JWTVerifier(SECRET);
try {
final Map<String,Object> claims= verifier.verify(jwt);
if (claims.containsKey(EXP) && claims.containsKey(PAYLOAD)) {
//long exp = (Long)claims.get(EXP);
//long currentTimeMillis = System.currentTimeMillis();
//if (exp > currentTimeMillis) {
String json = (String)claims.get(PAYLOAD);
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(json, classT);
//}
}
return null;
} catch (Exception e) {
return null;
}
}
public static void main(String[] args) {
System.out.println(System.currentTimeMillis());
//加密
String ss = sign("123444444444444444444444444444444444444444444444444444444444444444444;4a", 0);
System.out.println(ss);
//解密
System.out.println(unsign("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NTg5NDY4NDUzODksInBheWxvYWQiOiJcIjExMTFcIiJ9.jJwpdHgpNLjU1vL0DZhxtAsNEBG0ysXrJEvMf_X5OXk", String.class));
}
}
2.3编写用户登录LoginInterceptor.java拦截器
public class LoginInterceptor implements HandlerInterceptor{
public static final String REDIS_TOKEN_FLAG = "redis_token";
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//过滤不匹配的请求
PrintWriter writer = null;
if(handler instanceof DefaultServletHttpRequestHandler){
writer = response.getWriter();
Map<String, Object> resultMap = ResponseCode.buildReturnMap(ResponseCode.REQUEST_URL_NOT_SERVICE, false);
responseMessage(response, writer, resultMap);
return false;
}
response.setCharacterEncoding("utf-8");
String token = request.getHeader("token");
//拼接redis的key=平台标识+登录方式+(会员表id或用户授权表id)
//token不存在
if(StringUtils.isEmpty(token)) {
writer = response.getWriter();
Map<String, Object> resultMap = LoginResponseCode.buildReturnMap(LoginResponseCode.LOGIN_TOKEN_NOT_NULL, false);
responseMessage(response, writer, resultMap);
return false;
}
//tokenRedisKey是redis存token的key
String tokenRedisKey = LoginInterceptor.REDIS_TOKEN_FLAG + JWT.unsign(token, String.class);
if(StringUtils.isEmpty(tokenRedisKey)){
writer = response.getWriter();
Map<String, Object> resultMap = LoginResponseCode.buildReturnMap(LoginResponseCode.USERID_NOT_UNAUTHORIZED, false);
responseMessage(response, writer, resultMap);
return false;
}
//根据token截取用户id
String redisToken = (String)RedisClusterUtils.get(tokenRedisKey);
LoginResponseCode loginCode = null;
if(StringUtils.isEmpty(redisToken)){
//未登录
loginCode = LoginResponseCode.LOGIN_TIME_EXP;
}
else if(!StringUtils.equals(redisToken, token)){
//用户在同设备(APP、PC、小程序、公众号,比如APP设备不能登录两个一样的账号)上登录,你已经被踢下线
loginCode= LoginResponseCode.RESPONSE_CODE_LOGIN_ERROR;
}
if(loginCode != null){
writer = response.getWriter();
Map<String, Object> resultMap = LoginResponseCode.buildReturnMap(loginCode, false);
responseMessage(response, writer, resultMap);
return false;
}
//重新设置会话时间,半小时
RedisClusterUtils.expire(LoginInterceptor.REDIS_TOKEN_FLAG + token, 1800);
request.setAttribute("tokenRedisKey", tokenRedisKey);
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
private void responseMessage(HttpServletResponse response, PrintWriter out, Object responseVO) {
response.setContentType("application/json; charset=utf-8");
out.print(JSONObject.fromObject(responseVO).toString());
out.flush();
out.close();
}
}
2.4 定义LoginResponseCode
public enum LoginResponseCode {
USERID_NOT_NULL(3001,"用户id不能为空."),
LOGIN_TOKEN_NOT_NULL(3002,"登录token不能为空."),
USERID_NOT_UNAUTHORIZED(3003, "用户token无效"),
RESPONSE_CODE_UNLOGIN_ERROR(421, "未登录异常"),
RESPONSE_CODE_LOGIN_ERROR(422, "用户在另一台机器上登录,你已经被踢下线"),
LOGIN_TIME_EXP(3004, "未登录或登录时间超时,请重新登录");
// 成员变量
private int code; //状态码
private String message; //返回消息
// 构造方法
private LoginResponseCode(int code,String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public static ResponseVO buildEnumResponseVO(LoginResponseCode responseCode, Object data) {
return new ResponseVO(responseCode.getCode(),responseCode.getMessage(),data);
}
public static Map<String, Object> buildReturnMap(LoginResponseCode responseCode, Object data) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("code", responseCode.getCode());
map.put("message", responseCode.getMessage());
map.put("data", data);
return map;
}
}
2.5 定义ResponseCode 枚举类
public enum ResponseCode {
RESPONSE_CODE_FAILURE(10000,"请求失败、结果处理失败"),
RESPONSE_CODE_SUCCESS(200,"请求成功、结果处理成功"),
RESPONSE_CODE_PARAM_FORMAT_ERROR(10002,"请求失败、参数格式错误"),
RESPONSE_CODE_PARAM_ERROR(10003,"参数错误"),
RESPONSE_CODE_REQ_CANNOT_EMPTY(10004,"必要的请求参数不能为空"),
RESPONSE_CODE_USER_DOES_NOT_EXIST(10005,"用户不存在"),
RESPONSE_CODE_QUERY_SUCCESS(10006,"数据查询成功"),
RESPONSE_CODE_QUERY_NO_DATA(10007,"无数据或者结果"),
USER_LOGIN_PWD_ERROR(10008,"用户名密码错误"),
REQUEST_URL_NOT_SERVICE(10009,"访问了非服务路径"),
RESPONSE_CODE_QUERY_REMARKS_50(10010,"退货原因不得超过50字"),
RESPONSE_CODE_UNLOGIN_ERROR(421,"未登录异常"),
RESPONSE_CODE_LOGIN_SUCCESS(200,"用户登录成功"),
RESPONSE_CODE_NO_PERMISSION(403,"无权访问该系统"),
RESPONSE_CODE_SYSTEM_ERROR(500,"系统内部异常"),
RESPONSE_CODE_TIME_OUT(504,"上游服务端网关超时"),
REGISTER_MOBILE_EXIST(2001,"手机号码已经注册"),
REGISTER_VERIFICA_CODE_INVALID(2002,"验证码错误或者已经失效,请重新获取验证码"),
VERIFICA_CODE_ERROR(2003,"验证码不正确或失效"),
REFERRER_NOT_EXIST(2004, "推荐人不存在"),
PROXY_ACTIVATE_CODE_ERROR(2005, "激活码错误"),
USER_TRADE_PASSWORD_ERROR(2006,"用户交易密码错误"),
APP_VERSION_MISMATCHING(2007,"app版本号不是最新版本"),
USER_IDNO_NOT_MATCHING(2008, "用户身份证号码不匹配"),
BANK_CARD_MUST_HAVE_TO_ONE(2010, "银行卡必须保留一张"),
OLD_PASSWORD_ERROR(2011, "原密码不正确"),
NEW_PASSWORD_INCONSISTENT(2012, "新密码不一致"),
V_PASSWORD_REG(2013, "密码为数字或字母"),
TWO_PASSWORD_INCONSISTENCIES(2014, "密码和确认密码不一致"),
ACCEPT_PROTOCOL(2015, "请阅读和接受协议"),
LOGOUT_SUCESS(2016, "退出成功"),
TRANSCODE_FAILED(2017,"转码失败"),
APPLY_SALE_AFTER_STATUS_ERROR(2018,"申请售后失败,该订单未付款"),
NO_PAY_VALIDATE(2019,"支付未认证"),
PAY_VALIDATE_PROCESSING(2020,"支付人工审核中"),
PAY_VALIDATE_FAIL(2021,"支付人工审核失败,请重新填写"),
PAY_VALIDATE_SUCESS(2022,"支付人工审核成功"),
ACCOUNT_EXIST(2023,"账号已存在"),
ROLE_NAME_EXIST(2024,"角色已存在"),
PASSWORD_IS_BLENK(2025,"密码不能为空"),
DECRYPT_FAIL(2026,"参数解密失败"),
NOT_BINDING_MEMBER(2027,"未绑定会员"),
GET_ACCESSTOKEN_FAIL(2028,"获取accessToken失败"),
TWO_PASSWORD_CONSISTENCIES(2029, "新旧密码一致"),
NOT_PLATFORM_AUTH(2030, "该平台账号未授权或者已停用"),
AUTH_CODE_NOT_NULL(2031, "平台授权码不能为空");
private int code; //状态码
private String message; //返回消息
private static String version = "v1.0"; //版本号
// 构造方法
private ResponseCode(int code,String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public static ResponseVO buildEnumResponseVO(ResponseCode responseCode, Object data) {
return new ResponseVO(responseCode.getCode(),responseCode.getMessage(),data);
}
public static Map<String, Object> buildReturnMap(ResponseCode responseCode, Object data) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("code", responseCode.getCode());
map.put("version", version);
map.put("message", responseCode.getMessage());
map.put("data", data);
return map;
}
/**
* 自定义状态码返回结果Map
* @param code 状态码
* @param message 消息提示
* @param data 数据
* @return
*/
public static Map<String, Object> customBuildReturnMap(String code, String message, Object data) {
if(StringUtils.isEmpty(code)){
code = "9999"; // 自定义错误消息状态码
}
Map<String, Object> map = new HashMap<String, Object>();
map.put("code", code);
map.put("version", version);
map.put("message", message);
map.put("data", data);
return map;
}
}
2.6 RedisClusterUtils(redis集群工具类)
public class RedisClusterUtils {
@SuppressWarnings("rawtypes")
private static RedisTemplate redisTemplate;
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
RedisClusterUtils.redisTemplate = redisTemplate;
}
static {
if (null == redisTemplate) {
RedisClusterUtils.redisTemplate = SpringContextHolder
.getBean(RedisTemplate.class);
}
}
/**
* 普通缓存获取
*
* @param key
* 键
* @return 值
*/
public static Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
*
* @param key
* 键
* @param value
* 值
* @return true成功 false失败
*/
public static 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 static 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 by
* 要增加几(大于0)
* @return
*/
public static long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key
* 键
* @param by
* 要减少几(小于0)
* @return
*/
public static long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
/**
* HashGet
*
* @param key
* 键 不能为null
* @param item
* 项 不能为null
* @return 值
*/
public static Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
public static Object hget(byte[] key, byte[] item) {
RedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setHashValueSerializer(stringSerializer);
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
*
* @param key
* 键
* @return 对应的多个键值
*/
public static Map<Object, Object> hmget(String key) {
RedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setHashValueSerializer(stringSerializer);
return redisTemplate.opsForHash().entries(key);
}
/**
*
* @Title: hkeys
* @Description: 获取hash所有字段
* @param @param key
* @param @return 设定文件
* @return Set<byte[]> 返回类型
* @throws
*/
public static Set<byte[]> hkeys(String key) {
return redisTemplate.opsForHash().keys(key);
}
/**
* HashSet
*
* @param key
* 键
* @param map
* 对应多个键值
* @return true 成功 false 失败
*/
public static 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 static 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 static boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public static boolean hset(byte[] key, byte[] item, byte[] 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 static 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 static void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
public static void hdel(byte[] key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key
* 键 不能为null
* @param item
* 项 不能为null
* @return true 存在 false不存在
*/
public static boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key
* 键
* @param item
* 项
* @param by
* 要增加几(大于0)
* @return
*/
public static 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 static double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
/**
* 根据key获取Set中的所有值
*
* @param key
* 键
* @return
*/
public static 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 static 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 static 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 static 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 static long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key
* 键
* @param values
* 值 可以是多个
* @return 移除的个数
*/
public static long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取list缓存的内容
*
* @param key
* 键
* @param start
* 开始
* @param end
* 结束 0 到 -1代表所有值
* @return
*/
public static 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 static 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 static 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
* 值
* @param time
* 时间(秒)
* @return
*/
public static 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 static 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
* 值
* @param time
* 时间(秒)
* @return
*/
public static 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 static 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 static 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 static 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;
}
}
/**
* 获取byte[]类型Key
*
* @param key
* @return
*/
public static byte[] getBytesKey(Object object) {
if (object instanceof String) {
return StringUtils.getBytes((String) object);
} else {
return ObjectUtils.serialize(object);
}
}
/**
* Object转换byte[]类型
*
* @param key
* @return
*/
public static byte[] toBytes(Object object) {
return ObjectUtils.serialize(object);
}
/**
* byte[]型转换Object
*
* @param key
* @return
*/
public static Object toObject(byte[] bytes) {
return ObjectUtils.unserialize(bytes);
}
/**
* 指定缓存失效时间
*
* @param key
* 键
* @param time
* 时间(秒)
* @return
*/
public static 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 static long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key
* 键
* @return true 存在 false不存在
*/
public static boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param key
* 可以传一个值 或多个
*/
public static void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
/**
* 判断key是否存在
*
* @param key
* 键
* @return true 存在 false不存在
*/
public static Collection<byte[]> values(String key) {
return redisTemplate.opsForHash().values(key);
}
}
2.7 redis集群配置spring-cluster-jedis.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd"
default-lazy-init="true">
<description>Jedis Configuration</description>
<!-- 加载配置属性文件 -->
<context:property-placeholder
ignore-unresolvable="true" location="classpath:jeesz.properties" />
<bean id="redisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.maxTotal}" />
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxWaitMillis" value="${redis.maxWaitMillis}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean>
<!-- Redis集群配置 -->
<bean id="redisClusterConfig"
class="org.springframework.data.redis.connection.RedisClusterConfiguration">
<property name="maxRedirects" value="3"></property>
<property name="clusterNodes">
<list>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg value="${redis.port1}"></constructor-arg>
<constructor-arg value="${redis.port1}"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg value="${redis.host2}"></constructor-arg>
<constructor-arg value="${redis.port2}"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg value="${redis.host3}"></constructor-arg>
<constructor-arg value="${redis.port3}"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg value="${redis.host4}"></constructor-arg>
<constructor-arg value="${redis.port4}"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg value="${redis.host5}"></constructor-arg>
<constructor-arg value="${redis.port5}"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg value="${redis.host6}"></constructor-arg>
<constructor-arg value="${redis.port6}"></constructor-arg>
</bean>
</list>
</property>
</bean>
<!-- ReDis连接工厂 -->
<bean id="redis4CacheConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg name="clusterConfig" ref="redisClusterConfig" />
<property name="timeout" value="${redis.timeout}" />
<property name="poolConfig" ref="redisPoolConfig" />
</bean>
<!-- 存储序列化 -->
<bean name="stringRedisSerializer"
class="com.sml.sz.serializer.StringRedisSerializer" />
<!-- 集群Resis使用模板 -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="redis4CacheConnectionFactory" />
<property name="keySerializer" ref="stringRedisSerializer" />
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
</property>
<property name="hashKeySerializer" ref="stringRedisSerializer" />
</bean>
</beans>
2.8 redis集群配置属性文件
#--------------redis settings--------------
redis.keyPrefix=
redis.host1=${redis.host1}
redis.port1=${redis.port1}
redis.host2=${redis.host2}
redis.port2=${redis.port2}
redis.host3=${redis.host3}
redis.port3=${redis.port3}
redis.host4=${redis.host4}
redis.port4=${redis.port4}
redis.host5=${redis.host5}
redis.port5=${redis.port5}
redis.host6=${redis.host6}
redis.port6=${redis.port6}
redis.maxTotal=1000
redis.maxIdle=100
redis.maxWaitMillis=1000
redis.timeout=30000
redis.testOnBorrow=true
redis.password=
2.9 登录Controller
@RequestMapping(value = "/login", method = RequestMethod.POST)
public ResponseVO login(@RequestBody Member member, HttpServletRequest request, HttpServletResponse response) {
//获取平台标识
String strPlatformFlag = (String)request.getAttribute("platformFlag");
System.out.println("==============strPlatformFlag:=============" + strPlatformFlag);
member.setPlatformFlag(strPlatformFlag);
//判断必要参数是否为空
if (!StringUtils.isNotBlank(member.getLoginName()) || !StringUtils.isNotBlank(member.getPassword()) ||
!StringUtils.isNotBlank(member.getPlatformFlag()) ||!StringUtils.isNotBlank(member.getLoginType()) ||
!StringUtils.isNotBlank(member.getSource())) {
return ResponseCode.buildEnumResponseVO(ResponseCode.RESPONSE_CODE_REQ_CANNOT_EMPTY, null);
}
//请求参数,登录日志记录
String strParam = member.toString();
//判断用户是否存在
Member user = memberService.getByLoginName(member.getLoginName());
if(user == null ){
return ResponseCode.buildEnumResponseVO(ResponseCode.RESPONSE_CODE_USER_DOES_NOT_EXIST, null);
}
String strPassword = "";
String password = "";
try {
//RSA解密密码
strPassword = RSAUtils.decrypt(member.getPassword(), RSAUtils.getPrivateKey());
member.setPassword(strPassword);
password = MD5EncryptUtil.encryptMD5Code(member.getPassword());
}catch (Exception e){
return ResponseCode.buildEnumResponseVO(ResponseCode.DECRYPT_FAIL, null);
}
try {
//判断密码是否正确并添加token
if (StringUtils.equals(password, user.getPassword())) {
String token = JWT.sign(member.getSource() + user.getId(), 0);
//token在redis中的key等于redis_token+ 来源(0.公众号,1.PC,2.APP3.小程序) + 用户id
//token在redis中的value等于key加密
RedisClusterUtils.set(LoginInterceptor.REDIS_TOKEN_FLAG + member.getSource() + user.getId(), token, 1800);
Map dataMap = new HashMap<String, Object>();
dataMap.put("token", token);
dataMap.put("mid", user.getId());
return ResponseCode.buildEnumResponseVO(ResponseCode.RESPONSE_CODE_LOGIN_SUCCESS, JSONObject.fromObject(dataMap));
}
}catch (Exception e){
return ResponseCode.buildEnumResponseVO(ResponseCode.RESPONSE_CODE_FAILURE, null);
}
return ResponseCode.buildEnumResponseVO(ResponseCode.USER_LOGIN_PWD_ERROR, false);
}