這裏不對如何實現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/