キャッシュによるAPP側とサーバインタフェースの相互作用を実現するSession制御
18561 ワード
従来のB/SモードのWebシステムとは異なり、モバイル端末APPとサーバ間のインタフェースのインタラクションは一般的にC/Sモードである.この場合、ユーザ登録にかかわると、WebシステムのようにWebコンテナに依存してSessionを管理することはできない.APPは要求するたびにサーバ側に新しいSessionを作成するからである.ユーザーのプライバシーや資金取引に関連するインタフェースの中には、現在のユーザーログインの合法性を確認する必要があるものもあります.ログインがないか、ログインが期限切れになっている場合は、このような操作はできません.ユーザーが初めてログインした後、ユーザーのIDをローカルストレージに保存し、サーバとインタラクティブなインタフェースがユーザーIDでユーザーIDを識別する「サボる」方法を見たことがあります.
この方法には主に2つの弊害があります.ローカルに記憶されているユーザIDが削除されていない限り、常に以上のインタフェースにアクセスすることができ、有効期間の判断を増加したり、ユーザが自発的に終了したりしない限り、再ログインする必要はありません. インタフェースはセキュリティが弱い.ユーザーIDはデータベース内のユーザー固有のIDに対応しているため、他の人はユーザーIDを取得したり、ユーザーIDを偽造したりすれば、以上のインタフェースを使用してそのユーザーを不正に操作することができる.
総合的に考えると、キャッシュを利用してサーバ側でSession管理メカニズムをシミュレートしてこの問題を解決することができます.もちろん、これは現在私が知っている比較的簡単で効果的なAPPユーザーSessionを解決する方案にすぎません.もし誰か他の良い案があれば、次のメッセージで交流してください.
ここで使うキャッシュフレームワークはEhcache、ダウンロードアドレスですhttp://www.ehcache.org/downloads/あ、もちろんMemcachedとか他のものも使えます.Ehcacheフレームワークを使用するのは、軽量、高速、統合が簡単なため、HibernateのデフォルトのCacheProviderでもあり、Hibernateを統合したプロジェクトにはEhcacheのjarパッケージを追加する必要はありません.
Ehcacheがあれば、Springプロファイルに適切な構成を追加します.構成情報は次のとおりです.
また、Ehcacheのプロファイルehcache.xmlの構成は次のとおりです.
Ehcacheを構成すると、@Autowiredまたは@Resourceからキャッシュインスタンスを直接注入できます.サンプルコードは次のとおりです.
キャッシュの準備が完了し、次にシミュレーションユーザーSessionが実現されます.実現の構想は次のとおりです.ユーザーがログインに成功した後、サーバー側は一定の規則に従ってTokenトークンを生成し、Tokenは可変であり、固定であってもよい(後述); Tokenをkeyとし、ユーザー情報をvalueとしてキャッシュに入れ、有効時間を設定する(例えば30分以内にアクセスしないと失効する); TokenをAPP側に戻し、APPをローカルストレージに保存してインタフェースを要求するときにこのパラメータを持参する. は、ユーザのプライバシーセキュリティなどに関連するすべてのインタフェースをブロックすることによって、要求中のTokenパラメータの正当性を検証し、キャッシュが期限切れであるかどうかを確認する. 検証が通過した後、現在のスレッドの操作がキャッシュから現在ログインしているユーザ情報を直接インデックスできるように、Token値をスレッド記憶に保存する.
以上のように、アプリ側がすべきことは、ログインしてサーバ側からTokenストレージを取得し、ユーザーのプライバシーに関するインタフェースにアクセスするときにこのTokenを持って自分のアイデンティティを識別することです.サーバ側が行うべきことは,ユーザのプライバシーに関するインタフェースをブロックしてTokenとログイン情報を検証し,検証後にスレッド変数にTokenを保存した後,他の操作でこのTokenを取り出してキャッシュから現在のユーザ情報を取得することである.このようにAPPはユーザーIDを知る必要はなく、取得したのはアイデンティティIDだけで、このIDは可変で、サーバはこのIDに基づいて操作するユーザーを知ることができます.
Tokenが可変かどうかについては,処理の詳細が異なり,効果も異なる. Tokenが固定されている場合:サーバ側がTokenを生成する際にユーザ名とパスワードを一緒にMD 5(username+password)を暗号化する.これにより、同じユーザにとって、ログインするたびにTokenは同じであり、ユーザは複数のクライアントにログインし、1つのセッションを共有することができ、ユーザのパスワードが変更されたときにユーザに再ログインを要求することができる. Tokenが可変の場合:サーバ側がTokenを生成すると、ユーザ名、パスワードと現在のタイムスタンプとともにMD 5が暗号化されます.すなわち、MD 5(username+password+timestamp).これにより,同じユーザに対してログイン毎のTokenが異なり,前回ログインしたキャッシュ情報をクリアすることで,一意のユーザログインの効果を実現できる.
同じユーザがキャッシュに1つのログイン情報しかないことを保証するために、サーバ側はTokenを生成した後、ユーザ名に対してMD 5をSeed、すなわちMD 5(username)として単独で行うことができる.さらにSeedをkey,Tokenをvalueとしてキャッシュに保存することで,Tokenが変化してもユーザごとのSeedが固定されていれば,SeedインデックスによってTokenに,前回のログイン情報をTokenによって消去することができ,重複ログイン時にキャッシュに無効なログイン情報が過剰に保存されないようにする.
TokenベースのSession制御部分コードは以下の通りである.
Tokenブロッキング部分のコードは以下の通りです.
ここまで,インタフェース要求の正当性をある程度確保することができ,ユーザ情報の偽造が容易になることはなく,他人が不正な手段でTokenを手に入れたとしても一時的であり,キャッシュが失効した後やユーザが再ログインした後もTokenと同様に無効である.サーバインタフェースのセキュリティ要件がより高い場合は、要求情報が盗まれないようにSSLプロトコルに変更できます.
この方法には主に2つの弊害があります.
総合的に考えると、キャッシュを利用してサーバ側で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が実現されます.実現の構想は次のとおりです.
以上のように、アプリ側がすべきことは、ログインしてサーバ側からTokenストレージを取得し、ユーザーのプライバシーに関するインタフェースにアクセスするときにこのTokenを持って自分のアイデンティティを識別することです.サーバ側が行うべきことは,ユーザのプライバシーに関するインタフェースをブロックしてTokenとログイン情報を検証し,検証後にスレッド変数にTokenを保存した後,他の操作でこのTokenを取り出してキャッシュから現在のユーザ情報を取得することである.このようにAPPはユーザーIDを知る必要はなく、取得したのはアイデンティティIDだけで、このIDは可変で、サーバはこのIDに基づいて操作するユーザーを知ることができます.
Tokenが可変かどうかについては,処理の詳細が異なり,効果も異なる.
同じユーザがキャッシュに1つのログイン情報しかないことを保証するために、サーバ側はTokenを生成した後、ユーザ名に対してMD 5をSeed、すなわちMD 5(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 @PreDestroy
11 protected void shutdown() {
12 if (ehcache != null) {
13 ehcache.getCacheManager().shutdown();
14 }
15 }
16
17 /**
18 *
19 *
20 * @param loginUser
21 */
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 * @return
41 */
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 token
51 * @return
52 */
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 seed
74 */
75 public void clearLoginInfoBySeed(String seed) {
76 // seed token
77 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 @Override
10 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 URI
37 * @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 request
55 * @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プロトコルに変更できます.