• 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

  • 相关阅读:
    小试牛刀,建立jsp网页与导出war包
    IDEA 官方背景与修改jsp模板以及字体大小
    类库日期和jsp导包
    eclipse配置
    mysql绿色版下载及应用
    创建一个学生信息表,与页面分离
    Tomcat配置
    c#简单加密和对称加密
    04面向对象基础
    ADO.NET复习——自己编写SqlHelper类
  • 原文地址:https://www.cnblogs.com/auh2010006/p/5637000.html
Copyright © 2020-2023  润新知