REST API 基于ACCESS TOKEN 的权限解决方案

MaxPco 4年前

来自: http://www.cnblogs.com/sloong/p/5157654.html

REST 设计原则是statelessness的,而且但客户端是APP时,从APP发起的请求,不是基于bowers,无法带相同的sessionid,所以比较好的方案是每次请求都带一个accesstoken进行验证。然后后台是根据token 找到用户,然后找到用户资源

但总不能每个方法都去调用token验证的方法,也不能每次验证都需要查询数据库吧!

解决办法:

  • 为了业务层只关注业务,所以需要把token验证的方法在进入controller前集中处理,用 Interceptor实现

  • 由于根据token获得用户,只需要用到 用户ID,用户登录名等 不会改变的信息,用缓存实现,需要支持过期失效,ConcurrentHashMap没有过期失效的功能,自己懒得实现就用 ehcache

集中处理token

interceptor实现:

/**   * 验证token有效性   */  @Component  public class AccessTokenVerifyInterceptor extends HandlerInterceptorAdapter {      @Resource      UserService userService;        private final static Logger LOG = LoggerFactory.getLogger(AccessTokenVerifyInterceptor.class);        @Override      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)              throws Exception {          LOG.debug("AccessTokenVerifyInterceptor executing.......");          boolean flag = false;          //accesstoken 参数          String accessToken = request.getParameter("accesstoken");          if(StringUtils.notEmpty(accessToken)) {              //验证accessToken              //verifyAccessToken 已做缓存处理              User user = userService.verifyAccessToken(accessToken);              if(user!=null){                  flag = true;                  //塞到request中去,供controller里面调用                  request.setAttribute(SystemConstants.SESSION_NAME_USER,user);              }          }            if(!flag){              response.setStatus(HttpStatus.FORBIDDEN.value());              response.getWriter().print("wrong access token");          }          return flag;      }  }

然后到spring配置文件中加上这个拦截器:

<!--过滤器-->  <mvc:interceptors>      <!--API ACCESS TOKEN INTERCEPTOR-->      <mvc:interceptor>          <mvc:mapping path="/api/**"/>          <mvc:exclude-mapping path="/**/api/user/**" />          <mvc:exclude-mapping path="/**/api/accesstoken" />          <bean class="cn.ifengkou.athena.controller.interceptor.AccessTokenVerifyInterceptor"></bean>      </mvc:interceptor>      <!--other interceptor -->  </mvc:interceptors>

缓存处理

pom.xml中加入ehcache包:(spring集成ehcache ,需要spring-context和spring-context-support)

        <dependency>              <groupId>net.sf.ehcache</groupId>              <artifactId>ehcache</artifactId>              <version>2.10.0</version>          </dependency>

加入ehcache.xml,大部分都是默认,参考springside里面说的,改了updateCheck="false",

<ehcache updateCheck="false"           monitoring="autodetect"           dynamicConfig="true">      <diskStore path="java.io.tmpdir" />      <cache name="accessTokenUser"             maxEntriesLocalHeap="10000"             maxEntriesLocalDisk="1000"             eternal="false"             diskSpoolBufferSizeMB="20"             timeToIdleSeconds="300" timeToLiveSeconds="600"             memoryStoreEvictionPolicy="LFU"             transactionalMode="off">          <persistence strategy="localTempSwap" />      </cache>  </ehcache>

开启缓存,在spring配置文件中加入:

<!-- 缓存配置 -->  <!-- 启用缓存注解功能(请将其配置在Spring主配置文件中) -->  <cache:annotation-driven cache-manager="cacheManager" />    <!-- Spring自己的基于java.util.concurrent.ConcurrentHashMap实现的缓存管理器(该功能是从Spring3.1开始提供的) -->  <!-- <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">      <property name="caches"> <set> <bean name="myCache" class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"/>      </set> </property> </bean> -->  <!-- 若只想使用Spring自身提供的缓存器,则注释掉下面的两个关于Ehcache配置的bean,并启用上面的SimpleCacheManager即可 -->  <!-- Spring提供的基于的Ehcache实现的缓存管理器 -->  <bean id="cacheManagerFactory"        class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">      <property name="configLocation" value="classpath:ehcache.xml" />  </bean>  <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">      <property name="cacheManager" ref="cacheManagerFactory" />  </bean>

对verifyAccessToken 方法做缓存处理,也就是在原有方法上加Cacheable注解:

@Cacheable(value = "accessTokenUser",key = "#accessToken")  @Override  public User verifyAccessToken(String accessToken) {      LOG.debug("verifyAccessToken executing......");      List<User> users = userDao.getUserByAccessToken(accessToken);      if(users.size()!=1){          if(users.size()>1){              LOG.error("accessToken 出现了重复,bug!请检查!");          }          return null;      }      return users.get(0);  }

开始run出现 java.io.NotSerializableException: cn.ifengkou.athena.model.User

User 实现序列化,再试:

前端请求三次的日志,可以看到verifyAccessToken只执行了一次

2015-12-04 15:25:56,531 INFO [cn.ifengkou.athena.controller.interceptor.AccessTokenVerifyInterceptor] - <AccessTokenVerifyInterceptor executing.......>  2015-12-04 15:25:56,628 INFO [cn.ifengkou.athena.service.impl.UserServiceImpl] - <verifyAccessToken executing......>  2015-12-04 15:26:21,838 INFO [cn.ifengkou.athena.controller.interceptor.AccessTokenVerifyInterceptor] - <AccessTokenVerifyInterceptor executing.......>  2015-12-04 15:26:29,184 INFO [cn.ifengkou.athena.controller.interceptor.AccessTokenVerifyInterceptor] - <AccessTokenVerifyInterceptor executing.......>

如有token无效,查出来User为null,cache 把null也缓存起来了

keywords:

REST,accesstoken,权限,spring,ehcache,interceptor

备注: