使用token和redis配合使用机制

思路:

  1. 请求页面的时候后台生成token同时放到后台redis中, 然后同时将token放作用域中

2.页面将后台返回的token放到前台的隐藏域中

3.提交表单的时候 可将token放 form表单中,也可将token放入head头中

4,使用spring aop 拦截机制

     1).首先用前台传的token 去redis中获取如果能获取到证明没提交过,则进行提交并清除redis数据

     2)如果前台传的token验证不通过 则返回前台请勿重复提交表单信息

------定义type常量:-----------------

/**

  • 通用常量信息
  • @author www.axxo.top
    */
    public interface ConstantsInterface
    {
    public static String EXTAPI_HEAD = "head"; //http头
    public static String EXTAPI_FROM = "form"; //form 表单

}

---一下自定义注解ExtApiToken 前置生成token----

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**

  • 解决幂等性问题,前置通知生成token
  • @author www.axxo.top
    */
    @Target( ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ExtApiToken {
    String type();

}

--------自定义注解验证token-----------

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**

  • 解决幂等性问题
  • 验证token注解
  • @author www.axxo.top
    */
    @Target( ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ExtApiIdempotent {
    String type();

}
-------------aop代码------------------

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;

/**

  • 幂等性问题处理
    *
  • @author www.axxo.top
    */
    @Aspect
    @Component
    @EnableAsync
    public class ExtapiAspect {
    @Autowired
    private RedisToken redisToken;

    private static final Logger log = LoggerFactory.getLogger(ExtapiAspect.class);

    // 配置ExtApiIdempotent织入点
    @Pointcut("@annotation(com.ruoyi.framework.aspectj.lang.annotation.ExtApiIdempotent)")
    public void ExtApi() {
    } // 配置ExtApiToken 织入点
    @Pointcut("@annotation(com.ruoyi.framework.aspectj.lang.annotation.ExtApiToken)")
    public void ExtApiToken() {
    }

    /**

    • 前置通知 用于拦截操作
      *
    • @param joinPoint 切点
      */
      @Before("ExtApiToken()")
      public void doBefore(JoinPoint joinPoint) {
      HttpServletRequest request = ServletUtils.getRequest();
      Signature signature = joinPoint.getSignature();
      MethodSignature methodSignature = (MethodSignature) signature;
      Method method = methodSignature.getMethod();
      //获取带有通知注解的方法 有则拦截 无则放过
      ExtApiToken extApiToken = method.getAnnotation(ExtApiToken.class);
      if (StringUtils.isNotNull(extApiToken)){
       request.setAttribute("token",redisToken.getToken());
      
      }
}

/**
 * 环绕通知
 *
 * @param proceedingJoinPoint 切点
 */
@Around("ExtApi()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable {
    //思路 拦截请求
    //判断方法是否加入注解
    //处理

    ExtApiIdempotent extApiIdempotent = extApiIdempotent(proceedingJoinPoint);
    HttpServletRequest request = ServletUtils.getRequest();
    try {
        //判断是否有注解
        if (StringUtils.isNotNull(extApiIdempotent)) {
            String type = extApiIdempotent.type();
            String token = null;
            if (StringUtils.isEmpty(type)) {
                type = ConstantsInterface.EXTAPI_HEAD;
            }
            //如果是请求头
            if (ConstantsInterface.EXTAPI_HEAD.equals(type)) {
                token = request.getHeader("token");
            } else {
                token = request.getParameter("token");
            }
            if (StringUtils.isEmpty(token)) {
                return AjaxResult.error("参数错误");
            }
            Boolean isToken = redisToken.findRedisToken(token);
            if (!isToken) {

// response("请勿重复提交");
return AjaxResult.error("请勿重复提交!");
}
}

    } catch (Exception e) {

    }
    //放行
    Object proceed = proceedingJoinPoint.proceed();
    return proceed;
}


/**
 * 是否存在注解,如果存在就获取
 */
private ExtApiIdempotent extApiIdempotent(JoinPoint joinPoint) throws Exception {
    Signature signature = joinPoint.getSignature();
    MethodSignature methodSignature = (MethodSignature) signature;
    Method method = methodSignature.getMethod();

    if (method != null) {
        return method.getAnnotation(ExtApiIdempotent.class);
    }
    return null;
}

/**
 * 获取response
 *
 * @param msg
 * @throws IOException
 */
public void response(String msg) throws IOException {
    HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
    response.setHeader("Content-type", "text/html;charset=UTF-8");
    PrintWriter writer = response.getWriter();
    try {
        writer.print(msg);
    } catch (Exception e) {

    } finally {
        writer.close();
    }
}

}
------------注解的使用-------------
适应注解方法:

只需在跳转页面加入正常token的注解即可

/**

  • 跳转新增用户
  • ExtApiToken 前置通知生成token
    */
    @GetMapping("/add")
    @ExtApiToken(type = ConstantsInterface.EXTAPI_FROM)
    public String add(ModelMap mmap) {
    mmap.put("roles", roleService.selectRoleAll());
    mmap.put("posts", postService.selectPostAll());
    return prefix + "/add";
    }

/**

  • 新增保存用户
  • ExtApiIdempotent 环绕通知验证token删除token
    */
    @RequiresPermissions("system:user:add")
    @Log(title = "用户管理", action = BusinessType.INSERT)
    @PostMapping("/add")
    @Transactional(rollbackFor = Exception.class)
    @ResponseBody
    @ExtApiIdempotent(type = ConstantsInterface.EXTAPI_FROM)
    public AjaxResult addSave(User user, String isMain) {
    if (StringUtils.isNotNull(user.getUserId()) && User.isAdmin(user.getUserId())) {
     return error("不允许修改超级管理员用户");
    
    }
    return toAjax(userService.insertUser(user));
    }
    ------简单写了个生成token 验证token工具类--------

import com.ruoyi.framework.web.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class RedisToken {
@Autowired
private RedisService redisService;
//生成token
public String getToken(){
String token = "token_"+RandomUUID.getUUID();
redisService.set(token,token,60L*3);
return token;
}
//获取token并验证
public Boolean findRedisToken(String token) {
String resToken = (String) redisService.get(token);
if (StringUtils.isEmpty(resToken)) {
return false;
} else {
redisService.remove(resToken);
return true;
}
}
}
---------redisService类---------

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Service
public class RedisService {
@Autowired
private RedisTemplate redisTemplate;
/**

 * 写入缓存
 * @param key
 * @param value
 * @return
 */
public boolean set(final String key, Object value) {
    boolean result = false;
    try {
        ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
        operations.set(key, value);
        result = true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return result;
}
/**
 * 写入缓存设置时效时间
 * @param key
 * @param value
 * @return
 */
public boolean set(final String key, Object value, Long expireTime) {
    boolean result = false;
    try {
        ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
        operations.set(key, value);
        redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
        result = true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return result;
}
/**
 * 批量删除对应的value
 * @param keys
 */
public void remove(final String... keys) {
    for (String key : keys) {
        remove(key);
    }
}

/**
 * 批量删除key
 * @param pattern
 */
public void removePattern(final String pattern) {
    Set<Serializable> keys = redisTemplate.keys(pattern);
    if (keys.size() > 0)
        redisTemplate.delete(keys);
}
/**
 * 删除对应的value
 * @param key
 */
public void remove(final String key) {
    if (exists(key)) {
        redisTemplate.delete(key);
    }
}
/**
 * 判断缓存中是否有对应的value
 * @param key
 * @return
 */
public boolean exists(final String key) {
    return redisTemplate.hasKey(key);
}
/**
 * 读取缓存
 * @param key
 * @return
 */
public Object get(final String key) {
    Object result = null;
    ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
    result = operations.get(key);
    return result;
}
/**
 * 哈希 添加
 * @param key
 * @param hashKey
 * @param value
 */
public void hmSet(String key, Object hashKey, Object value){
    HashOperations<String, Object, Object>  hash = redisTemplate.opsForHash();
    hash.put(key,hashKey,value);
}

/**
 * 哈希获取数据
 * @param key
 * @param hashKey
 * @return
 */
public Object hmGet(String key, Object hashKey){
    HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
    return hash.get(key,hashKey);
}

/**
 * 列表添加
 * @param k
 * @param v
 */
public void lPush(String k,Object v){
    ListOperations<String, Object> list = redisTemplate.opsForList();
    list.rightPush(k,v);
}

/**
 * 列表获取
 * @param k
 * @param l
 * @param l1
 * @return
 */
public List<Object> lRange(String k, long l, long l1){
    ListOperations<String, Object> list = redisTemplate.opsForList();
    return list.range(k,l,l1);
}

/**
 * 集合添加
 * @param key
 * @param value
 */
public void add(String key,Object value){
    SetOperations<String, Object> set = redisTemplate.opsForSet();
    set.add(key,value);
}

/**
 * 集合获取
 * @param key
 * @return
 */
public Set<Object> setMembers(String key){
    SetOperations<String, Object> set = redisTemplate.opsForSet();
    return set.members(key);
}

/**
 * 有序集合添加
 * @param key
 * @param value
 * @param scoure
 */
public void zAdd(String key,Object value,double scoure){
    ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
    zset.add(key,value,scoure);
}

/**
 * 有序集合获取
 * @param key
 * @param scoure
 * @param scoure1
 * @return
 */
public Set<Object> rangeByScore(String key,double scoure,double scoure1){
    ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
    return zset.rangeByScore(key, scoure, scoure1);
}

}
----------springboot redis配置---------------

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

import java.lang.reflect.Method;

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

/**
 * 生成key的策略
 * @return
 */
@Bean
public KeyGenerator keyGenerator() {
    return new KeyGenerator() {
        @Override
        public 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();
        }
    };
}



/**
 * RedisTemplate配置
 */
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
    StringRedisTemplate template = new StringRedisTemplate(factory);
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);
    template.setValueSerializer(jackson2JsonRedisSerializer);
    template.afterPropertiesSet();
    return template;
}

}
----------springboot redis的maven依赖--------------

     <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>