spring oauth2.0 demo入門分析

這裏不對如何實現oauth2.0分析,也不對security做分析,讀者可以google下security相關的知識,這裏主要列出看oauth2.0demo時流程流轉存在的疑惑。

1.oauth 2.0中的四個角色,資源擁有者,資源服務器,授權服務器,客戶端。

2.spring security限制訪問受限資源

3.client請求資源過程流轉分析

1.oauth 2.0中的四個角色不再接受,不明白的可以google下,如果有充足的實際可以看下oauth 2.0完整譯文或者原文。如果時間緊可以通過互聯網對oauth2.0做初步的瞭解。

 

2.spring security限制資源訪問

oauth2.0主要解決三方客戶端訪問資源需要的資源憑證的安全性,那麼這裏勢必會牽涉到資源訪問保護。spring oauth在spring security的基礎上實現的,所以需要對spring security有一點的瞭解。
tonr spring security配置

<http access-denied-page="/login.jsp?authorization_error=true" xmlns="http://www.springframework.org/schema/security">
		<intercept-url pattern="/sparklr/**" access="ROLE_USER" />
		<intercept-url pattern="/facebook/**" access="ROLE_USER" />
		<intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />

		<form-login authentication-failure-url="/login.jsp?authentication_error=true" default-target-url="/index.jsp"
			login-page="/login.jsp" login-processing-url="/login.do" />
		<logout logout-success-url="/index.jsp" logout-url="/logout.do" />
		<anonymous />
		<custom-filter ref="oauth2ClientFilter" after="EXCEPTION_TRANSLATION_FILTER" />
	</http>

上面主要表達的是對於http://localhost/sparklr/xxx這類的鏈接需要驗證,採用的是form驗證,登陸的url在form-login配置片段裏給出,登陸後以客戶端的身份訪問資源服務器的資源。

sparklr spring security配置片段

<http pattern="/photos/**" create-session="never" entry-point-ref="oauthAuthenticationEntryPoint"
		access-decision-manager-ref="accessDecisionManager" xmlns="http://www.springframework.org/schema/security">
		<anonymous enabled="false" />
		<intercept-url pattern="/photos" access="ROLE_USER,SCOPE_READ" />
		<intercept-url pattern="/photos/trusted/**" access="ROLE_CLIENT,SCOPE_TRUST" />
		<intercept-url pattern="/photos/user/**" access="ROLE_USER,SCOPE_TRUST" />
		<intercept-url pattern="/photos/**" access="ROLE_USER,SCOPE_READ" />
		<custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
		<access-denied-handler ref="oauthAccessDeniedHandler" />
	</http>

上面主要是表達的是對photos資源的訪問需要時授權的用戶。

接下來我們來看看整個流程是怎麼樣的。

3.client請求資源流程

a.  用戶通過瀏覽器訪問http://localhost/tonr/,用戶點擊sparklr pics
b. 請求被spring給攔截,要求做登陸進行權限校驗(tonr站點的驗證)
c. 若登陸成功,我們點擊sparklr pics訪問的url是sparklr/photos所以會被tonr的SparklrController處理

@RequestMapping("/sparklr/photos")
	public String photos(Model model) throws Exception {
		model.addAttribute("photoIds", sparklrService.getSparklrPhotoIds());
		return "sparklr";
	}

上面這兩句話很重要,

sparklrService.getSparklrPhotoIds()

該方法訪問資源服務器的資源,資源服務器會對進行權限驗證,這也是oauth協議工作的地方,我們先看是如何跳轉到sparklr服務器的

public List<String> getSparklrPhotoIds() throws SparklrException {
		try {
			InputStream photosXML = new ByteArrayInputStream(
					sparklrRestTemplate.getForObject(
							URI.create(sparklrPhotoListURL), byte[].class));
			System.out.println("hiiii, i have get the photo info");

			final List<String> photoIds = new ArrayList<String>();
			SAXParserFactory parserFactory = SAXParserFactory.newInstance();
			parserFactory.setValidating(false);
			parserFactory.setXIncludeAware(false);
			parserFactory.setNamespaceAware(false);
			SAXParser parser = parserFactory.newSAXParser();
			parser.parse(photosXML, new DefaultHandler() {
				@Override
				public void startElement(String uri, String localName,
						String qName, Attributes attributes)
						throws SAXException {
					if ("photo".equals(qName)) {
						photoIds.add(attributes.getValue("id"));
					}
				}
			});
			return photoIds;
		} catch (IOException e) {
			throw new IllegalStateException(e);
		} catch (SAXException e) {
			throw new IllegalStateException(e);
		} catch (ParserConfigurationException e) {
			throw new IllegalStateException(e);
		}
	}

如果不留意可能還奇怪怎麼會跳轉到資源服務器進行驗證了呢?

InputStream photosXML = new ByteArrayInputStream(
					sparklrRestTemplate.getForObject(
							URI.create(sparklrPhotoListURL), byte[].class));

注意到了沒 URI.create(spaklrPhotoLISTURL)這裏,請求資源服務器的資源,上面的配置我們看到,資源服務器會對請求校驗。這裏或許會有人對這個url怎麼來的有些疑惑

sparklrPhotoListURL

其實在resources目錄下有sparklr.properties配置文件,該文件最後被spring處理,綁定到這個ServiceBean裏(spring-servlet.xml裏有引用這個配置文件,看下就明白了),

<context:property-placeholder location="classpath:/sparklr.properties" />

spring會對該配置文件解析,然後將spring-servlet.xml裏的佔位符的地方給替換成具體的配置文件裏的值。ok繼續Controller往下走,最後一句不是麼

return "sparklr";

先別急,資源服務器驗證還沒走完呢。我們明白了訪問圖片的時候客戶端像服務器端發送http://localhost:8080/sparklr2/photos?format=xml,由於資源服務器(同時也是授權服務器)對這類請求要求權限驗證,上面的配置文件說過了。所以tonr客戶端會引導用戶到資源服務器/授權服務器進行授權;這裏表達不夠清晰或者是不正確,應該是讀取受限資源,tonr發起授權請求(如何處理這個異常就是spring oauth處理了)

tonr2 18:08:45.233 [DEBUG] DefaultRedirectStrategy - Redirecting to 'http://localhost:8080/sparklr2/oauth/authorize?client_id=tonr&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Ftonr%2Fsparklr%2Fphotos&response_type=code&scope=read+write&state=iVZRRl'

請求被導向了資源服務器登錄頁面(如果你還是不夠明白,你可以想象你用新浪微博登陸某個小網站,如果你新浪微博登陸過期了, 新浪微博會要你登陸一次),登陸成功後會有一個頁面讓資源擁有者(用戶)確認是否允許tonr訪問資源,注意瀏覽器的url:http://localhost:8080/sparklr2/oauth/authorize?client_id=tonr&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Ftonr%2Fsparklr%2Fphotos&response_type=code&scope=read+write&state=EGwPYB(這個過程可以用chrome的開發者工具查看,結合後臺log)

根據oauth2.0規範,client_id redirect_url response_type code 都是必須的,如果允許後,tonr能從授權服務器拿到一個access_token(這個過程是一個複雜的交互過程,可以寫一系列的文章了,先把它當做黑盒,不影響我們分析),以後客戶端拿着這個access_token去獲取資源,資源服務器會檢查token的有效性。到這裏我們可以繼續往下走了,拿到這個access_token後資源服務器會根據redirect_uri重定向到這個地址,用戶在三方客戶端上進行資源訪問了。

我們sparklr.jsp視圖(WEB-INF/jsp下),注意這個視圖文件裏的代碼片段

<ul id="picturelist">
      <c:forEach var="sparklrPhotoId" items="${photoIds}">
        <li><img src="<c:url value="/sparklr/photos/${sparklrPhotoId}"/>"/></li>
      </c:forEach>
    </ul>
 <li><img src="<c:url value="/sparklr/photos/${sparklrPhotoId}"/>"/></li>

這裏具體的圖片,查看SparklrController

@RequestMapping("/sparklr/photos/{id}")
	public ResponseEntity<BufferedImage> photo(@PathVariable String id) throws Exception {
		InputStream photo = sparklrService.loadSparklrPhoto(id);
		if (photo == null) {
			throw new UnavailableException("The requested photo does not exist");
		}
		BufferedImage body;
		MediaType contentType = MediaType.IMAGE_JPEG;
		Iterator<ImageReader> imageReaders = ImageIO.getImageReadersByMIMEType(contentType.toString());
		if (imageReaders.hasNext()) {
			ImageReader imageReader = imageReaders.next();
			ImageReadParam irp = imageReader.getDefaultReadParam();
			imageReader.setInput(new MemoryCacheImageInputStream(photo), true);
			body = imageReader.read(0, irp);
		} else {
			throw new HttpMessageNotReadableException("Could not find javax.imageio.ImageReader for Content-Type ["
					+ contentType + "]");
		}
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.IMAGE_JPEG);
		return new ResponseEntity<BufferedImage>(body, headers, HttpStatus.OK);
	}

這裏的分析跟獲取圖片所有數據類似,輸入流的獲取從配置文件裏讀取到的資源服務器的url,然後進行讀

public InputStream loadSparklrPhoto(String id) throws SparklrException {
		return new ByteArrayInputStream(sparklrRestTemplate.getForObject(
				URI.create(String.format(sparklrPhotoURLPattern, id)),
				byte[].class));
	}

再然後將圖片數據返回給瀏覽器,顯示之。

這裏省略了很多很多實現,只是對demo的流程進行了簡易的分析。行文裏如果有錯誤,歡迎大家批評指正。

 原文地址:http://marspring.mobi/spring-oauth2-0-demo/

 

發佈了89 篇原創文章 · 獲贊 2 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章