Oltu在Jersey框架上实现oauth2.0授权模块

oltu是一个开源的oauth2.0协议的实现,本人在此开源项目的基础上进行修改,实现一个自定义的oauth2.0模块。

关于oltu的使用大家可以看这里:http://oltu.apache.org/

项目可以从这里下载:http://mirror.bit.edu.cn/apache/oltu/org.apache.oltu.oauth2/

项目中我将四种授权方式都做了实现(授权码模式、简化模式、密码模式及客户端模式),但是这里仅以授权码模式为例,服务端采用Jersey框架实现,而客户端采用spring mvc实现。

图片1

服务器端实现:为了方便开发,我将oltu的所有源码都拖进了项目中,而不是导入jar,因为很多地方可能在我所开发的项目中不适用,这样可以方便修改和跟踪代码。

其实服务端开发很简单,主要集中在两个比较主要的文件中,一个是请求授权的AuthzEndpoint.java,一个是生成令牌的TokenEndpoint.java,如图所示。

code

至于资源控制器由于我开发的项目中,资源访问控制是采用过滤器的方式,因此没有用到oltu提供的java类,两个主要类文件的代码修改如下:

AuthzEndpoint.java

    /**
     *       Copyright 2010 Newcastle University
     *
     *          http://research.ncl.ac.uk/smart/
     *
     * 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
     *
     *          http://research.ncl.ac.uk/smart/
     *
     * 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

原文地址:https://www.cnblogs.com/auh2010006/p/5637000.html