最近在学习Oauth,趁有时间把一些实践经验和理解记录下来,目前oauth最新的版本是2.0,相比1.0更简单更安全,在企业的业务系统中可用来实现SSO。
1、OAuth的简述
OAuth(Open Authorization,开放授权)是为用户资源的授权定义了一个安全、开放及简单的标准,第三方无需知道用户的账号及密码,就可获取到用户的授权信息,并且这是安全的。
2、名称定义
在详细讲解OAuth 2.0之前,需要了解几个专用名词。它们对读懂后面的讲解,至关重要。
(1)Third-party application:第三方应用程序,本文中又称"客户端"(client)。
(2)Resource Owner:资源所有者,本文中又称"用户"(user)。
(3)User Agent:用户代理,本文中就是指浏览器。
(4)Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。
(5)Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。
知道了上面这些名词,就不难理解,OAuth的作用就是让"客户端"安全可控地获取"用户"的授权,与"服务商提供商"进行互动。
3、OAuth的原理
上图是Oauth2.0其中的授权码模式(authorization code),因为这个模式比较常用,我主要学习这个为主,图中我分为5个步骤讲解
【A】 用户请求访问客户端,或者将前者导向至认证服务器,带上参数:client_id和redirect_url
【B】 用户登录认证服务器,并选择是否授权给客户端访问
【C】 用户选择同意授权,认证服务器把用户导向至redirect_url,并且附上授权码(authorization code)
【D】 客户端向认证服务器请求access token,需要带上参数:client_id,client_secret,redirect_uri,和上一步获取的authorization code;认证服务器检查参数正确则返回access tonken表示授权成功
【E】 客户端使用access token请求资源服务器获取用户资源。
3、OAuth2.0简单实现(java)
国外有几个开源的java版的oauth实现,可以帮助较快的搭建一个oauth服务器,例如以下几个(可点击打开对应官网)
· Java
· Apache Oltu
· Spring Security for OAuth
· Apis Authorization Server (v2-31)
· Restlet Framework (draft 30)
· Apache CXF
Apache Oltu
目前我只是尝试了Oltu,比较轻量级,也不需要依赖其他服务,比较简单,但是官方的文档实在简陋,也是花了些时间才大概搞明白怎么用,下面就放出代码给大家演示下
服务端
获取授权码服务端代码示例
@RequestMapping("/authorize") public ModelAndView authorize(HttpServletRequest request) throws OAuthSystemException, OAuthProblemException, URISyntaxException { ModelAndView mav = new ModelAndView(); // 构建OAuth请求 OAuthAuthzRequest oAuthzRequest = new OAuthAuthzRequest(request); // 获取OAuth客户端Id String clientId = oAuthzRequest.getClientId(); // 校验客户端Id是否正确 if (!checkClientId(clientId)) { mav.addObject("msg", "无效的客户ID"); mav.setViewName("forward:/ljdp/oauth/authorizefail"); returnmav; }
//检查用户是否登陆和同意授权,如何还没登陆则跳转至登陆页面,然后进行授权提示 //待开发。。。
//生成授权码 String authCode = null; String responseType = oAuthzRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE); //ResponseType仅支持CODE和TOKEN if(responseType.equals(ResponseType.CODE.toString())){ OAuthIssuerImpl oAuthIssuerImpl = new OAuthIssuerImpl(new MD5Generator()); authCode = oAuthIssuerImpl.authorizationCode(); System.out.println("授权码="+authCode); }
//获取客户端重定向地址 String redirectURI = oAuthzRequest.getParam(OAuth.OAUTH_REDIRECT_URI); mav.setViewName("redirect:"+redirectURI+"?code="+authCode); returnmav; } |
获取Access Token服务端代码示例
@RequestMapping("/accesstoken") public ResponseEntity<String> accessToken(HttpServletRequest request) throws OAuthSystemException, OAuthProblemException{ //构建OAuth请求 OAuthTokenRequest tokenRequest = new OAuthTokenRequest(request); //获取OAuth客户端Id String clientId = tokenRequest.getClientId(); //校验客户端Id是否正确 if(!checkClientId(clientId)){ OAuthResponse oAuthResponse = OAuthASResponse .errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_CLIENT) .setErrorDescription("无效的客户端Id") .buildJSONMessage(); System.out.println(oAuthResponse.getBody()); return new ResponseEntity<String>(oAuthResponse.getBody(), HttpStatus.valueOf(oAuthResponse.getResponseStatus())); }
//检查客户端安全KEY是否正确 if(!checkClientSecret(tokenRequest.getClientSecret())){ OAuthResponse response = OAuthResponse.errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT) .setErrorDescription("客户端安全KEY认证不通过") .buildJSONMessage(); return new ResponseEntity<String>(response.getBody(), HttpStatus.valueOf(response.getResponseStatus())); }
//检查redirect_uri是否和认证的一致 if(!checkRedirectUri(tokenRequest.getRedirectURI())){ OAuthResponse response = OAuthResponse.errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT) .setErrorDescription("客户端认证不通过") .buildJSONMessage(); return new ResponseEntity<String>(response.getBody(), HttpStatus.valueOf(response.getResponseStatus())); }
//验证类型,有AUTHORIZATION_CODE/PASSWORD/REFRESH_TOKEN/CLIENT_CREDENTIALS if(tokenRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(GrantType.AUTHORIZATION_CODE.toString())){ String authCode = tokenRequest.getParam(OAuth.OAUTH_CODE); if(!checkAuthCode(authCode)){ OAuthResponse response = OAuthResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_GRANT) .setErrorDescription("错误的授权码") .buildJSONMessage(); return new ResponseEntity<String>(response.getBody(), HttpStatus.valueOf(response.getResponseStatus())); } //生成访问令牌 OAuthIssuerImpl authIssuerImpl = new OAuthIssuerImpl(new MD5Generator()); String accessToken = authIssuerImpl.accessToken();
//生成OAuth响应 OAuthResponse response = OAuthASResponse .tokenResponse(HttpServletResponse.SC_OK) .setAccessToken(accessToken) .setExpiresIn("1000") .buildJSONMessage(); System.out.println(response.getBody()); // HttpHeaders headers = new HttpHeaders(); // headers.setContentType(MediaType.APPLICATION_JSON); return new ResponseEntity<String>(response.getBody(), HttpStatus.valueOf(response.getResponseStatus())); }
OAuthResponse response = OAuthResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.UNSUPPORTED_GRANT_TYPE) .setErrorDescription("不支持此授权类型") .buildJSONMessage(); return new ResponseEntity<String>(response.getBody(), HttpStatus.valueOf(response.getResponseStatus())); } |
用Accesstoken获取授权用户资源服务器端代码示例
@RequestMapping("/resource") @ResponseBody public AuthUserInfo resource(HttpServletRequest request, HttpServletResponse response) { // Make the OAuth Request out of this request and validate it // Specify where you expect OAuth access token (request header, body // or query string) OAuthAccessResourceRequest oauthRequest = new OAuthAccessResourceRequest(request, ParameterStyle.QUERY);
// Get the access token String accessToken = oauthRequest.getAccessToken();
// ... validate access token
//验证通过返回用户信息 return new AuthUserInfo("test", "测试"); } |
客户端
获取accessToken代码示例
OAuthClientRequest request = OAuthClientRequest //获取accesstoken地址 .tokenLocation("http://localhost:8080/ljdp/oauth/accesstoken") //授权方式 .setGrantType(GrantType.AUTHORIZATION_CODE) //你的clientid和secret .setClientId("your-application-client-id") .setClientSecret("your-application-client-secret") //第一步重定向的uri .setRedirectURI("http://localhost:8080/ljdp/oauth/getcode") //第一步获取的认证码 .setCode("****") .buildQueryMessage();
//create OAuth client that uses custom http client under the hood OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
//Custom response classes are an easy way to deal with oauth providers that introduce modifications to //OAuth 2.0 specification OAuthJSONAccessTokenResponse oAuthResponse = oAuthClient.accessToken(request, OAuthJSONAccessTokenResponse.class);
String accessToken = oAuthResponse.getAccessToken(); Long expiresIn = oAuthResponse.getExpiresIn(); |
用accesstoken获取用户资源代码示例
OAuthClientRequest bearerClientRequest = new OAuthBearerClientRequest( "http://localhost:8080/ljdp/oauth/resource") .setAccessToken("********") .buildQueryMessage();
OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
OAuthResourceResponse resourceResponse = oAuthClient.resource( bearerClientRequest, OAuth.HttpMethod.GET, OAuthResourceResponse.class);
System.out.println(resourceResponse.getBody()); System.out.println(resourceResponse.getResponseCode()); |