标签:
oltu是一个开源的oauth2.0协议的实现,本人在此开源项目的基础上进行修改,实现一个自定义的oauth2.0模块。
关于oltu的使用大家可以看这里:http://oltu.apache.org/
项目可以从这里下载:http://mirror.bit.edu.cn/apache/oltu/org.apache.oltu.oauth2/
项目中我将四种授权方式都做了实现(授权码模式、简化模式、密码模式及客户端模式),但是这里仅以授权码模式为例,服务端采用Jersey框架实现,而客户端采用spring mvc实现。
服务器端实现:为了方便开发,我将oltu的所有源码都拖进了项目中,而不是导入jar,因为很多地方可能在我所开发的项目中不适用,这样可以方便修改和跟踪代码。
其实服务端开发很简单,主要集中在两个比较主要的文件中,一个是请求授权的AuthzEndpoint.java,一个是生成令牌的TokenEndpoint.java,如图所示。
至于资源控制器由于我开发的项目中,资源访问控制是采用过滤器的方式,因此没有用到oltu提供的java类,两个主要类文件的代码修改如下:
AuthzEndpoint.java
/** * Copyright 2010 Newcastle University * * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.oltu.oauth2.integration.endpoints; import java.net.URI; import java.net.URISyntaxException; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; import java.util.Properties; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import org.apache.ibatis.session.SqlSession; import org.apache.oltu.oauth2.as.issuer.MD5Generator; import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl; import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest; import org.apache.oltu.oauth2.as.response.OAuthASResponse; import org.apache.oltu.oauth2.common.OAuth; import org.apache.oltu.oauth2.common.error.OAuthError; import org.apache.oltu.oauth2.common.error.ServerErrorType; import org.apache.oltu.oauth2.common.exception.OAuthProblemException; import org.apache.oltu.oauth2.common.exception.OAuthSystemException; import org.apache.oltu.oauth2.common.message.OAuthResponse; import org.apache.oltu.oauth2.common.message.types.ResponseType; import org.apache.oltu.oauth2.integration.utils.Cache; import org.apache.oltu.oauth2.integration.utils.CacheManager; import com.cz.bean.App; import com.cz.bean.Authority; import com.cz.bean.RefreshToken; import com.cz.dao.AppMapper; import com.cz.dao.AuthorityMapper; import com.cz.dao.RefreshTokenMapper; import com.cz.util.DbUtil; /** * * client request authorization * */ @Path ( "/authz" ) public class AuthzEndpoint { SqlSession sqlSession = DbUtil.getSessionFactory().openSession( true ); AppMapper appDao = sqlSession.getMapper(AppMapper. class ); AuthorityMapper authorityDao = sqlSession.getMapper(AuthorityMapper. class ); RefreshTokenMapper refreshTokenDao = sqlSession .getMapper(RefreshTokenMapper. class ); //登录页面 private static String loginPage; //错误页面 private static String errorPage; static { Properties p = new Properties(); try { p.load(AuthzEndpoint. class .getClassLoader().getResourceAsStream( "config.properties" )); loginPage = p.getProperty( "loginPage" ); errorPage = p.getProperty( "errorPage" ); } catch (Exception e) { e.printStackTrace(); } } public static final String INVALID_CLIENT_DESCRIPTION = "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)." ; @GET public Response authorize( @Context HttpServletRequest request) throws URISyntaxException, OAuthSystemException { OAuthAuthzRequest oauthRequest = null ; OAuthIssuerImpl oauthIssuerImpl = new OAuthIssuerImpl( new MD5Generator()); try { oauthRequest = new OAuthAuthzRequest(request); /* * 当前登录的用户,模拟一个从session中获取的登录用户 * 该方法未实现,待模块与养老平台整合时,应调用养老平台方法判断用户是否已登录 * 并获得对应用户的userId */ String userId = "1" ; if ( "" .equals(userId) || userId == null ) { // 用户没有登录就跳转到登录页面 return Response.temporaryRedirect( new URI(loginPage)).build(); } App app = null ; if (oauthRequest.getClientId()!= null && ! "" .equals(oauthRequest.getClientId())){ app = appDao.selectByPrimaryKey(oauthRequest.getClientId()); } else { return Response.temporaryRedirect( new URI(errorPage+ "?error=" +ServerErrorType.CLIENT_ID_IS_NULL)).build(); } // 根据response_type创建response String responseType = oauthRequest .getParam(OAuth.OAUTH_RESPONSE_TYPE); OAuthASResponse.OAuthAuthorizationResponseBuilder builder = OAuthASResponse .authorizationResponse(request, HttpServletResponse.SC_FOUND); // 检查传入的客户端id是否正确 if (app == null ) { return Response.temporaryRedirect( new URI(errorPage+ "?error=" +ServerErrorType.UNKOWN_CLIENT_ID)).build(); } String scope = oauthRequest.getParam(OAuth.OAUTH_SCOPE); // 授权请求类型 if (responseType.equals(ResponseType.CODE.toString())) { String code = oauthIssuerImpl.authorizationCode(); builder.setCode(code); CacheManager.putCache(userId+ "_code" , new Cache( "code" , code, 216000000 , false )); CacheManager.putCache(userId+ "_scope" , new Cache( "scope" , scope, 216000000 , false )); } if (responseType.equals(ResponseType.TOKEN.toString())) { // 校验client_secret if (!app.getSecret_key().equals(oauthRequest.getClientSecret())) { OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_OK) .setError(OAuthError.TokenResponse.INVALID_CLIENT).setErrorDescription(INVALID_CLIENT_DESCRIPTION) .buildJSONMessage(); return Response.status(response.getResponseStatus()).entity(response.getBody()).build(); } String accessToken = oauthIssuerImpl.accessToken(); builder.setAccessToken(accessToken); builder.setExpiresIn(3600l); //判断是否已经授权----待调整是放在authz部分还是token部分 Map<String,Object> aQueryParam = new HashMap<>(); aQueryParam.put( "appKey" ,oauthRequest.getClientId()); aQueryParam.put( "userId" ,Integer.valueOf(userId)); if (authorityDao.findUnique(aQueryParam)== null ){ Authority authority = new Authority(); authority.setApp_key(oauthRequest.getClientId()); authority.setUser_id(Integer.valueOf(userId)); authorityDao.insert(authority); } // 存储token,已授权则更新令牌,未授权则新增令牌 Map<String,Object> rQueryParam = new HashMap<>(); rQueryParam.put( "appKey" , oauthRequest.getClientId()); rQueryParam.put( "userId" , Integer.valueOf(userId)); if (refreshTokenDao.findUnique(rQueryParam) != null ) { Map<String,Object> map = new HashMap<>(); map.put( "accessToken" , accessToken); map.put( "appKey" , oauthRequest.getClientId()); map.put( "userId" , Integer.valueOf(userId)); map.put( "createTime" , getDate()); map.put( "scope" , scope); map.put( "authorizationTime" , getDate()); refreshTokenDao.updateAccessToken(map); } else { RefreshToken rt = new RefreshToken(); rt.setApp_key(oauthRequest.getClientId()); rt.setUser_id(Integer.valueOf(userId)); rt.setAccess_token(accessToken); rt.setCreate_time(getDate()); rt.setAuthorization_time(getDate()); rt.setExpire( "3600" ); rt.setScope(scope); rt.setAuthorization_time(getDate()); refreshTokenDao.insert(rt); } } // 客户端跳转URI String redirectURI = oauthRequest .getParam(OAuth.OAUTH_REDIRECT_URI); final OAuthResponse response = builder.location(redirectURI).setParam( "scope" , scope) .buildQueryMessage(); String test = response.getLocationUri(); URI url = new URI(response.getLocationUri()); return Response.status(response.getResponseStatus()).location(url) .build(); } catch (OAuthProblemException e) { return Response.temporaryRedirect( new URI(errorPage+ "?error=" +ServerErrorType.BAD_RQUEST)).build(); } } private String getDate() { SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); return sdf.format(System.currentTimeMillis()); } } |
上面的代码是授权码认证的第一步,当用户同意授权之后向服务器请求授权码。你可以使用一下腾讯的授权功能来加深一下体会,因为我所开发的模块也是参考腾讯的授权认证流程来实现的,客户端通过提交请求,访问类似http://192.168.19.75:10087/oauth/authz?client_id=s6BhdRkqt3&client_secret=12345&redirect_uri=http://localhost:8080/redirect.jsp&state=y&response_type=authorization_code的链接来访问上面的程序,参数的含义如下
client_id :客户端id
client_secret:客户端密钥
redirect_uri:回调地址,第三方应用定义的地址
State:状态,服务器将返回一个一模一样的参数。
response_type:授权方式,这里必须是authorization_code,表示授权码 方式。
这个过程结束时,服务器会跳转至第三方应用定义的回调地址并附上授权码,而第三方通过这个回调地址获得授权码并进行相应的处理,而这个过程在oltu的实现中其实就是几行简单的代码:
// 创建response wrapper OAuthAuthzResponse oar = null ; oar = OAuthAuthzResponse.oauthCodeAuthzResponse(request); // 获得授权码 String code = oar.getCode(); |
上面的代码就是oltu客户端接收服务器发回的授权码的代码,其中request是一个HttpServletRequest对象,获得了授权码之后,按照下一步的流程,自然就是向授权服务器请求令牌并附上上一步获得的授权码。服务器获得授权码并进行相应处理的代码如下:
TokenEndpoint.java
/** * Copyright 2010 Newcastle University * * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.oltu.oauth2.integration.endpoints; import java.net.URI; import java.net.URISyntaxException; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; import java.util.Properties; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Consumes; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import org.apache.ibatis.session.SqlSession; import org.apache.oltu.oauth2.as.issuer.MD5Generator; import org.apache.oltu.oauth2.as.issuer.OAuthIssuer; import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl; import org.apache.oltu.oauth2.as.request.OAuthTokenRequest; import org.apache.oltu.oauth2.as.response.OAuthASResponse; import org.apache.oltu.oauth2.common.OAuth; import org.apache.oltu.oauth2.common.error.OAuthError; import org.apache.oltu.oauth2.common.error.ServerErrorType; import org.apache.oltu.oauth2.common.exception.OAuthProblemException; import org.apache.oltu.oauth2.common.exception.OAuthSystemException; import org.apache.oltu.oauth2.common.message.OAuthResponse; import org.apache.oltu.oauth2.common.message.types.GrantType; import org.apache.oltu.oauth2.integration.utils.CacheManager; import com.cz.bean.App; import com.cz.bean.Authority; import com.cz.bean.RefreshToken; import com.cz.bean.User; import com.cz.dao.AppMapper; import com.cz.dao.AuthorityMapper; import com.cz.dao.RefreshTokenMapper; import com.cz.dao.UserMapper; import com.cz.util.DbUtil; /** * * get access token * */ @Path ( "/token" ) public class TokenEndpoint { SqlSession sqlSession = DbUtil.getSessionFactory().openSession( true ); AppMapper appDao = sqlSession.getMapper(AppMapper. class ); RefreshTokenMapper refreshTokenDao = sqlSession .getMapper(RefreshTokenMapper. class ); UserMapper dao = sqlSession.getMapper(UserMapper. class ); AuthorityMapper authorityDao = sqlSession.getMapper(AuthorityMapper. class ); // 登录页面 private static String loginPage; // 错误页面 private static String errorPage; static { Properties p = new Properties(); try { p.load(AuthzEndpoint. class .getClassLoader().getResourceAsStream( "config.properties" )); loginPage = p.getProperty( "loginPage" ); errorPage = p.getProperty( "errorPage" ); } catch (Exception e) { e.printStackTrace(); } } public static final String INVALID_CLIENT_DESCRIPTION = "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)." ; @SuppressWarnings ({ "unchecked" , "rawtypes" }) @POST @Consumes ( "application/x-www-form-urlencoded" ) @Produces ( "application/json" ) public Response authorize( @Context HttpServletRequest request) throws OAuthSystemException, URISyntaxException { OAuthTokenRequest oauthRequest = null ; String scope = "" ; OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl( new MD5Generator()); try { oauthRequest = new OAuthTokenRequest(request); /* * 当前登录的用户,模拟一个从session中获取的登录用户 * 该方法未实现,待模块与养老平台整合时,应调用养老平台方法判断用户是否已登录 */ String userId = "1" ; if ( "" .equals(userId) || userId == null ) { // 用户没有登录的话就跳转到登录页面 return Response.temporaryRedirect( new URI(loginPage)).build(); } App app = null ; if (oauthRequest.getClientId() != null && ! "" .equals(oauthRequest.getClientId())) { app = appDao.selectByPrimaryKey(oauthRequest.getClientId()); } else { return Response.temporaryRedirect( new URI(errorPage + "?error=" + ServerErrorType.CLIENT_ID_IS_NULL)).build(); } // 校验clientid if (app == null || !app.getApp_key().toString().equals(oauthRequest.getClientId())) { if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(GrantType.AUTHORIZATION_CODE.toString())){ return Response.temporaryRedirect( new URI(errorPage + "?error=" + ServerErrorType.UNKOWN_CLIENT_ID)).build(); } else { OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_OK) .setError(OAuthError.TokenResponse.INVALID_CLIENT).setErrorDescription(INVALID_CLIENT_DESCRIPTION) .buildJSONMessage(); return Response.status(response.getResponseStatus()).entity(response.getBody()).build(); } } // 校验client_secret if (!app.getSecret_key().equals(oauthRequest.getClientSecret())) { if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(GrantType.AUTHORIZATION_CODE.toString())){ return Response.temporaryRedirect( new URI(errorPage + "?error=" + ServerErrorType.UNKOWN_CLIENT_SECRET)).build(); } else { OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_OK) .setError(OAuthError.TokenResponse.INVALID_CLIENT).setErrorDescription(INVALID_CLIENT_DESCRIPTION) .buildJSONMessage(); return Response.status(response.getResponseStatus()).entity(response.getBody()).build(); } } // 校验不同类型的授权方式 if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(GrantType.AUTHORIZATION_CODE.toString())) { String cacheCode = null ; if (CacheManager.getCacheInfo(userId + "_code" ).getValue() != null ) { cacheCode = CacheManager.getCacheInfo(userId + "_code" ) .getValue().toString(); } else { // 用户没有登录的话就跳转到登录页面 return Response.temporaryRedirect( new URI(loginPage)).build(); } if (!cacheCode.equals(oauthRequest.getParam(OAuth.OAUTH_CODE))) { return Response.temporaryRedirect( new URI(errorPage+ "?error=" + ServerErrorType.INVALID_AUTHORIZATION_CODE)).build(); } if (CacheManager.getCacheInfo(userId+ "_scope" ).getValue()!= null ){ scope = CacheManager.getCacheInfo(userId+ "_scope" ).getValue().toString(); } } else if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(GrantType.PASSWORD.toString())) { User user = dao.getById(userId); if (!user.getPassword().equals(oauthRequest.getPassword())|| !user.getName().equals(oauthRequest.getUsername())) { OAuthResponse response = OAuthASResponse .errorResponse(HttpServletResponse.SC_OK) .setError(OAuthError.TokenResponse.INVALID_CLIENT) .setErrorDescription( "Invalid username or password." ) .buildJSONMessage(); return Response.status(response.getResponseStatus()).entity(response.getBody()).build(); } } else if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals( GrantType.CLIENT_CREDENTIALS.toString())) { // 客户端id以及secret已验证,更多验证规则在这里添加,没有其他验证则程序直接发放令牌 // OAuthResponse response = OAuthASResponse // .errorResponse(HttpServletResponse.SC_OK) // .setError(OAuthError.TokenResponse.INVALID_GRANT) // .setErrorDescription("invalid client") // .buildJSONMessage(); // return Response.status(response.getResponseStatus()).entity(response.getBody()).build(); } else if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals( GrantType.REFRESH_TOKEN.toString())) { // 刷新令牌未实现 } String accessToken = oauthIssuerImpl.accessToken(); String refreshToken = oauthIssuerImpl.refreshToken(); // 构建响应 OAuthResponse response = OAuthASResponse .tokenResponse(HttpServletResponse.SC_OK) .setAccessToken(accessToken).setRefreshToken(refreshToken) .setExpiresIn( "3600" ) .buildJSONMessage(); // 判断是否已经授权----待调整是放在authz部分还是token部分 Map aQueryParam = new HashMap(); aQueryParam.put( "appKey" , oauthRequest.getClientId()); aQueryParam.put( "userId" , Integer.valueOf(userId)); if (authorityDao.findUnique(aQueryParam) == null ) { Authority authority = new Authority(); authority.setApp_key(oauthRequest.getClientId()); authority.setUser_id(Integer.valueOf(userId)); authorityDao.insert(authority); } // String scope = ""; // if(CacheManager.getCacheInfo(userId+"_scope").getValue()!=null){ // scope = CacheManager.getCacheInfo(userId+"_scope").getValue().toString(); // } // 存储token,已授权则更新令牌,未授权则新增令牌 Map rQueryParam = new HashMap(); rQueryParam.put( "appKey" , oauthRequest.getClientId()); rQueryParam.put( "userId" , Integer.valueOf(userId)); if (refreshTokenDao.findUnique(rQueryParam) != null ) { Map map = new HashMap(); map.put( "accessToken" , accessToken); map.put( "appKey" , oauthRequest.getClientId()); map.put( "userId" , Integer.valueOf(userId)); map.put( "createTime" , getDate()); map.put( "scope" , scope); map.put( "authorizationTime" , getDate()); refreshTokenDao.updateAccessToken(map); } else { RefreshToken rt = new RefreshToken(); rt.setApp_key(oauthRequest.getClientId()); rt.setUser_id(Integer.valueOf(userId)); rt.setAccess_token(accessToken); rt.setRefresh_token(refreshToken); rt.setCreate_time(getDate()); rt.setAuthorization_time(getDate()); rt.setExpire( "3600" ); rt.setScope(scope); rt.setAuthorization_time(getDate()); refreshTokenDao.insert(rt); } return Response.status(response.getResponseStatus()) .entity(response.getBody()).build(); } catch (OAuthProblemException e) { System.out.println(e.getDescription()); return Response.temporaryRedirect( new URI(errorPage + "?error=" + ServerErrorType.BAD_RQUEST)).build(); } } private String getDate() { SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); return sdf.format(System.currentTimeMillis()); } } |
上面的代码,处理了客户端发来的申请令牌请求,并向客户端发放访问令牌,而oltu的客户端则通过如下代码来完成这个请求令牌和解析令牌的过程:
OAuthClient client = new OAuthClient( new URLConnectionClient()); OAuthAccessTokenResponse oauthResponse = null ; oauthResponse = client.accessToken(request, OAuth.HttpMethod.POST); String token = oauthResponse.getRefreshToken(); |
如果你是第一次开发,oauth2.0的认证过程可能会让你觉得头疼,因为你首先需要对这个流程很熟悉,并且同时要看懂了oltu的代码才好理解这个开源的项目到底是怎么实现这个过程的,因此这里我不过多的粘贴代码,因为这并没有什么卵用,还是运行项目和追踪代码比较容易理解它的原理,下面是我实现的项目代码,代码写得比较简陋,不过对于跟我一样的菜鸟,还是能起到一定的帮助的~
服务端:https://git.oschina.net/honganlei/oauth-server.git
服务端授权登录页面:https://git.oschina.net/honganlei/OauthClient.git
第三方接入授权模块的例子:https://git.oschina.net/honganlei/OauthApp.git
标签:
原文地址:http://www.cnblogs.com/auh2010006/p/5637000.html