以SpringFramework提供的sparklr2为蓝本,照葫芦画瓢画出一个OAuth2Server程序来,运行都没有问题。当正式要做项目的时候,却一而再再二三的出现怪异的问题。
一开始,是用户登录之后,直接报空指针错误。错误原因出现在AccessConfirmationController里,这是自己写的Controller,与Spring-Security-OAuth2配搭使用的,sparklr2里面有这个Controller。主要原因是getAccessConfirmation方法的参数model,里面没有给出名为“authorizationRequest”的对象,size为0.这个参数是从session里面取出来的,这个类在sparklr(包括葫芦画瓢的测试程序中是一样的)的代码如下:
import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.servlet.ModelAndView; /** * Controller for retrieving the model for and displaying the confirmation page for access to a protected resource. * * @author Ryan Heaton */ @Controller @SessionAttributes("authorizationRequest") public class AccessConfirmationController { private ClientDetailsService clientDetailsService; @RequestMapping("/oauth/confirm_access") public ModelAndView getAccessConfirmation(Map<String, Object> model) throws Exception { AuthorizationRequest clientAuth = (AuthorizationRequest) model.remove("authorizationRequest"); ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId()); model.put("auth_request", clientAuth); model.put("client", client); return new ModelAndView("access_confirmation", model); } @RequestMapping("/oauth/error") public String handleError(Map<String,Object> model) throws Exception { // We can add more stuff to the model here for JSP rendering. If the client was a machine then // the JSON will already have been rendered. model.put("message", "There was a problem with the OAuth2 protocol"); return "oauth_error"; } @Autowired public void setClientDetailsService(ClientDetailsService clientDetailsService) { this.clientDetailsService = clientDetailsService; } }
里面声明了SessionAttributes,但方法的model里面没有。
我起初的解决方法是,将AuthorizationRequest对象添加到方法参数列表中来,用@ModelAttribute来加以修饰。果然,可以获得这个参数,也可以正常的获得accessToken了,然而通过该accessToken获取受保护的数据总是提示权限不够。
通过跟踪调查,发现Vote进行表决的时候,检查scope通不过。
客户应用提供了scope为ROLE_READ,但是提取的授权中,scope为“read+write”,本来应该为两个字符串的一个Set结果变成了只有一个字符串的Set,因此检查的时候总是通不过。
跟踪了好久,结果发现就是在AccessConfirmationController获取AuthorizationRequest对象的时候发生了问题:往session里面存的时候还是好的(AuthorizationEndpoint.authorize方法,第159行,通过往model里面put值完成设置到Session里面),scope属性为Set[read, write],但取出来就是Set[read+write]。
此时的代码如下:
import java.util.Enumeration; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.servlet.ModelAndView; /** * Controller for retrieving the model for and displaying the confirmation page for access to a protected resource. * * @author Ryan Heaton */ @Controller @SessionAttributes("authorizationRequest") public class AccessConfirmationController { private ClientDetailsService clientDetailsService; @RequestMapping("/oauth/confirm_access") public ModelAndView getAccessConfirmation(Map<String, Object> model, @ModelAttribute("authorizationRequest")AuthorizationRequest clientAuth) throws Exception { model.remove("authorizationRequest"); ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId()); model.put("auth_request", clientAuth); model.put("client", client); return new ModelAndView("access_confirmation", model); } @RequestMapping("/oauth/error") public String handleError(Map<String,Object> model) throws Exception { // We can add more stuff to the model here for JSP rendering. If the client was a machine then // the JSON will already have been rendered. model.put("message", "There was a problem with the OAuth2 protocol"); return "oauth2/error"; } @Autowired public void setClientDetailsService(ClientDetailsService clientDetailsService) { this.clientDetailsService = clientDetailsService; } }
后来不得已,将AuthorizationRequest对象从getAccessConfirmation的参数列表清除了出去,然后添加了HttpSession对象作为参数,然后从session中直接获取数据,结果就好了:scope=Set[read, write],这样后面的流程就都通了。
最后的代码为:
import java.util.Map; import javax.servlet.http.HttpSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.servlet.ModelAndView; /** * Controller for retrieving the model for and displaying the confirmation page for access to a protected resource. * * @author Ryan Heaton */ @Controller @SessionAttributes("authorizationRequest") public class AccessConfirmationController { private ClientDetailsService clientDetailsService; @RequestMapping("/oauth/confirm_access") public ModelAndView getAccessConfirmation(Map<String, Object> model, HttpSession session) throws Exception { AuthorizationRequest clientAuth = (AuthorizationRequest) session.getAttribute("authorizationRequest"); // session.removeAttribute("authorizationRequest");//不要删除,删除后面会报错。model里面删除不代表从Session中删除对象 ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId()); model.put("auth_request", clientAuth); model.put("client", client); return new ModelAndView("access_confirmation", model); } @RequestMapping("/oauth/error") public String handleError(Map<String,Object> model) throws Exception { // We can add more stuff to the model here for JSP rendering. If the client was a machine then // the JSON will already have been rendered. model.put("message", "There was a problem with the OAuth2 protocol"); return "oauth_error"; } @Autowired public void setClientDetailsService(ClientDetailsService clientDetailsService) { this.clientDetailsService = clientDetailsService; } }
现在的问题是:
1.AccessConfirmationController的getAccessConfirmation为什么无法从model中获取AuthorizationRequest对象?如果将Session注入,是明明能看到里面有这个对象的。
2.将AuthorizationRequest声明在getAccessConfirmation方法中,然后用@ModelAttribute来修饰,为什么就能取出来了?但又为什么scope属性偏偏从Set[read, write]变成了Set[read+write]?谁搞的鬼?
3.在上述追踪过程中有没有遗漏什么地方,是导致这个问题的罪魁?