標籤:
前言
這次為大家簡單介紹兩個在WEB開發中經常使用的概念——單點登入和訊息佇列以及具體到J2EE中的一些實現方案。本文原創性的工作比較少,主要是一些總結概括和自己的理解。
單點登入SSOSSO的業務情境
所謂單點登入就是在一個網站登入之後可以授信給其他網站,這樣就可以做到一次登入,到處操作。單點登入的實質就是安全上下文(Security Context)或憑證(Credential)在多個應用系統之間的傳遞或共用。
大部分的網站採用Cookie作為登入的一種簡單實現方案,在同一個頂層網域下面,這樣做並無問題,不需要對各個子系統分別驗證。但是Cookie無法跨域傳遞。將使用者的登入、憑證取得等解耦處理單獨作為一個子系統是合理的選擇。
SSO的核心要素
- 共用同一個身份認證系統,也就是說所有網站的身分識別驗證操作在同一個系統下完成
- 每個子系統從共同的身份認證系統中取得使用者憑證,包含使用者的身份、許可權資訊等
如下:
SSO的一種簡單實現方案
下面以採用Cookie的一種方案為例來解釋:
我們首先定義授信伺服器A,受信伺服器B,客戶C;當前的業務是B需要驗證C的身份。需要注意的是B和C都會保有session來記錄C的登入狀態,均會向C 的Header中寫入對應自己網域名稱的Cookie以儲存憑證資訊。Cookie中含有tokenId來標示C,也就是說對於A和B他們的Cookie中對應於同一個C,其tokenId應該一致。
C向B發起請求後,會有以下幾種情形:
- B含有session,C含有Cookie,且session和Cookie中的token一致,那麼不需要向A求助
- C中對於B無Cookie或Cookie到期或session與Cookie不一致,將向A發起請求。之後根據A的情形,有以下情況:
- A中session與C中Cookie的token一致,重建憑證資訊返回給B,B重新寫入Cookie與session
- A中Cookie到期或資訊不一致,將重新導向到登入頁面
- 對於登入情形,A將更新Cookie與session,然後C再向B發起請求,這時就會變成2中第一種情況,導致A和B的資訊完成同步。
Message Queue的業務情境
訊息佇列本身是簡單的,可以直接看做一個隊列,重點是如何定義儲存在隊列中的資料格式,以滿足我們對應的操作需求。MQ常常應用於那些並發量大而對於即時性要求不高的情況。舉個例子,比如一個使用者量較大的社交網站的評論發布,為什麼這麼說呢?對於這個任務,隊列中只用儲存評論相關資訊,對於從隊列中取的一方,只需要進行插入操作,符合前面所說的並發量大且可以有延時,同時並不難實現。
MQ的兩種模式
訊息佇列在WEB開發中主要有兩種模式:
- 生產者/消費者模式:對於一則訊息,只有一個消費者線程會去處理它,適用於我們上面所說的評論系統
- 發行者/訂閱者模式:對於所有訂閱者,它可以讀取所有在它加入之後發布的訊息
在J2EE中加入訊息佇列,我個人認為應該是這樣的:對於特定的HTTP請求,調用生產者/發行者的介面,入隊必要訊息,這個並不困難。大有蹊蹺的我覺得在於處理訊息的一方,可以實現listener將其交由容器管理,也可以自己開闢池來調度。舉例來說明,對於前者Spring-redis實現的pub/sub模式隊列就是直接在設定檔中設定RedisListener的實作類別,對於後者,你可以直接獨立出來寫離線指令碼來監聽隊列。
MQ的實現方案
目前業界有比較成熟的MQ解決產品,如下:
- RabbitMQ
- ActiveMQ
- kafka
- Redis
MQ的Spring+Redis實現簡單樣本
在Pom.xml中加入以下依賴
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.4.2.RELEASE</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.3</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.6.2</version> </dependency>
在ApplicationContext.xml的頭部插入schema
xmlns:redis="http://www.springframework.org/schema/redis"
在ApplicationContext中加入Redis的配置
<!-- 配置redis池,依次為最大執行個體數,最大空閑執行個體數,(建立執行個體時)最大等待時間,(建立執行個體時)是否驗證 --> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxTotal" value="${redis.maxTotal}"/> <property name="maxIdle" value="${redis.maxIdle}"/> <property name="maxWaitMillis" value="${redis.maxWaitMillis}"/> <property name="testOnBorrow" value="${redis.testOnBorrow}"/> </bean> <!-- 配置資料來源--> <bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="127.0.0.1"></property> <property name="port" value="6379"></property> <property name="usePool" value="true"></property> </bean> <!-- 配置資料操作 --> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="redisConnectionFactory"></property> </bean> <bean id="jdkSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /> <bean id="messageListener" class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter"> <property name="delegate" ref="messageDelegateListener" /> <!--這裡的messageDelegateListener在後面的檔案中註解的,這裡對應的具體訊息處理類的實現--> <property name="serializer" ref="jdkSerializer" /> </bean> <!-- 將訊息handler註冊 --> <redis:listener-container> <redis:listener ref="messageListener" method="handleMessage" serializer="jdkSerializer" topic="java"/> </redis:listener-container>
上文在定義Listener的時候採用了註解對象作為實作類別,也可以手動在設定檔中再寫一個bean,如下
<bean id="messageDelegateListener" class="***.***.***" />
最後我們給出一個接收方的實現
import java.io.Serializable;import org.springframework.stereotype.Component;@Component(value="messageDelegateListener")public class ListenMessage { public void handleMessage(Serializable message){ System.out.println(message); }}
單點登入與訊息佇列以及在J2EE中的實現方案