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实现。
服务器端实现:为了方便开发,我将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
Oltu在Jersey框架上实现oauth2.0授权模块的更多相关文章
- 使用DotNetOpenAuth搭建OAuth2.0授权框架
标题还是一如既往的难取. 我认为对于一个普遍问题,必有对应的一个简洁优美的解决方案.当然这也许只是我的一厢情愿,因为根据宇宙法则,所有事物总归趋于混沌,而OAuth协议就是混沌中的产物,不管是1.0. ...
- 使用微服务架构思想,设计部署OAuth2.0授权认证框架
1,授权认证与微服务架构 1.1,由不同团队合作引发的授权认证问题 去年的时候,公司开发一款新产品,但人手不够,将B/S系统的Web开发外包,外包团队使用Vue.js框架,调用我们的WebAPI,但是 ...
- API代理网关和OAuth2.0授权认证框架
API代理网关和OAuth2.0授权认证框架 https://www.cnblogs.com/bluedoctor/p/8967951.html 1,授权认证与微服务架构 1.1,由不同团队合作引发的 ...
- Spring Security实现OAuth2.0授权服务 - 基础版
一.OAuth2.0协议 1.OAuth2.0概述 OAuth2.0是一个关于授权的开放网络协议. 该协议在第三方应用与服务提供平台之间设置了一个授权层.第三方应用需要服务资源时,并不是直接使用用户帐 ...
- OAuth2.0授权
一.什么是OAuth2.0官方网站:http://oauth.net/ http://oauth.net/2/ 权威定义:OAuth is An open protocol to allow secu ...
- Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端
Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端 目录 前言 OAuth2.0简介 授权模式 (SimpleSSO示例) 使用Microsoft.Owin.Se ...
- 微信开发——OAuth2.0授权
微信公众平台最近新推出微信认证,认证后可以获得高级接口权限,其中一个是OAuth2.0网页授权,很多朋友在使用这个的时候失败了或者无法理解其内容,希望我出个教程详细讲解一下,于是便有了这篇文章. 一. ...
- 新浪微博Oauth2.0授权认证及SDK、API的使用(Android)
---------------------------------------------------------------------------------------------- [版权申明 ...
- 工作笔记—新浪微博Oauth2.0授权 获取Access Token (java)
java发送新浪微博,一下博客从注册到发布第一条微博很详细 利用java语言在eclipse下实现在新浪微博开发平台发微博:http://blog.csdn.net/michellehsiao/art ...
随机推荐
- js页面载入特效如何实现
js页面载入特效如何实现 一.总结 一句话总结:可以加选择器(里面的字符串)作为参数,这样函数就可以针对不同选择器,就很棒了. 1.特效的原理是什么? 都是通过标签的位置和样式来实现特效的. 二.js ...
- 对spring控制反转以及依赖注入的理解
一.说到依赖注入(控制反转),先要理解什么是依赖. Spring 把相互协作的关系称为依赖关系.假如 A组件调用了 B组件的方法,我们可称A组件依赖于 B组件. 二.什么是依赖注入. 在传统的程序设计 ...
- iOS开发Quzrtz2D 十:圆形图片的绘制以及加边框圆形图片的绘制
一:圆形图片的绘制 @interface ViewController () @property (weak, nonatomic) IBOutlet UIImageView *imageV; @en ...
- iOS开发Quartz2D之八:图形上下文状态栈
#import "DrawView.h" @implementation DrawView - (void)drawRect:(CGRect)rect { // Drawing c ...
- 【u015】兽径管理
[问题描述] 约翰农场的牛群希望能够在 N 个(1<=N<=200)草地之间任意移动.草地的编号由 1到N.草地之间有树林隔开.牛群希望能够选择草地间的路径,使牛群能够从任一 片草地移动到 ...
- PHP移动互联网开发笔记(7)——MySQL数据库基础回顾[1]
一.数据类型 1.整型 数据类型 存储空间 说明 取值范围 TINYINT 1字节 非常小的整数 带符号值:-128~127无符号值:0~255 SMALLINT 2字节 较小的整数 带符号值:-32 ...
- 【codeforces 757C】Felicity is Coming!
time limit per test2 seconds memory limit per test256 megabytes inputstandard input outputstandard o ...
- VNC不同用户 Oracle
VNC登录用户缺省是root,但在安装oracle时必须用oracle用户的身份登录,下面我们就以oracle为例说明如何配置VNC,从而可以使用不同的用户登录到主机.步骤描述如下: 步骤一:修 ...
- oracle11g 在azure云中使用rman进行实例迁移
1,開始备份 备份脚本rman_full_backup.sh内容例如以下: #!/bin/sh export DATE=`date +%F` export BACK_DIR='/backupdisk/ ...
- 【codeforces 546A】Soldier and Bananas
time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...