REST API 基於ACCESS TOKEN 的權限解決方案

REST 設計原則是statelessness的,並且但客戶端是APP時,從APP發起的請求,不是基於bowers,沒法帶相同的sessionid,因此比較好的方案是每次請求都帶一個accesstoken進行驗證。而後後臺是根據token 找到用戶,而後找到用戶資源html

但總不能每一個方法都去調用token驗證的方法,也不能每次驗證都須要查詢數據庫吧!前端

解決辦法:java

  • 爲了業務層只關注業務,因此須要把token驗證的方法在進入controller前集中處理,用 Interceptor實現spring

  • 因爲根據token得到用戶,只須要用到 用戶ID,用戶登陸名等 不會改變的信息,用緩存實現,須要支持過時失效,ConcurrentHashMap沒有過時失效的功能,本身懶得實現就用ehcache數據庫

集中處理token

interceptor實現:api

/**
 * 驗證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)session

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

加入ehcache.xml,大部分都是默認,參考springside裏面說的,改了updateCheck="false",mvc

<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配置文件中加入:app

<!-- 緩存配置 -->
<!-- 啓用緩存註解功能(請將其配置在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

備註:

轉載請附帶原文路徑:http://www.cnblogs.com/sloong/p/5157654.html

相關文章
相關標籤/搜索