您现在的位置是:主页 > news > 深圳大型网站建设/最好的免费推广平台
深圳大型网站建设/最好的免费推广平台
admin2025/6/17 19:00:41【news】
简介深圳大型网站建设,最好的免费推广平台,做网站分为哪些功能的网站,wordpress meta keytoken的意思,即"令牌",有这个令牌就可以进行访问,就具有一定的权限,在传统的应用中,一般是存储于session,但在当下很多分布式微服务的应用中,session就显得力不从心了。当用户第一次登…
token的意思,即"令牌",有这个令牌就可以进行访问,就具有一定的权限,在传统的应用中,一般是存储于session,但在当下很多分布式微服务的应用中,session就显得力不从心了。当用户第一次登陆之后,服务端生成一个token并返回给客户端,客户端每次以后带着这个token访问即可,无需用户名和密码。token可以防止表单重复提交和身份验证等用途
流程:
1、用户登录之后,先校验用户名和密码
2、用户名和密码通过之后生成token,以zset加分数存储redis,方便后期排行进行删除不活跃的用户
3、将token返回前端,后面的每次请求携带token
4、验证token,通过则返回相应的数据,删除token,再重新生成一个token返回给前端存储在本地。不通过,重新登录
也要进行限制token无限增加,比如登录之后生成token之后,无限登录,导致redis中的token无限增加以及相关用户信息修改之后要进行重新生成token
application.yml:看redis配置就好,这里是单机
server: port: 8081# 下面是配置undertow作为服务器的参数undertow: # 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程io-threads: 4# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载worker-threads: 20# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理# 每块buffer的空间大小,越小的空间被利用越充分buffer-size: 1024# 是否分配的直接内存direct-buffers: true#启用shutdown
#endpoints: # shutdown: # enable: true
#禁用密码验证
#endpoints: #shutdown: #sensitive: false
#linux关闭的脚本
#curl -X POST host:port/shutdown#开启shutdown的安全验证
#endpoints: #shutdown: #sensitive: true#验证用户名和密码
#security: #user: #name: admin#password: admin#角色
#management: #address: 127.0.0.1#port: 8081#security: #role: SUPERUSERspring: datasource: type: com.alibaba.druid.pool.DruidDataSourcedriverClassName: com.mysql.cj.jdbc.Driverdriver-class-name: com.mysql.cj.jdbc.Driverplatform: mysqlurl: jdbc:mysql://xxx.xxx.xxx.xx:5306/miniprogram?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=falseusername: rootpassword: admin
# ELASTICSEARCH (ElasticsearchProperties)
# Elasticsearch cluster name.
# data:
# elasticsearch:
# cluster-name: elasticsearch
# cluster-nodes: 192.168.13.111:9300,192.168.13.222:9300
# repositories:
# enabled: trueredis:
# database: 1host: 127.0.0.1port: 6379password: timeout: 10000lettuce:pool:minIdle: 0maxIdle: 10maxWait: 10000max-active: 10
# cluster:
# nodes:
# - 192.168.91.5:9001
# - 192.168.91.5:9002
# - 192.168.91.5:9003
# - 192.168.91.5:9004
# - 192.168.91.5:9005
# - 192.168.91.5:9006activemq: queueName: mvp.queuetopicName: mvp.topic#账号密码user: userpassword: user#URL of the ActiveMQ broker.broker-url: tcp://localhost:61616in-memory: false#必须使用连接池pool: #启用连接池enabled: true#连接池最大连接数max-connections: 5#空闲的连接过期时间,默认为30秒idle-timeout: 30smybatis: typeAliasesPackage: com.pinyu.miniprogram.mysql.entitymapper-locations: classpath:mapper/**/*Mapper.xml
mapper:mappers: com.pinyu.miniprogram.mysql.mappers.BaseMapperidentity: mysql#logging.config:
# classpath: test/log4j2_test.xml
自定义token注解,只要在controller方法贴上此注解表示此请求是必须要进行身份验证
package com.pinyu.miniprogram.global.ann;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequireToken {}
登录:
@RequestMapping("/login")public String login(@RequestBody Map<String, String> logMap) throws Exception {String memberName = logMap.get("memberName");String pwd = logMap.get("pwd");MemberEntity m = new MemberEntity();m.setMemberName(memberName);pwd = MD5Utils.convertMD5(MD5Utils.md5(pwd));m.setPwd(pwd);List<MemberEntity> list = service.select(m);if (list != null && list.size() > 0) {MemberEntity member = list.get(0);token.delToken(member);//清除之前redis有的token信息String memberToken = token.saveToken(member);//重新生成tokenreturn JsonMsg.OK(memberToken);} else {return JsonMsg.OK("用户名或密码错误");}}
Token:
package com.pinyu.miniprogram.utils;import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.pinyu.miniprogram.config.redis.RedisUtils;
import com.pinyu.miniprogram.mysql.entity.member.MemberEntity;
import com.pinyu.miniprogram.utils.date.DateUtils;
import com.pinyu.miniprogram.utils.result.Code;
import com.pinyu.miniprogram.utils.result.JsonMsg;
import com.pinyu.miniprogram.utils.rsa.RSAUtils;@Component
public class Token {private final static String TOKEN_ERROR = "error";public final static Integer TOKEN_ERROR_CODE = -1;public static final String TOKEN_VALIDA_NULL="token为空";public static final String TOKEN_VALIDA_ERROR="token验证错误";public static final String TOKEN_VALIDA_FAIL="token验证失败";public static String Error(String msg) {Map<String, Object> map = new HashMap<String, Object>();map.put("code", TOKEN_ERROR_CODE);map.put("msg", msg);return JSONObject.toJSONStringWithDateFormat(map, DateUtils.YYYY_MM_DD_HH_MM_SS,SerializerFeature.WriteMapNullValue);}public static final String MEMBER_TOKEN = "MEMBER_TOKEN";@Autowiredprivate RedisUtils redisUtils;// 生成token前的格式为token:id:时间:六位随机数public String generateToken(Long id) throws Exception{StringBuilder tokenBuilder = new StringBuilder();//生成未加密的token:tokenBuilder.append("token:").append(id).append(":").append(new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date())+":").append(new Random().nextInt((999999 - 111111 + 1)) + 111111);String token=(RSAUtils.privateEncrypt(tokenBuilder.toString(), RSAUtils.getPrivateKey(RSAUtils.privateKey)));System.out.println("token=>" + token.toString());return token.toString();}public String saveToken(MemberEntity member) throws Exception {String token = generateToken(member.getId());redisUtils.zsetAdd(MEMBER_TOKEN, token, Double.valueOf(System.currentTimeMillis()));// 设置zset用于线程根据分数定时清理不活跃用户redisUtils.set(token, member, 60 * 60 * 24 * 30L);// 存储相关用户信息(权限等信息)redisUtils.set(String.valueOf(member.getId()), token);// 用于重新登录,但之前token还存在的情况,通过id获取相应的token来进行之前的token清理return token;}public void delToken(MemberEntity member) {Object object = redisUtils.get(String.valueOf(member.getId()));if (object != null) {String token = (String) object;redisUtils.del(token);// 移除token以及相对应的权限信息redisUtils.del(String.valueOf(member.getId()));// 移除tokenredisUtils.remove(MEMBER_TOKEN, token);// 移除zset排行的token,避免一个用户重复排行}}
}
RedisUtils:
package com.pinyu.miniprogram.config.redis;import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;/*** @author ypp 创建时间:2018年11月19日 下午4:58:04* @Description: TODO(redis缓存工具类)*/
@Component
public class RedisUtils {@Autowired@Qualifier("redisTemplate")private RedisTemplate<String, Object> redisTemplate;// =============================common============================/*** 指定缓存失效时间* @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 by 要增加几(大于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 by 要减少几(小于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 值* @param time 时间(秒)* @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 值* @param time 时间(秒)* @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;}}/*** 添加一个元素, zset与set最大的区别就是每个元素都有一个score,因此有个排序的辅助功能; zadd** @param key* @param value* @param score*/public boolean zsetAdd(String key, Object value, Double score) {try {redisTemplate.opsForZSet().add(key, value,score);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 删除元素 zrem** @param key* @param value*/public void remove(String key, String value) {redisTemplate.opsForZSet().remove(key, value);}/*** @param key* @param value* @param score*/public Double incrScore(String key, String value, double score) {return redisTemplate.opsForZSet().incrementScore(key, value, score);}/*** 查询value对应的score zscore** @param key* @param value* @return*/public Double score(String key, String value) {return redisTemplate.opsForZSet().score(key, value);}/*** 判断value在zset中的排名 zrank** @param key* @param value* @return*/public Long rank(String key, String value) {return redisTemplate.opsForZSet().rank(key, value);}/*** 返回集合的长度** @param key* @return*/public Long size(String key) {return redisTemplate.opsForZSet().zCard(key);}/*** 查询集合中指定顺序的值, 0 -1 表示获取全部的集合内容 zrange** 返回有序的集合,score小的在前面** @param key* @param start* @param end* @return*/public Set<Object> range(String key, int start, int end) {return redisTemplate.opsForZSet().range(key, start, end);}}
RedisConfiguration:
package com.pinyu.miniprogram.config.redis;import java.lang.reflect.Method;
import java.time.Duration;import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** @author ypp 创建时间:2018年12月27日 下午2:55:17* @Description: TODO(配置redis)*/
@Configuration
@EnableCaching // spring中注解驱动的缓存管理功能
public class RedisConfiguration extends CachingConfigurerSupport {private Logger logger = LogManager.getLogger(RedisConfiguration.class);@Beanpublic KeyGenerator KeyGenerator() {return new KeyGenerator() {@Overridepublic Object generate(Object target, Method method, Object... params) {StringBuilder sb = new StringBuilder();sb.append(target.getClass().getName());sb.append(method.getName());for (Object obj : params) {sb.append(obj.toString());}return sb.toString();}};}// @Bean
// CacheManager cacheManager(RedisConnectionFactory connectionFactory) {// 初始化一个RedisCacheWriter
// RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);// 设置CacheManager的值序列化方式为JdkSerializationRedisSerializer,但其实RedisCacheConfiguration默认就是使用StringRedisSerializer序列化key,JdkSerializationRedisSerializer序列化value,所以以下注释代码为默认实现// ClassLoader loader = this.getClass().getClassLoader();// JdkSerializationRedisSerializer jdkSerializer = new// JdkSerializationRedisSerializer(loader);// RedisSerializationContext.SerializationPair<Object> pair =// RedisSerializationContext.SerializationPair.fromSerializer(jdkSerializer);// RedisCacheConfiguration// defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
// RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();// 设置默认超过期时间是30秒
// defaultCacheConfig.entryTtl(Duration.ofSeconds(30));// 初始化RedisCacheManager
// RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
// return cacheManager;
// }// SpringBoot2.0之后,spring容器是自动的生成了StringRedisTemplate和RedisTemplate<Object,Object>,可以直接注入// 新增配置类RedisTemplate<String,Object>@Bean("redisTemplate")public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(connectionFactory);// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);// ObjectMapper objectMapper = new ObjectMapper();// objectMapper.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);// objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);// jackson2JsonRedisSerializer.setObjectMapper(objectMapper);// 使用StringRedisSerializer来序列化和反序列化redis的key值RedisSerializer redisSerializer = new StringRedisSerializer();// keyredisTemplate.setKeySerializer(keySerializer());redisTemplate.setHashKeySerializer(keySerializer());// valueredisTemplate.setValueSerializer(valueSerializer());redisTemplate.setHashValueSerializer(valueSerializer());redisTemplate.afterPropertiesSet();return redisTemplate;}@Beanpublic CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {// 缓存配置对象RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();redisCacheConfiguration = redisCacheConfiguration.entryTtl(Duration.ofMinutes(30L)) // 设置缓存的默认超时时间:30分钟.disableCachingNullValues() // 如果是空值,不缓存.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer())) // 设置key序列化器.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer((valueSerializer()))); // 设置value序列化器return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory)).cacheDefaults(redisCacheConfiguration).build();}// 使用Jackson序列化器private RedisSerializer<Object> valueSerializer() {return new GenericJackson2JsonRedisSerializer();}private RedisSerializer<String> keySerializer() {return new StringRedisSerializer();}
}
key的token存储member用户信息,key为id的对应token标识,这里就比较简单,只有了id,没有其他操作,单独存储一个token用于使用token查找member并更新member用户信息,存储zset主要用于利用毫秒分数定时清理不活跃的用户,比如清除排行5W后的用户信息,允许redis最多同时允许登录5W个用户,这里可以根据redis服务器配置进行处理
rsa加密token信息,防止token泄露导致用户信息/id被泄露,rsa非对称加密,只有自己的秘钥才可以解密,自己生成一对秘钥就好
RSAUtils:
package com.pinyu.miniprogram.utils.rsa;import java.io.ByteArrayOutputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;import javax.crypto.Cipher;import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;public class RSAUtils {public static String publicKey = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALVgisuDj0LMDf5i89wC2SvSujBYj5vshKFBAz3OfKvSmSXgVteN1dH6NRcyi5K6wNrVXu-4KsUm4Uf37rAiQvsCAwEAAQ";public static String privateKey = "MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEAtWCKy4OPQswN_mLz3ALZK9K6MFiPm-yEoUEDPc58q9KZJeBW143V0fo1FzKLkrrA2tVe77gqxSbhR_fusCJC-wIDAQABAkBbws_1TkW4QYwC2wUMldRRO3c-5k8hT3N6MW32YvTn5_XBWRwMjrl-t2G1kli1TIyrv8U2MiNiV5rm3KAgMRqBAiEA3OOp8y-gTaNzr9pHPYS7NEZJktwhDdabjBF9U7qbnxECIQDSNRCDq40ArmE1fMGvpt2nYrIGRzveW0PLksPPFeIZSwIgCa_KMinyg7UZS6rs2NvLQd2bOF-C65Jvu9LAhj12uaECIA_C7tQQnuf4K03JZvR2vJP6cILMAI8xpKm0_X2flG51AiB-7mj7JRaDoSHEYRfaCiCFY-js-iLH2sFBBdFo63wttw";public static final String CHARSET = "UTF-8";public static final String RSA_ALGORITHM = "RSA";public static Map<String, String> createKeys(int keySize) {// 为RSA算法创建一个KeyPairGenerator对象KeyPairGenerator kpg;try {kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);} catch (NoSuchAlgorithmException e) {throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM + "]");}// 初始化KeyPairGenerator对象,密钥长度kpg.initialize(keySize);// 生成密匙对KeyPair keyPair = kpg.generateKeyPair();// 得到公钥Key publicKey = keyPair.getPublic();String publicKeyStr = Base64.encodeBase64URLSafeString(publicKey.getEncoded());// 得到私钥Key privateKey = keyPair.getPrivate();String privateKeyStr = Base64.encodeBase64URLSafeString(privateKey.getEncoded());Map<String, String> keyPairMap = new HashMap<String, String>();keyPairMap.put("publicKey", publicKeyStr);keyPairMap.put("privateKey", privateKeyStr);return keyPairMap;}/*** 得到公钥* * @param publicKey* 密钥字符串(经过base64编码)* @throws Exception*/public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {// 通过X509编码的Key指令获得公钥对象KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);return key;}/*** 得到私钥* * @param privateKey* 密钥字符串(经过base64编码)* @throws Exception*/public static RSAPrivateKey getPrivateKey(String privateKey)throws NoSuchAlgorithmException, InvalidKeySpecException {// 通过PKCS#8编码的Key指令获得私钥对象KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);return key;}/*** 公钥加密* * @param data* @param publicKey* @return*/public static String publicEncrypt(String data, RSAPublicKey publicKey) {try {Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);cipher.init(Cipher.ENCRYPT_MODE, publicKey);return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET),publicKey.getModulus().bitLength()));} catch (Exception e) {throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);}}/*** 私钥解密* * @param data* @param privateKey* @return*/public static String privateDecrypt(String data, RSAPrivateKey privateKey) {try {Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);cipher.init(Cipher.DECRYPT_MODE, privateKey);return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data),privateKey.getModulus().bitLength()), CHARSET);} catch (Exception e) {throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);}}/*** 私钥加密* * @param data* @param privateKey* @return*/public static String privateEncrypt(String data, RSAPrivateKey privateKey) {try {Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);cipher.init(Cipher.ENCRYPT_MODE, privateKey);return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET),privateKey.getModulus().bitLength()));} catch (Exception e) {throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);}}/*** 公钥解密* * @param data* @param publicKey* @return*/public static String publicDecrypt(String data, RSAPublicKey publicKey) {try {Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);cipher.init(Cipher.DECRYPT_MODE, publicKey);return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data),publicKey.getModulus().bitLength()), CHARSET);} catch (Exception e) {throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);}}private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) {int maxBlock = 0;if (opmode == Cipher.DECRYPT_MODE) {maxBlock = keySize / 8;} else {maxBlock = keySize / 8 - 11;}ByteArrayOutputStream out = new ByteArrayOutputStream();int offSet = 0;byte[] buff;int i = 0;try {while (datas.length > offSet) {if (datas.length - offSet > maxBlock) {buff = cipher.doFinal(datas, offSet, maxBlock);} else {buff = cipher.doFinal(datas, offSet, datas.length - offSet);}out.write(buff, 0, buff.length);i++;offSet = i * maxBlock;}} catch (Exception e) {throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常", e);}byte[] resultDatas = out.toByteArray();IOUtils.closeQuietly(out);return resultDatas;}public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException {
// Map<String, String> keyMap = RSAUtils.createKeys(512);
// String publicKey = keyMap.get("publicKey");
// String privateKey = keyMap.get("privateKey");
// System.out.println("公钥: \n\r" + publicKey);
// System.out.println("私钥: \n\r" + privateKey);// System.out.println("公钥加密——私钥解密");String str = "365";
// System.out.println("\r明文:\r\n" + str);
// System.out.println("\r明文大小:\r\n" + str.getBytes().length);String encodedData = RSAUtils.publicEncrypt(str, RSAUtils.getPublicKey(publicKey));System.out.println("密文:\r\n" + encodedData);String decodedData = RSAUtils.privateDecrypt(encodedData, RSAUtils.getPrivateKey(privateKey));System.out.println("解密后文字: \r\n" + decodedData);}
}
每次请求前都对需要token的请求进行拦截,验证token
TokenAspect:
package com.pinyu.miniprogram.global.aspect;import java.io.IOException;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.pinyu.miniprogram.config.SpringBeanTool;
import com.pinyu.miniprogram.config.redis.RedisUtils;
import com.pinyu.miniprogram.global.exception.AuthTokenException;
import com.pinyu.miniprogram.mysql.entity.member.MemberEntity;
import com.pinyu.miniprogram.utils.RequestUtils;
import com.pinyu.miniprogram.utils.Token;@Aspect
@Component("tokenAspect")
public class TokenAspect {@Resourceprivate MappingJackson2HttpMessageConverter converter;@Autowiredprivate SpringBeanTool springBeanTool;@Autowiredprivate RedisUtils redisUtils;@Autowiredprivate Token token;private static final Logger log = LoggerFactory.getLogger(TokenAspect.class);// 配置织入点@Pointcut("@annotation(com.pinyu.miniprogram.global.ann.RequireToken)")public void pointCut() {}/*** 拦截token* * @param joinPoint* @param e* @throws IOException* @throws HttpMessageNotWritableException*/@Before(value = "pointCut()")public void doBefore(JoinPoint joinPoint) throws Exception {handleLog(joinPoint);}private void handleLog(JoinPoint joinPoint) throws Exception {HttpServletResponse response = springBeanTool.getResponse();HttpServletRequest request = springBeanTool.getRequest();HttpOutputMessage outputMessage = new ServletServerHttpResponse(response);String requestToken = RequestUtils.getHeader(request, "token");if (StringUtils.isBlank(requestToken)) {// 可以使用以下方式返回json数据// converter.write(JsonMsg.Error(Code.TOKEN_VALIDA_NULL),MediaType.APPLICATION_JSON, outputMessage);// shutdownResponse(response);// 不要用,用了这个在controller@responseBody无效,输出流关闭了throw new AuthTokenException(Token.TOKEN_VALIDA_NULL);}Double score = redisUtils.score(Token.MEMBER_TOKEN, requestToken);if (score == null) {// 终止继续往下面走,另外全局异常捕获AuthTokenException并给前端code码和提示throw new AuthTokenException(Token.TOKEN_VALIDA_FAIL);}// 获取redis已有的member信息,不查数据库,重新生成token放入MemberEntity member = (MemberEntity) redisUtils.get(requestToken);// 移除之前的token(包含member信息、token排行信息)token.delToken(member);String saveToken = token.saveToken(member);response.setHeader("Access-Control-Expose-Headers","Cache-Control,Content-Type,Expires,Pragma,Content-Language,Last-Modified,token");response.setHeader("token", saveToken); // 设置响应头}private void shutdownResponse(HttpServletResponse response) throws IOException {response.getOutputStream().close();}}
在token验证不通过,抛出异常中断请求进入controller方法并给予客户端提示:
AuthTokenException:
package com.pinyu.miniprogram.global.exception;public class AuthTokenException extends Exception{public AuthTokenException(String msgs){super(msgs);}
}
全局异常处理:GlobalControllerAdvice
package com.pinyu.miniprogram.global.exception;import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;import com.pinyu.miniprogram.utils.Token;
import com.pinyu.miniprogram.utils.result.JsonMsg;/**
* @author ypp
* 创建时间:2018年10月15日 下午4:06:50
* @Description: TODO(全局异常处理)
*/
@ControllerAdvice
public class GlobalControllerAdvice implements ResponseBodyAdvice<Object>{protected static Logger log=LogManager.getLogger(GlobalControllerAdvice.class);/*** 应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器* @param binder*/@InitBinderpublic void initBinder(WebDataBinder binder) {}/*** 把值绑定到Model中,使全局@RequestMapping可以获取到该值* @param model*/@ModelAttributepublic void addAttributes(Model model) {
// model.addAttribute("author", "Magical Sam");}/*** 全局异常捕捉处理* @param ex* @return*/@ResponseBody@ExceptionHandler(value = Exception.class)public String errorHandler(Exception ex) {Throwable cause = ex.getCause();if(cause instanceof AuthTokenException){return Token.Error(cause.getMessage());}String sOut = ex.getClass().getName()+"\r\n";StackTraceElement[] trace = ex.getStackTrace();for (StackTraceElement s : trace) {sOut += "\tat " + s + "\r\n";}log.error(sOut);ex.printStackTrace();return JsonMsg.Error("服务器异常,请联系管理员");}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter arg1, MediaType arg2,Class<? extends HttpMessageConverter<?>> arg3, ServerHttpRequest arg4, ServerHttpResponse arg5) {//可以在此处进行返回数据全局处理,body就是返回数据,还没有经过@ResponseBody处理return body;}@Overridepublic boolean supports(MethodParameter arg0, Class<? extends HttpMessageConverter<?>> arg1) {return true;// 只有返回true才会继续执行}
}
SpringBeanTool:
package com.pinyu.miniprogram.config;import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;/*** @author ypp 创建时间:2018年10月17日 上午11:59:11* @Description: TODO(用一句话描述该文件做什么)*/
@Component
@WebListener
public class SpringBeanTool implements ApplicationContextAware, ServletContextListener {/*** 上下文对象实例*/private ApplicationContext applicationContext;private ServletContext servletContext;private static Environment env;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;env=applicationContext.getBean(Environment.class);}/*** 获取applicationContext* * @return*/public ApplicationContext getApplicationContext() {return applicationContext;}/*** 获取servletContext* * @return*/public ServletContext getServletContext() {return servletContext;}/*** 通过name获取 Bean.* * @param name* @return*/public Object getBean(String name) {return getApplicationContext().getBean(name);}/*** 通过class获取Bean.* * @param clazz* @param <T>* @return*/public <T> T getBean(Class<T> clazz) {return getApplicationContext().getBean(clazz);}/*** 通过name,以及Clazz返回指定的Bean* * @param name* @param clazz* @param <T>* @return*/public <T> T getBean(String name, Class<T> clazz) {Assert.hasText(name, "name为空");return getApplicationContext().getBean(name, clazz);}@Overridepublic void contextInitialized(ServletContextEvent sce) {this.servletContext = sce.getServletContext();}@Overridepublic void contextDestroyed(ServletContextEvent sce) {}public HttpServletRequest getRequest() {return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();}public HttpServletResponse getResponse() {return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();}public static String getValueApplicationPropertiesByKey(String key){return env.getProperty(key);}}
允许跨域CorsConfig:
package com.pinyu.miniprogram.config;import java.nio.charset.Charset;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {//设置允许跨域的路径registry.addMapping("/**")//设置允许跨域请求的域名.allowedOrigins("*")//是否允许证书 不再默认开启.allowCredentials(true)//设置允许的方法.allowedMethods("*");//跨域允许时间
// .maxAge(3600);}
}
全局乱码处理WebAppConfigurer:
package com.pinyu.miniprogram.config;import java.nio.charset.Charset;
import java.util.List;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import com.pinyu.miniprogram.global.interceptor.LoginInterceptor;@Configuration
public class WebAppConfigurer implements WebMvcConfigurer {// @Bean
// public HttpMessageConverter responseBodyConverter(){
// //解决返回值中文乱码,除非返回值出现乱码情况,不然别设置,设置了以后@RequestBody绑定json参数可能会报错:Content type 'application/json;charset=UTF-8' not supported 需进一步处理
// StringHttpMessageConverter converter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
// return converter;
// }
//
// @Override
// public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// converters.add(responseBodyConverter());
// }@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 可添加多个,这里选择拦截所有请求地址registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**");}}
测试:
贴上注解@RequireToken表示需要token验证,AOP切面拦截贴了@RequireToken注解的请求
@RequestMapping("/findById-{id}")@RequireTokenpublic String findById(@PathVariable("id") Long id) {return JsonMsg.OK(service.findById(id));}
测试:
上图中,我在header传入了token,这个方法是需要校验token的,但是提示token为空和token错误。然后进行登录,登录之后会返回token,使用该token进行查询findby-xx,是可以查询member信息的,再次使用token查询,第二次点击,验证错误(是因为这时的token已经不存在了,每请求一次token都是会变的)
再次测试:
还是登录之后生成token,登录之后token是需要返回到客户端保存的,下一次请求带token请求,请求之后返回新的token,一次类推,每次请求后的token不一样的。
以上示例根据自己的规则稍加改造一点是可以拿到实际应用中使用的!
这个项目示例包含了springboot redis集成、springboot整合elasticsearch集群、springboot整合activeMq和整合mybatis通用mapper以及token相关示例等
源码已上传github https://github.com/yfcgklypp/miniprogram.git