要实现OAuth服务端,我觉的就得先理解客户端的调用流程,服务提供商实现可能也有些区别,实现OAuth服务端的方式很多,具体可能看 http://oauth.net/code/
各语言的实现有(我使用了Apache Oltu):
Java NodeJS
- NodeJS OAuth 2.0 Provider
- Mozilla Firefox Accounts. A full stack Identy Provider system developed to support Firefox market place and other services
Ruby .NET
实现主要涉及参数配置如下:
授权码设置(code)
第三方通过code进行获取 access_token的时候需要用到,code的超时时间为10分钟,一个code只能成功换取一次access_token即失效。
授权作用域(scope)
作用域代表用户授权给第三方的接口权限,第三方应用需要向服务端申请使用相应scope的权限后,经过用户授权,获取到相应access_token后方可对接口进行调用。
令牌有效期(access_token)
access_token是调用授权关系接口的调用凭证,由于access_token有效期(目前为2个小时)较短,当access_token超时后,可以使用refresh_token进行刷新,access_token刷新结果有两种:
1. 若access_token已超时,那么进行refresh_token会获取一个新的access_token,新的超时时间;
2. 若access_token未超时,那么进行refresh_token不会改变access_token,但超时时间会刷新,相当于续期access_token。
refresh_token拥有较长的有效期(30天),当refresh_token失效的后,需要用户重新授权。
请求授权码
/** * Authorization Code 授权码模式 * Created by Irving on 2014/11/22. * Impl OAth2 http://oauth.net/2/ */ @Controller @RequestMapping("/oauth2") public class AuthzController { private static Logger logger = LoggerFactory.getLogger(AuthzController.class); private Cache cache ; @Autowired public AuthzController(CacheManager cacheManager) { this.cache = cacheManager.getCache("oauth2-cache"); } /* * * 构建OAuth2授权请求 [需要client_id与redirect_uri绝对地址] * @param request * @param session * @param model * @return 返回授权码(code)有效期10分钟,客户端只能使用一次[与client_id和redirect_uri一一对应关系] * @throws OAuthSystemException * @throws IOException * @url http://localhost:8080/oauth2/authorize?client_id={AppKey}&response_type=code&redirect_uri={YourSiteUrl} * @test http://localhost:8080/oauth2/authorize?client_id=fbed1d1b4b1449daa4bc49397cbe2350&response_type=code&redirect_uri=http://baidu.comx */ @RequestMapping(value = "/authorize") public String authorize(HttpServletRequest request,HttpSession session,Model model) throws OAuthSystemException, IOException { try { //构建OAuth请求 OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(request); //验证redirecturl格式是否合法 (8080端口测试) if (!oauthRequest.getRedirectURI().contains(":8080")&&!Pattern.compile("^[a-zA-Z]+://(\\w+(-\\w+)*)(\\.(\\w+(-\\w+)*))*(\\?\\s*)?$").matcher(oauthRequest.getRedirectURI()).matches()) { OAuthResponse oauthResponse = OAuthASResponse .errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .setError(OAuthError.CodeResponse.INVALID_REQUEST) .setErrorDescription(OAuthError.OAUTH_ERROR_URI) .buildJSONMessage(); logger.error("oauthRequest.getRedirectURI() : " + oauthRequest.getRedirectURI() + " oauthResponse.getBody() : " + oauthResponse.getBody()); model.addAttribute("errorMsg", oauthResponse.getBody()); return "/oauth2/error"; } //验证appkey是否正确 if (!validateOAuth2AppKey(oauthRequest)){ OAuthResponse oauthResponse = OAuthASResponse .errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .setError(OAuthError.CodeResponse.ACCESS_DENIED) .setErrorDescription(OAuthError.CodeResponse.UNAUTHORIZED_CLIENT) .buildJSONMessage(); logger.error("oauthRequest.getRedirectURI() : "+oauthRequest.getRedirectURI()+" oauthResponse.getBody() : "+oauthResponse.getBody()); model.addAttribute("errorMsg", oauthResponse.getBody()); return "/oauth2/error"; } //查询客户端Appkey应用的信息 String clientName= "Just Test App";//oauthClientService.findByClientId(oauthRequest.getClientId()); model.addAttribute("clientName",clientName); model.addAttribute("response_type",oauthRequest.getResponseType()); model.addAttribute("client_id",oauthRequest.getClientId()); model.addAttribute("redirect_uri",oauthRequest.getRedirectURI()); model.addAttribute("scope",oauthRequest.getScopes()); //验证用户是否已登录 if(session.getAttribute(ConstantKey.MEMBER_SESSION_KEY)==null) { //用户登录 if(!validateOAuth2Pwd(request)) { //登录失败跳转到登陆页 return "/oauth2/login"; } } //判断此次请求是否是用户授权 if(request.getParameter("action")==null||!request.getParameter("action").equalsIgnoreCase("authorize")){ //到申请用户同意授权页 return "/oauth2/authorize"; } //生成授权码 UUIDValueGenerator OR MD5Generator String authorizationCode = new OAuthIssuerImpl(new MD5Generator()).authorizationCode(); //把授权码存入缓存 cache.put(authorizationCode, DigestUtils.sha1Hex(oauthRequest.getClientId()+oauthRequest.getRedirectURI())); //构建oauth2授权返回信息 OAuthResponse oauthResponse = OAuthASResponse .authorizationResponse(request,HttpServletResponse.SC_FOUND) .setCode(authorizationCode) .location(oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI)) .buildQueryMessage(); //申请令牌成功重定向到客户端页 return "redirect:"+oauthResponse.getLocationUri(); } catch(OAuthProblemException ex) { OAuthResponse oauthResponse = OAuthResponse .errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .error(ex) .buildJSONMessage(); logger.error("oauthRequest.getRedirectURI() : " + ex.getRedirectUri() + " oauthResponse.getBody() : " + oauthResponse.getBody()); model.addAttribute("errorMsg", oauthResponse.getBody()); return "/oauth2/error"; } } /** * 用户登录 * @param request * @return */ private boolean validateOAuth2Pwd(HttpServletRequest request) { if("get".equalsIgnoreCase(request.getMethod())) { return false; } String name = request.getParameter("name"); String pwd = request.getParameter("pwd"); if(StringUtils.isEmpty(name) || StringUtils.isEmpty(pwd)) { return false; } try { if(name.equalsIgnoreCase("Irving")&&pwd.equalsIgnoreCase("123456")){ //登录成功 request.getSession().setAttribute(ConstantKey.MEMBER_SESSION_KEY,"Irving"); return true; } return false; } catch (Exception ex) { logger.error("validateOAuth2Pwd Exception: " + ex.getMessage()); return false; } } /** * 验证ClientID 是否正确 * @param oauthRequest * @return */ public boolean validateOAuth2AppKey(OAuthAuthzRequest oauthRequest) { //客户端Appkey ArrayList arrayKeys = new ArrayList(); arrayKeys.add("fbed1d1b4b1449daa4bc49397cbe2350"); arrayKeys.add("a85b033590714fafb20db1d11aed5497"); arrayKeys.add("d23e06a97e2d4887b504d2c6fdf42c0b"); return arrayKeys.contains(oauthRequest.getClientId()); } }
Refer:https://open.weixin.qq.com/cgi-bin/readtemplate?t=resource/app_wx_login_tmpl&lang=zh_CN#faq