Spring Security OAuth 官方例子分析

来源:互联网 发布:mac 邮件归类 编辑:程序博客网 时间:2024/05/29 03:17

流程分析

官方demo主要由两部分组成,tonr2(客户端)和sparklr2(服务端)。

学习曲线,spring+spring mvc+spring security+spring security oauth

1)在客户端tonr2

  • 客户端主界面上点击View my Sparklr photos调用 org.springframework.security.oauth.examples.tonr.mvc.SparklrController/sparklr/photos端点。它将通过一个sparklrService调用获取照片。sparklrService里面有装配sparklrRestTemplate,向sparklr2发送http://localhost:8080/sparklr2/photos?format=xml请求。
  • 调用OAuth2RestTemplate.doExecute()的第一步,就是从上下文(org.springframework.security.oauth2.client.DefaultOAuth2ClientContext)中获取accessToken()
OAuth2AccessToken accessToken = context.getAccessToken();
  • 第一次进来肯定是没有accessToken的,接下去,先没有判断,直接执行父类的doexcute。OAuth2RestTemplate重载了createRequest()方法,调用OAuth2RestTemplate.getAccessToken(),去获取token
  • 从context中取出token,如果token不存在或者已经超时,则调用OAuth2RestTemplate.acquireAccessToken()方法。
  • 接着调用accessTokenProvider.obtainAccessToken(resource, accessTokenRequest),这个accessTokenProvider其实是一个提供者列表:
private AccessTokenProvider accessTokenProvider = new AccessTokenProviderChain(Arrays.<AccessTokenProvider> asList(            new AuthorizationCodeAccessTokenProvider(), new ImplicitAccessTokenProvider(),            new ResourceOwnerPasswordAccessTokenProvider(), new ClientCredentialsAccessTokenProvider()));
  • obtainAccessToken()方法,里面做了很多判断,比如如果存在clientTokenServices的话,会先从存储中读取token,如果过期,还有调用refresh_token去刷新token。都没有则调用了中会根据资源类型判断使用具体哪种provider,本例子调用org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProviderobtainAccessToken(),然后因为没有授权码和状态码。抛出异常。
if (request.getAuthorizationCode() == null) {    if (request.getStateKey() == null) {        // 抛出异常,交由OAuth2ClientContextFilter捕获,进行处理。        throw getRedirectForAuthorization(resource, request);    }    obtainAuthorizationCode(resource, request);}
  • 这里抛出org.springframework.security.oauth2.client.resource.UserRedirectRequiredException异常,异常将被org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter.doFilter的Catch处理。
  • 然后进行会进行跳转redirectUser(redirect, request, response);然后再到this.redirectStrategy.sendRedirect(request, response, builder.build().encode().toUriString());这里再向sparklr2http://localhost:8080/sparklr2/oauth/authorize?client_id=tonr&redirect_uri=http://localhost:8080/tonr2/sparklr/photos&response_type=code&scope=read%20write&state=BHPKRb请求(授权请求)
  • 从这里开始获取获取授权码。

2)在服务端sparklr2(获取授权码流程)

  • 经过spring security的org.springframework.security.web.FilterChainProxy过滤,用户没登录,将用户导向登录页面登录,(Spring Security的功能,不详细说明)登录完成后继续跳转到之前的获取授权码请求。org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.authorize接受请求,会判断是否已经授权:
boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);authorizationRequest.setApproved(approved);// 如果用户已经授权给了客户端if (authorizationRequest.isApproved()) {    if (responseTypes.contains("token")) {        return getImplicitGrantResponse(authorizationRequest);    }    // 直接获取授权响应码    if (responseTypes.contains("code")) {        return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest,                (Authentication) principal));    }}// 否则还要导向用户到授权界面。model.put("authorizationRequest", authorizationRequest);return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);
  • 假设用户还没授权过给客户端,用户在界面选择是否授权并提交,org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.approveOrDeny再接收请求,然后再响应获取授权码(当然用户都拒绝授权所有权限就会抛UserDeniedAuthorizationException异常),同意则正常生成授权码,并保持授权状态信息,授权码会根据client_id做对应保存,一个授权码只能使用一次,oauth2没有对授权码做超时处理,需要的话自己增加。再根据回调url回到客户端。

3)获取授权码的响应回到客户端 tonr2(重定向回到redirect_url)

  • 回到org.springframework.security.oauth.examples.tonr.impl.SparklrServiceImpl.getSparklrPhotoIds再次发请求,此时又调用了sparklrRestTemplate。跟第一步时一样的流程,区别在于判断授权码和授权状态码时,因为已经在请求中带有这两个参数,所以不会抛出异常,而是执行retrieveToken();
  • 调用getRestTemplate().execute();这时就会向sparklr2发起获取accessToken的请求http://localhost:8080/sparklr2/oauth/token,这里发的是POST请求,参数都在form里面的。

4)在服务端获取 accessToken (获取token流程)

  • org.springfram work.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken接收请求,处理生成accessToken(是一个UUID,实际上包含的授权信息还是在服务端,只是这个UUID会对应Authentication,如果是JWT的话,用户和角色以及权限范围都会放在JWT中,传输过来进行解码生成Authentication,服务端不保留任何数据)。
  • 这个例子生成的accessToken在org.springframework.security.oauth2.provider.token.DefaultTokenServices.createAccessToken()
    然后调用tokenStore.storeAccessToken(accessToken, authentication),保存到服务端存储中,这里的tokenStore使用InMemoryTokenStore实现。(还有其他几种存储方式,例如JDBC, Redis以及JWT,可配置)。

5)获取accessToken的响应回到客户端

org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken这个方法会将得到的accessToken保存到OAuth2ClientContext。以后用户用这个accessToken来访问受保护的资源(直接访问资源服务端,当然这里授权服务端和资源服务端连在一起)就可以了.

6)访问受资源服务端保护的资源(前面没有特别说明的服务端都是指授权服务端)

  • 走正常流程,用户获取完accessToken再访问受保护资源的跟踪,发起请求GET http://localhost:8080/sparklr2/photos?format=xml(OAuth2RestTemplate的context有保存accessToken,发请求时将这个accessToken放进了请求头)
  • 使用注解@EnableResourceServer配置资源服务器之后,会自动创建OAuth2AuthenticationProcessingFilter,它用于将用户传过来的token验证,从存储找回用户的Authentication,下面是它的doFilter方法主要内容:
//从请求获取accessToken,即那个UUID.并实例化为PreAuthenticatedAuthenticationToken对象 Authentication authentication = tokenExtractor.extract(request); //authenticationManager为OAuth2AuthenticationManager的实例,//它会调用OAuth2Authentication auth = tokenServices.loadAuthentication(token); Authentication authResult = authenticationManager.authenticate(authentication); //将Authentication存到spring security的上下文.以供后续使用 SecurityContextHolder.getContext().setAuthentication(authResult); 
  • 如果没有抛出异常则代表验证通过,可以访问受保护的资源了,当然还可以根据角色ROLE和范围SCOPE进行限制,这个就跟Spring Security联系比较密切了,这里就暂时不分析了。

总结

总的来说,框架已经帮我们把OAuth2.0协议中的各个方面考虑清楚了,我们能自定义的内容主要集中在配置上,所以研究清楚每个配置的意义就比较重要了。

原创粉丝点击