亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

利用緩存實現APP端與服務器接口交互的Session控制

2019-11-14 14:57:17
字體:
來源:轉載
供稿:網友

與傳統B/S模式的Web系統不同,移動端APP與服務器之間的接口交互一般是C/S模式,這種情況下如果涉及到用戶登錄的話,就不能像Web系統那樣依賴于Web容器來管理session了,因為APP每發一次請求都會在服務器端創建一個新的Session。而有些涉及到用戶隱私或者資金交易的接口又必須確認當前用戶登錄的合法性,如果沒有登錄或者登錄已過期則不能進行此類操作。
我見過一種“偷懶”的方式,就是在用戶第一次登錄之后,保存用戶的ID在本地存儲中,之后跟服務器交互的接口都通過用戶ID來標識用戶身份。

這種方式主要有兩個弊端:

  1. 只要本地存儲的用戶ID沒有被刪掉,就始終可以訪問以上接口,不需要重新登錄,除非增加有效期的判斷或者用戶主動退出;
  2. 接口安全性弱,因為用戶ID對應了數據庫里的用戶唯一標識,別人只要能拿到用戶ID或者偽造一個用戶ID就可以使用以上接口對該用戶進行非法操作。

綜上考慮,可以利用緩存在服務器端模擬Session管理機制來解決這個問題,當然這只是目前我所知道的一種比較簡單有效的解決APP用戶Session的方案。如果哪位朋友有其它好的方案,歡迎在下面留言交流。

這里用的緩存框架是Ehcache,下載地址http://www.ehcache.org/downloads/,當然也可以用Memcached或者其它的。之所以用Ehcache框架,一方面因為它輕量、快速、集成簡單等,另一方面它也是Hibernate中默認的CachePRovider,對于已經集成了Hibernate的項目不需要再額外添加Ehcache的jar包了。

有了Ehcache,接著就要在Spring配置文件里添加相應的配置了,配置信息如下:

 1 <!-- 配置緩存管理器工廠 --> 2 <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> 3     <property name="configLocation" value="classpath:ehcache.xml" /> 4     <property name="shared" value="true" /> 5 </bean> 6 <!-- 配置緩存工廠,緩存名稱為myCache --> 7 <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheFactoryBean"> 8     <property name="cacheName" value="myCache" /> 9     <property name="cacheManager" ref="cacheManager" />10 </bean>

另外,Ehcache的配置文件ehcache.xml里的配置如下:

 1 <?xml version="1.0" encoding="gbk"?> 2 <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3     xsi:noNamespaceSchemaLocation="ehcache.xsd"> 4     <diskStore path="java.io.tmpdir" /> 5      6     <!-- 配置一個默認緩存,必須的 --> 7     <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="30" timeToLiveSeconds="30" overflowToDisk="false" /> 8  9     <!-- 配置自定義緩存 maxElementsInMemory:緩存中允許創建的最大對象數 eternal:緩存中對象是否為永久的,如果是,超時設置將被忽略,對象從不過期。 10         timeToIdleSeconds:緩存數據的鈍化時間,也就是在一個元素消亡之前, 兩次訪問時間的最大時間間隔值,這只能在元素不是永久駐留時有效, 11         如果該值是 0 就意味著元素可以停頓無窮長的時間。 timeToLiveSeconds:緩存數據的生存時間,也就是一個元素從構建到消亡的最大時間間隔值, 12         這只能在元素不是永久駐留時有效,如果該值是0就意味著元素可以停頓無窮長的時間。 overflowToDisk:內存不足時,是否啟用磁盤緩存。 memoryStoreEvictionPolicy:緩存滿了之后的淘汰算法。 -->13     <cache name="myCache" maxElementsInMemory="10000" eternal="true" overflowToDisk="true" memoryStoreEvictionPolicy="LFU" />14 </ehcache>

配置好Ehcache之后,就可以直接通過@Autowired或者@Resource注入緩存實例了。示例代碼如下:

 1 @Component 2 public class Memory { 3     @Autowired 4     private Cache ehcache; // 注意這里引入的Cache是net.sf.ehcache.Cache 5      6     public void setValue(String key, String value) { 7         ehcache.put(new Element(key, value)); 8     } 9     10     public Object getValue(String key) {11         Element element = ehcache.get(key);12         return element != null ? element.getValue() : null;13     }14 }

緩存準備完畢,接下來就是模擬用戶Session了,實現思路是這樣的:

  1. 用戶登錄成功后,服務器端按照一定規則生成一個Token令牌,Token是可變的,也可以是固定的(后面會說明);
  2. 將Token作為key,用戶信息作為value放到緩存中,設置有效時長(比如30分鐘內沒有訪問就失效);
  3. 將Token返回給APP端,APP保存到本地存儲中以便請求接口時帶上此參數;
  4. 通過攔截器攔截所有涉及到用戶隱私安全等方面的接口,驗證請求中的Token參數合法性并檢查緩存是否過期;
  5. 驗證通過后,將Token值保存到線程存儲中,以便當前線程的操作可以通過Token直接從緩存中索引當前登錄的用戶信息。

綜上所述,APP端要做的事情就是登錄并從服務器端獲取Token存儲起來,當訪問用戶隱私相關的接口時帶上這個Token標識自己的身份。服務器端要做的就是攔截用戶隱私相關的接口驗證Token和登錄信息,驗證后將Token保存到線程變量里,之后可以在其它操作中取出這個Token并從緩存中獲取當前用戶信息。這樣APP不需要知道用戶ID,它拿到的只是一個身份標識,而且這個標識是可變的,服務器根據這個標識就可以知道要操作的是哪個用戶。

對于Token是否可變,處理細節上有所不同,效果也不一樣。

  1. Token固定的情況:服務器端生成Token時將用戶名和密碼一起進行md5加密,即MD5(username+passWord)。這樣對于同一個用戶而言,每次登錄的Token是相同的,用戶可以在多個客戶端登錄,共用一個Session,當用戶密碼變更時要求用戶重新登錄;
  2. Token可變的情況:服務器端生成Token時將用戶名、密碼和當前時間戳一起MD5加密,即MD5(username+password+timestamp)。這樣對于同一個用戶而言,每次登錄的Token都是不一樣的,再清除上一次登錄的緩存信息,即可實現唯一用戶登錄的效果。

為了保證同一個用戶在緩存中只有一條登錄信息,服務器端在生成Token后,可以再單獨對用戶名進行MD5作為Seed,即MD5(username)。再將Seed作為key,Token作為value保存到緩存中,這樣即便Token是變化的,但每個用戶的Seed是固定的,就可以通過Seed索引到Token,再通過Token清除上一次的登錄信息,避免重復登錄時緩存中保存過多無效的登錄信息。

基于Token的Session控制部分代碼如下:

 1 @Component 2 public class Memory { 3  4     @Autowired 5     private Cache ehcache; 6  7     /** 8      * 關閉緩存管理器 9      */10     @PreDestroy11     protected void shutdown() {12         if (ehcache != null) {13             ehcache.getCacheManager().shutdown();14         }15     }16 17     /**18      * 保存當前登錄用戶信息19      * 20      * @param loginUser21      */22     public void saveLoginUser(LoginUser loginUser) {23         // 生成seed和token值24         String seed = MD5Util.getMD5Code(loginUser.getUsername());25         String token = TokenProcessor.getInstance().generateToken(seed, true);26         // 保存token到登錄用戶中27         loginUser.setToken(token);28         // 清空之前的登錄信息29         clearLoginInfoBySeed(seed);30         // 保存新的token和登錄信息31         String timeout = getSystemValue(SystemParam.TOKEN_TIMEOUT);32         int ttiExpiry = NumberUtils.toInt(timeout) * 60; // 轉換成秒33         ehcache.put(new Element(seed, token, false, ttiExpiry, 0));34         ehcache.put(new Element(token, loginUser, false, ttiExpiry, 0));35     }36 37     /**38      * 獲取當前線程中的用戶信息39      * 40      * @return41      */42     public LoginUser currentLoginUser() {43         Element element = ehcache.get(ThreadTokenHolder.getToken());44         return element == null ? null : (LoginUser) element.getValue();45     }46 47     /**48      * 根據token檢查用戶是否登錄49      * 50      * @param token51      * @return52      */53     public boolean checkLoginInfo(String token) {54         Element element = ehcache.get(token);55         return element != null && (LoginUser) element.getValue() != null;56     }57 58     /**59      * 清空登錄信息60      */61     public void clearLoginInfo() {62         LoginUser loginUser = currentLoginUser();63         if (loginUser != null) {64             // 根據登錄的用戶名生成seed,然后清除登錄信息65             String seed = MD5Util.getMD5Code(loginUser.getUsername());66             clearLoginInfoBySeed(seed);67         }68     }69 70     /**71      * 根據seed清空登錄信息72      * 73      * @param seed74      */75     public void clearLoginInfoBySeed(String seed) {76         // 根據seed找到對應的token77         Element element = ehcache.get(seed);78         if (element != null) {79             // 根據token清空之前的登錄信息80             ehcache.remove(seed);81             ehcache.remove(element.getValue());82         }83     }84 }

Token攔截器部分代碼如下:

 1 public class TokenInterceptor extends HandlerInterceptorAdapter { 2     @Autowired 3     private Memory memory; 4  5     private List<String> allowList; // 放行的URL列表 6  7     private static final PathMatcher PATH_MATCHER = new AntPathMatcher(); 8  9     @Override10     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {11         // 判斷請求的URI是否運行放行,如果不允許則校驗請求的token信息12         if (!checkAllowaccess(request.getRequestURI())) {13             // 檢查請求的token值是否為空14             String token = getTokenFromRequest(request);15             response.setContentType(MediaType.application_JSON_VALUE);16             response.setCharacterEncoding("UTF-8");17             response.setHeader("Cache-Control", "no-cache, must-revalidate");18             if (StringUtils.isEmpty(token)) {19                 response.getWriter().write("Token不能為空");20                 response.getWriter().close();21                 return false;22             }23             if (!memory.checkLoginInfo(token)) {24                 response.getWriter().write("Session已過期,請重新登錄");25                 response.getWriter().close();26                 return false;27             }28             ThreadTokenHolder.setToken(token); // 保存當前token,用于Controller層獲取登錄用戶信息29         }30         return super.preHandle(request, response, handler);31     }32 33     /**34      * 檢查URI是否放行35      * 36      * @param URI37      * @return 返回檢查結果38      */39     private boolean checkAllowAccess(String URI) {40         if (!URI.startsWith("/")) {41             URI = "/" + URI;42         }43         for (String allow : allowList) {44             if (PATH_MATCHER.match(allow, URI)) {45                 return true;46             }47         }48         return false;49     }50 51     /**52      * 從請求信息中獲取token值53      * 54      * @param request55      * @return token值56      */57     private String getTokenFromRequest(HttpServletRequest request) {58         // 默認從header里獲取token值59         String token = request.getHeader(Constants.TOKEN);60         if (StringUtils.isEmpty(token)) {61             // 從請求信息中獲取token值62             token = request.getParameter(Constants.TOKEN);63         }64         return token;65     }66 67     public List<String> getAllowList() {68         return allowList;69     }70 71     public void setAllowList(List<String> allowList) {72         this.allowList = allowList;73     }74 }

到這里,已經可以在一定程度上確保接口請求的合法性,不至于讓別人那么容易偽造用戶信息,即便別人通過非法手段拿到了Token也只是臨時的,當緩存失效后或者用戶重新登錄后Token一樣無效。如果服務器接口安全性要求更高一些,可以換成SSL協議以防請求信息被竊取。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
中文字幕亚洲欧美日韩在线不卡| 国产日韩av在线| 中文字幕精品在线视频| 欧美日韩在线视频首页| 日韩美女视频免费在线观看| 2020欧美日韩在线视频| 欧美日韩成人在线视频| 热草久综合在线| 不卡av在线网站| 亚洲电影免费在线观看| 欧洲美女免费图片一区| 久久噜噜噜精品国产亚洲综合| 粗暴蹂躏中文一区二区三区| 精品久久香蕉国产线看观看亚洲| 国内精久久久久久久久久人| 欧美日韩国产va另类| 欧美大片在线影院| 欧美性理论片在线观看片免费| 日韩乱码在线视频| 最新亚洲国产精品| 最好看的2019年中文视频| 久久黄色av网站| 久久天堂av综合合色| 成人h猎奇视频网站| 韩国精品久久久999| 91久久夜色精品国产网站| 91精品视频在线免费观看| 国产在线观看91精品一区| 国产欧美精品在线播放| 日韩av电影在线免费播放| 亚洲成人激情图| 欧美日韩福利电影| 亚洲色无码播放| 91在线视频一区| 欧美精品在线免费播放| www.欧美精品一二三区| 国产成人综合av| 在线日韩av观看| 91精品国产综合久久香蕉最新版| 亚洲第一黄色网| 欧洲中文字幕国产精品| 精品美女永久免费视频| 欧美黑人国产人伦爽爽爽| 2020久久国产精品| 久久久久久久久久av| 国产在线观看精品一区二区三区| 欧美色道久久88综合亚洲精品| 日韩视频永久免费观看| 国产视频精品自拍| 成人黄色免费看| 国产精品久久色| 亚洲欧美日韩天堂一区二区| 欧美中在线观看| 亚洲va欧美va国产综合久久| 日韩欧美高清在线视频| 精品久久久久人成| 成人免费黄色网| 色无极影院亚洲| 日韩中文字幕国产精品| 中文字幕日本欧美| 伊人伊成久久人综合网站| 亚洲男女自偷自拍图片另类| 国内外成人免费激情在线视频网站| 精品亚洲精品福利线在观看| 国产精品免费视频xxxx| 热久久免费国产视频| 在线视频亚洲欧美| 亚洲视频999| 日韩专区在线播放| 亚洲激情视频在线| 中文字幕精品在线视频| 97福利一区二区| 亚洲美女av黄| 成人在线激情视频| 成人情趣片在线观看免费| 欧美性69xxxx肥| 欧美裸身视频免费观看| 精品成人在线视频| 色七七影院综合| 伊是香蕉大人久久| 精品欧美一区二区三区| 久久久久免费精品国产| 精品中文字幕在线观看| 狠狠综合久久av一区二区小说| 欧美大片在线影院| 中文字幕亚洲无线码a| 亚洲欧美国产另类| 国产偷亚洲偷欧美偷精品| 在线播放日韩av| 国产福利精品视频| 国产精品主播视频| 久久男人的天堂| 一本色道久久88综合亚洲精品ⅰ| 欧美第一黄网免费网站| 最近2019中文字幕在线高清| 91影院在线免费观看视频| 国产精品久久久av| 国产精品欧美风情| 91精品久久久久久久久中文字幕| 91久久精品国产91久久性色| 97在线免费观看视频| 欧美自拍视频在线| 中文字幕精品在线| 欧美另类交人妖| 国产一区二区在线免费| 欧美大人香蕉在线| 欧美一级高清免费播放| 日韩美女主播视频| 中文字幕欧美视频在线| 中文字幕亚洲专区| 国产精品扒开腿做爽爽爽男男| 日韩美女在线看| 亚洲性生活视频在线观看| 久久精品国产视频| 91午夜在线播放| 日韩毛片在线看| 国产日韩一区在线| 亚洲美女免费精品视频在线观看| 久久艳片www.17c.com| 精品国产乱码久久久久酒店| 成人高h视频在线| 日本午夜精品理论片a级appf发布| www.欧美精品一二三区| 亚洲iv一区二区三区| 国产亚洲精品久久久久久牛牛| www.亚洲成人| 欧美贵妇videos办公室| 久久精品美女视频网站| 欧美激情乱人伦一区| 午夜精品久久久久久久99热浪潮| 亚洲香蕉成视频在线观看| 国产成人精品在线视频| 国产成人福利视频| 粗暴蹂躏中文一区二区三区| 国产精品久久在线观看| 91九色国产在线| 日韩在线视频免费观看高清中文| 92看片淫黄大片看国产片| 久久久亚洲天堂| 亚洲欧美国产制服动漫| 精品视频久久久| 性色av一区二区三区免费| 成人午夜高潮视频| 在线视频欧美日韩| 欧美激情极品视频| 色狠狠av一区二区三区香蕉蜜桃| 亚洲黄色在线观看| 欧美专区在线播放| 狠狠色狠狠色综合日日小说| 欧美国产日韩一区二区| 亚洲深夜福利网站| 欧美国产视频一区二区| 69影院欧美专区视频| 亚洲石原莉奈一区二区在线观看| 日韩欧美精品中文字幕| 91网站在线看| 久久夜精品香蕉| 欧美午夜性色大片在线观看| 亚洲自拍小视频免费观看| 在线免费观看羞羞视频一区二区| 国内成人精品一区| 高跟丝袜一区二区三区| 亚洲精品国产欧美| 久久理论片午夜琪琪电影网|