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
備注: