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


    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

    备注:

    转载请附带原文路径:http://www.cnblogs.com/sloong/p/5157654.html

  • 相关阅读:
    各种类型的Dialog
    短信验证码的使用
    监听开机广播
    实现点击两次返回键退出
    Android 遮罩层效果--制作圆形头像
    Native方法的使用
    如何给数字添加分隔符
    自定义Toast
    Android px、dp、sp之间相互转换
    android:scrollbarStyle属性及滚动条和分割线覆盖问题
  • 原文地址:https://www.cnblogs.com/sloong/p/5157654.html
Copyright © 2020-2023  润新知