这里不对如何实现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://localhost/sparklr/xxx这类的链接需要验证,采用的是form验证,登陆的url在form-login配置片段里给出,登陆后以客户端的身份访问资源服务器的资源。
sparklr spring security配置片段
上面主要是表达的是对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 ListgetSparklrPhotoIds() 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 photoIds = new ArrayList (); 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里有引用这个配置文件,看下就明白了),
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下),注意这个视图文件里的代码片段
这里具体的图片,查看SparklrController
@RequestMapping("/sparklr/photos/{id}") public ResponseEntityphoto(@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 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 (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/