oauth2.0网页授权 授权的工作原理是怎样的?足够安全吗

比特客户端
您的位置:
详解大数据
详解大数据
详解大数据
详解大数据
OAuth API密钥如何降低API安全威胁
关键字:API OAuth API 安全威胁
  API聚合平台Apigee的首席安全架构师Subra Kumaraswamy与我们讨论了API安全的最佳实践方式。
  有了API,我们就有了更多方法来防止API安全漏洞。遗憾的是,通过使用简单的几种攻击方式,们就可以轻松地破坏API。Subra Kumaraswamy是API管理系统的供应商Apigee公司的首席安全架构师,同时也是Cloud Security and Privacy(一篇发自于O'Reilly媒体上的一篇文章)的合作者。他说,为了应对黑客袭击,开发人员不得不采用多种方法来保证API的安全性。
  作为Cloud Security Alliance的创始人之一,Kumaraswamy认为,开发人员必须在API管理平台中加入一些合适的安全工具,例如速度限制和安全令牌管理。并不是为每一类应用程序定制一种防护,Kumaraswamy建议,开发人员使用API来进行接下来的工作,同时也可以支持全球政策的实施。
  这本次采访中,Kumaraswamy向我们提供了几点关于创建API强安全性的几条建议,其中包括的使用、提供API密钥、身份验证机制、AWS API安全性等等。
  在建立API过程中,OAuth框架处于什么状态?这种方法是否仍可行呢?
  Subra Kumaraswamy:在API安全建设的过程中,OAuth [Open Authorization]框架是大家非常熟悉的一种授权框架。在向Web 应用程序、桌面应用程序、移动应用程序和提供特定授权功能的同时,OAuth2.0也非常重视客户端开发的简易性。然而,我们需要注意的是,OAuth是一种委托的授权协议,而不是真正的授权协议。因此,超越OAuth标准的OpenID Connect被列为开发人员可选方案之中。除了想要对应用程序授以访问受保护的用户代表资源外,这些开发人员还想要以编程的方式来验证用户身份。
  在API知道哪类客户发送请求的情况下,如何选择并使用最合适的认证方法来登陆本地应用程序?
  Kumaraswamy:当本地应用程序中加入终端客户认证功能后,开发人员应用采用三脚流OAuth2.0。在此模型中,以之前成功的用户认证为例,本地应用程序的授权上含有一个短期的访问令牌和长期刷新令牌。访问令牌向API服务提供受保护资源的访问权,而使用刷新令牌会更新短期存在的访问令牌。这种方法可以降低登录冲突,使用户与移动应用程序保持连续的互动。如果出现令牌泄露的事情,开发人员可以通过使用安全阀而轻松地撤销访问令牌和刷新令牌。
  开发人员在提供API密钥时会犯哪些共同的错误?如何避免这些错误?
  Kumaraswamy:在访问受保护资源之前,API密钥需通过API网关的验证。一类常见的错误是,在提供密钥时,会赋予平台操作一些应用程序不需要的特权,而这种特权与API产生冲突。例如,如果你向软件开发人员暴露了API支付验证,那么API密钥应仅限于支付验证时使用,而不应该被赋予修改支付明细的特权。
  另外一类常见错误是,在密钥没有配额的情况下就可以无限访问API。这会引起拒绝服务攻击,同时通过机器人可以收集到重要数据。使用散列法和随机因子来保护API密钥证书是一个标准惯例,例如SHA-256或者其他更高的版本。
  当创建API安全时,开发人员还应注意哪些问题?
  Kumaraswamy:开发人员应该以一种API威胁模型开始,在应用模型的基础上试图理解威胁的运作原理,例如内部API和外部API。他们必须知道有些因素会对API威胁,例如可信的开发人员、自行注册的开发人员以及其合作者。掌握这些信息后,API设计者和开发人员必须建立一些合适的安全措施以应对各种威胁。
  例如,我们可以看到,API威胁模型中,不可信的开发人员通过网络能够看到一些敏感的数据。在此,无论运输层安全性中的敏感数据处于运输状态还是静止状态,开发人员都要使用加密技术对实施保护。
  开发人员应该使标准应用程序安全性达到最佳状态,例如,通过使用动态和静态的编码分析工具来测试API中是否存在标准 OWASP Top 10缺陷。最后,开发人员应该在安全的位置记录API活动,对任何非正常行为进行定期审核,例如API暴利破解攻击。
  使用AWS API后,安全方面的优点和缺点分别有哪些?
  Kumaraswamy: AWS API和认证机制是亚马逊所独有的,对管理AWS基础设施即服务资源起到一定的限制作用。使用署名的请求模型(一种基于署名的认证模型HMAC即基于哈希的消息验证代码)对AWS(具象状态传输)API也可以起到保护。HMAC将哈希功能与共享在客户与AWS之间的对称密钥结合在一起使用。AWS客户要在请求信息(例如AWS服务、范围、活动和时间标记)以及共享于开发人员之间的AWS私有密钥基础上签署每一份API请求。AWS API安全机制对用户身份进行核查、对REST调用具有完整的保护性,同时免于再次遭受攻击。从某种方式来说,AWS认证机制与双脚OAuth(客户认证授权)有异曲同工之处。
相关文章:
[ 责任编辑:小石潭记 ]
据IDC预测,平板电脑行业将在…
甲骨文的云战略已经完成第一阶段…
软件信息化周刊
比特软件信息化周刊提供以数据库、操作系统和管理软件为重点的全面软件信息化产业热点、应用方案推荐、实用技巧分享等。以最新的软件资讯,最新的软件技巧,最新的软件与服务业内动态来为IT用户找到软捷径。
商务办公周刊
比特商务周刊是一个及行业资讯、深度分析、企业导购等为一体的综合性周刊。其中,与中国计量科学研究院合力打造的比特实验室可以为商业用户提供最权威的采购指南。是企业用户不可缺少的智选周刊!
比特网络周刊向企业网管员以及网络技术和产品使用者提供关于网络产业动态、技术热点、组网、建网、网络管理、网络运维等最新技术和实用技巧,帮助网管答疑解惑,成为网管好帮手。
服务器周刊
比特服务器周刊作为比特网的重点频道之一,主要关注x86服务器,RISC架构服务器以及高性能计算机行业的产品及发展动态。通过最独到的编辑观点和业界动态分析,让您第一时间了解服务器行业的趋势。
比特存储周刊长期以来,为读者提供企业存储领域高质量的原创内容,及时、全面的资讯、技术、方案以及案例文章,力求成为业界领先的存储媒体。比特存储周刊始终致力于用户的企业信息化建设、存储业务、数据保护与容灾构建以及数据管理部署等方面服务。
比特安全周刊通过专业的信息安全内容建设,为企业级用户打造最具商业价值的信息沟通平台,并为安全厂商提供多层面、多维度的媒体宣传手段。与其他同类网站信息安全内容相比,比特安全周刊运作模式更加独立,对信息安全界的动态新闻更新更快。
新闻中心热点推荐
新闻中心以独特视角精选一周内最具影响力的行业重大事件或圈内精彩故事,为企业级用户打造重点突出,可读性强,商业价值高的信息共享平台;同时为互联网、IT业界及通信厂商提供一条精准快捷,渗透力强,覆盖面广的媒体传播途径。
云计算周刊
比特云计算周刊关注云计算产业热点技术应用与趋势发展,全方位报道云计算领域最新动态。为用户与企业架设起沟通交流平台。包括IaaS、PaaS、SaaS各种不同的服务类型以及相关的安全与管理内容介绍。
CIO俱乐部周刊
比特CIO俱乐部周刊以大量高端CIO沙龙或专题研讨会以及对明星CIO的深入采访为依托,汇聚中国500强CIO的集体智慧。旨为中国杰出的CIO提供一个良好的互融互通 、促进交流的平台,并持续提供丰富的资讯和服务,探讨信息化建设,推动中国信息化发展引领CIO未来职业发展。
IT专家新闻邮件长期以来,以定向、分众、整合的商业模式,为企业IT专业人士以及IT系统采购决策者提供高质量的原创内容,包括IT新闻、评论、专家答疑、技巧和白皮书。此外,IT专家网还为读者提供包括咨询、社区、论坛、线下会议、读者沙龙等多种服务。
X周刊是一份IT人的技术娱乐周刊,给用户实时传递I最新T资讯、IT段子、技术技巧、畅销书籍,同时用户还能参与我们推荐的互动游戏,给广大的IT技术人士忙碌工作之余带来轻松休闲一刻。
微信扫一扫
关注ChinabyteOAuth2集成——《跟我学Shiro》 - 推酷
OAuth2集成——《跟我学Shiro》
目前很多开放平台如新浪微博开放平台都在使用提供开放
接口供开发者使用,随之带来了第三方应用要到开放平台进行授权的问题,
就是干这个的,
协议的下一个版本,相比
整个授权流程更简单安全了,但不兼容
,具体可以到
协议规范可以参考
。目前有好多参考实现供选择,可以到其官网查看下载。
,其之前的名字叫
Apache Amber&
版的参考实现。使用文档可参考
资源拥有者(
resource owner
:能授权访问受保护资源的一个实体,可以是一个人,那我们称之为最终用户;如新浪微博用户
资源服务器(
resource server
:存储受保护资源,客户端通过
access token
请求资源,资源服务器响应受保护资源给客户端;存储着用户
的微博等信息。
授权服务器(
authorization server
:成功验证资源拥有者并获取授权之后,授权服务器颁发授权令牌(
Access Token
)给客户端。
:如新浪微博客户端
、微格等第三方应用,也可以是它自己的官方应用;其本身不存储资源,而是资源拥有者授权通过后,使用它的授权(授权令牌)访问受保护资源,然后客户端把相应的数据展示出来
提交到服务器。“客户端”术语不代表任何特定实现(如应用运行在一台服务器、桌面、手机或其他设备)。
、客户端从资源拥有者那请求授权。授权请求可以直接发给资源拥有者,或间接的通过授权服务器这种中介,
后者更可取
、客户端收到一个授权许可,代表资源服务器提供的授权。
、客户端使用它自己的私有证书及授权许可到授权服务器验证。
、如果验证成功,则下发一个访问令牌。
、客户端使用访问令牌向资源服务器请求受保护资源。
、资源服务器会验证访问令牌的有效性,如果成功则下发受保护资源。
更多流程的解释请参考
的协议规范
本文把授权服务器和资源服务器整合在一起实现。
此处我们使用
apache oltu oauth2
服务端实现,需要引入
authzserver
(授权服务器依赖)和
resourceserver
(资源服务器依赖)。&
&dependency&
&groupId&org.apache.oltu.oauth2&/groupId&
&artifactId&org.apache.oltu.oauth2.authzserver&/artifactId&
&version&0.31&/version&
&/dependency&
&dependency&
&groupId&org.apache.oltu.oauth2&/groupId&
&artifactId&org.apache.oltu.oauth2.resourceserver&/artifactId&
&version&0.31&/version&
&/dependency&
其他的请参考
(oauth2_user)
(oauth2_client)
client_name
客户端名称
client_secret
客户端安全
用户表存储着认证
资源服务器的用户信息,即资源拥有者;比如用户名
密码;客户端表存储客户端的的客户端
及客户端安全
;在进行授权时使用。
具体请参考
sql/ shiro-schema.sql&
(表结构)
sql/ shiro-data.sql&&
(初始数据)
默认用户名
admin/123456
具体请参考
com.github.zhangkaitao.shiro.chapter17.entity
包下的实体,此处就不列举了。
具体请参考
com.github.zhangkaitao.shiro.chapter17.dao
接口及实现。
具体请参考
com.github.zhangkaitao.shiro.chapter17.service
接口及实现。以下是出了基本
之外的关键接口:&
public interface UserService {
public User createUser(User user);// 创建用户
public User updateUser(User user);// 更新用户
public void deleteUser(Long userId);// 删除用户
public void changePassword(Long userId, String newPassword); //修改密码
User findOne(Long userId);// 根据id查找用户
List&User& findAll();// 得到所有用户
public User findByUsername(String username);// 根据用户名查找用户
public interface ClientService {
public Client createClient(Client client);// 创建客户端
public Client updateClient(Client client);// 更新客户端
public void deleteClient(Long clientId);// 删除客户端
Client findOne(Long clientId);// 根据id查找客户端
List&Client& findAll();// 查找所有
Client findByClientId(String clientId);// 根据客户端id查找客户端
Client findByClientSecret(String clientSecret);//根据客户端安全KEY查找客户端
public interface OAuthService {
public void addAuthCode(String authCode, String username);// 添加 auth code
public void addAccessToken(String accessToken, String username); // 添加 access token
boolean checkAuthCode(String authCode); // 验证auth code是否有效
boolean checkAccessToken(String accessToken); // 验证access token是否有效
String getUsernameByAuthCode(String authCode);// 根据auth code获取用户名
String getUsernameByAccessToken(String accessToken);// 根据access token获取用户名
long getExpireIn();//auth code / access token 过期时间
public boolean checkClientId(String clientId);// 检查客户端id是否存在
public boolean checkClientSecret(String clientSecret);// 坚持客户端安全KEY是否存在
OAuthService
access token
后端数据维护控制器
具体请参考
com.github.zhangkaitao.shiro.chapter17.web.controller
IndexController
LoginController
UserController
ClientController
,其用于维护后端的数据,如用户及客户端数据;即相当于后台管理。
授权控制器
AuthorizeController&
@Controller
public class AuthorizeController {
@Autowired
private OAuthService oAuthS
@Autowired
private ClientService clientS
@RequestMapping(&/authorize&)
public Object authorize(Model model,
HttpServletRequest request)
throws URISyntaxException, OAuthSystemException {
//构建OAuth 授权请求
OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(request);
//检查传入的客户端id是否正确
if (!oAuthService.checkClientId(oauthRequest.getClientId())) {
OAuthResponse response = OAuthASResponse
.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
.setError(OAuthError.TokenResponse.INVALID_CLIENT)
.setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION)
.buildJSONMessage();
return new ResponseEntity(
response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
Subject subject = SecurityUtils.getSubject();
//如果用户没有登录,跳转到登陆页面
if(!subject.isAuthenticated()) {
if(!login(subject, request)) {//登录失败时跳转到登陆页面
model.addAttribute(&client&,
clientService.findByClientId(oauthRequest.getClientId()));
return &oauth2login&;
String username = (String)subject.getPrincipal();
//生成授权码
String authorizationCode =
//responseType目前仅支持CODE,另外还有TOKEN
String responseType = oauthRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE);
if (responseType.equals(ResponseType.CODE.toString())) {
OAuthIssuerImpl oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
authorizationCode = oauthIssuerImpl.authorizationCode();
oAuthService.addAuthCode(authorizationCode, username);
//进行OAuth响应构建
OAuthASResponse.OAuthAuthorizationResponseBuilder builder =
OAuthASResponse.authorizationResponse(request,
HttpServletResponse.SC_FOUND);
//设置授权码
builder.setCode(authorizationCode);
//得到到客户端重定向地址
String redirectURI = oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI);
//构建响应
final OAuthResponse response = builder.location(redirectURI).buildQueryMessage();
//根据OAuthResponse返回ResponseEntity响应
HttpHeaders headers = new HttpHeaders();
headers.setLocation(new URI(response.getLocationUri()));
return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus()));
} catch (OAuthProblemException e) {
//出错处理
String redirectUri = e.getRedirectUri();
if (OAuthUtils.isEmpty(redirectUri)) {
//告诉客户端没有传入redirectUri直接报错
return new ResponseEntity(
&OAuth callback url needs to be provided by client!!!&, HttpStatus.NOT_FOUND);
//返回错误消息(如?error=)
final OAuthResponse response =
OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND)
.error(e).location(redirectUri).buildQueryMessage();
HttpHeaders headers = new HttpHeaders();
headers.setLocation(new URI(response.getLocationUri()));
return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus()));
private boolean login(Subject subject, HttpServletRequest request) {
if(&get&.equalsIgnoreCase(request.getMethod())) {
String username = request.getParameter(&username&);
String password = request.getParameter(&password&);
if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
subject.login(token);
} catch (Exception e) {
request.setAttribute(&error&, &登录失败:& + e.getClass().getName());
如上代码的作用:
、首先通过如
http://localhost:8080/chapter17-server/authorize
?client_id=c1ebe466-1cdc-4bd3-ab69-77c3561b9dee&response_type=code&redirect_uri=http://localhost:9080/chapter17-client/oauth2-login
访问授权页面;
、该控制器首先检查
是否正确;如果错误将返回相应的错误信息;
、然后判断用户是否登录了,如果没有登录首先到登录页面登录;
、登录成功后生成相应的
即授权码,然后重定向到客户端地址,如
http://localhost:9080/chapter17-client/oauth2-login?code=52b1832f5dffae995da0ed
;在重定向到的地址中会带上
参数(授权码),接着客户端可以根据授权码去换取
access token
访问令牌控制器
AccessTokenController&
@RestController
public class AccessTokenController {
@Autowired
private OAuthService oAuthS
@Autowired
private UserService userS
@RequestMapping(&/accessToken&)
public HttpEntity token(HttpServletRequest request)
throws URISyntaxException, OAuthSystemException {
//构建OAuth请求
OAuthTokenRequest oauthRequest = new OAuthTokenRequest(request);
//检查提交的客户端id是否正确
if (!oAuthService.checkClientId(oauthRequest.getClientId())) {
OAuthResponse response = OAuthASResponse
.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
.setError(OAuthError.TokenResponse.INVALID_CLIENT)
.setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION)
.buildJSONMessage();
return new ResponseEntity(
response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
// 检查客户端安全KEY是否正确
if (!oAuthService.checkClientSecret(oauthRequest.getClientSecret())) {
OAuthResponse response = OAuthASResponse
.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
.setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)
.setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION)
.buildJSONMessage();
return new ResponseEntity(
response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
String authCode = oauthRequest.getParam(OAuth.OAUTH_CODE);
// 检查验证类型,此处只检查AUTHORIZATION_CODE类型,其他的还有PASSWORD或REFRESH_TOKEN
if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(
GrantType.AUTHORIZATION_CODE.toString())) {
if (!oAuthService.checkAuthCode(authCode)) {
OAuthResponse response = OAuthASResponse
.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
.setError(OAuthError.TokenResponse.INVALID_GRANT)
.setErrorDescription(&错误的授权码&)
.buildJSONMessage();
return new ResponseEntity(
response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
//生成Access Token
OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
final String accessToken = oauthIssuerImpl.accessToken();
oAuthService.addAccessToken(accessToken,
oAuthService.getUsernameByAuthCode(authCode));
//生成OAuth响应
OAuthResponse response = OAuthASResponse
.tokenResponse(HttpServletResponse.SC_OK)
.setAccessToken(accessToken)
.setExpiresIn(String.valueOf(oAuthService.getExpireIn()))
.buildJSONMessage();
//根据OAuthResponse生成ResponseEntity
return new ResponseEntity(
response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
} catch (OAuthProblemException e) {
//构建错误响应
OAuthResponse res = OAuthASResponse
.errorResponse(HttpServletResponse.SC_BAD_REQUEST).error(e)
.buildJSONMessage();
return new ResponseEntity(res.getBody(), HttpStatus.valueOf(res.getResponseStatus()));
如上代码的作用:
、首先通过如
http://localhost:8080/chapter17-server/accessToken
提交如下数据:
client_id= c1ebe466-1cdc-4bd3-ab69-77c3561b9dee& client_secret= d7-43ed-ad68-19c0f971738b&grant_type=authorization_code&code=828beda584f37bcfd597b6&redirect_uri=http://localhost:9080/chapter17-client/oauth2-login
、该控制器会验证
client_secret
的正确性,如果错误会返回相应的错误;
、如果验证通过会生成并返回相应的访问令牌
access token
资源控制器
UserInfoController&
@RestController
public class UserInfoController {
@Autowired
private OAuthService oAuthS
@RequestMapping(&/userInfo&)
public HttpEntity userInfo(HttpServletRequest request) throws OAuthSystemException {
//构建OAuth资源请求
OAuthAccessResourceRequest oauthRequest =
new OAuthAccessResourceRequest(request, ParameterStyle.QUERY);
//获取Access Token
String accessToken = oauthRequest.getAccessToken();
//验证Access Token
if (!oAuthService.checkAccessToken(accessToken)) {
// 如果不存在/过期了,返回未验证错误,需重新验证
OAuthResponse oauthResponse = OAuthRSResponse
.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
.setRealm(Constants.RESOURCE_SERVER_NAME)
.setError(OAuthError.ResourceResponse.INVALID_TOKEN)
.buildHeaderMessage();
HttpHeaders headers = new HttpHeaders();
headers.add(OAuth.HeaderType.WWW_AUTHENTICATE,
oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE));
return new ResponseEntity(headers, HttpStatus.UNAUTHORIZED);
//返回用户名
String username = oAuthService.getUsernameByAccessToken(accessToken);
return new ResponseEntity(username, HttpStatus.OK);
} catch (OAuthProblemException e) {
//检查是否设置了错误码
String errorCode = e.getError();
if (OAuthUtils.isEmpty(errorCode)) {
OAuthResponse oauthResponse = OAuthRSResponse
.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
.setRealm(Constants.RESOURCE_SERVER_NAME)
.buildHeaderMessage();
HttpHeaders headers = new HttpHeaders();
headers.add(OAuth.HeaderType.WWW_AUTHENTICATE,
oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE));
return new ResponseEntity(headers, HttpStatus.UNAUTHORIZED);
OAuthResponse oauthResponse = OAuthRSResponse
.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
.setRealm(Constants.RESOURCE_SERVER_NAME)
.setError(e.getError())
.setErrorDescription(e.getDescription())
.setErrorUri(e.getUri())
.buildHeaderMessage();
HttpHeaders headers = new HttpHeaders();
headers.add(OAuth.HeaderType.WWW_AUTHENTICATE, 、
oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE));
return new ResponseEntity(HttpStatus.BAD_REQUEST);
如上代码的作用:
、首先通过如
http://localhost:8080/chapter17-server/userInfo? access_token=828beda584f37bcfd597b6
进行访问;
、该控制器会验证
access token
的有效性;如果无效了将返回相应的错误,客户端再重新进行授权;
、如果有效,则返回当前登录用户的用户名。
具体请参考resources/spring*.xml,此处只列举spring-config-shiro.xml中的shiroFilter的filterChainDefinitions属性:&&
&property name=&filterChainDefinitions&&
/login = authc
/logout = logout
/authorize=anon
/accessToken=anon
/userInfo=anon
/** = user
&/property&
的几个地址
/authorize
/accessToken
都是匿名可访问的。
其他源码请直接下载文档查看。
服务器维护
localhost:8080/chapter17-server/
,登录后进行客户端管理和用户管理。
客户端管理就是进行客户端的注册,如新浪微博的第三方应用就需要到新浪微博开发平台进行注册;用户管理就是进行如新浪微博用户的管理。
对于授权服务和资源服务的实现可以参考新浪微博开发平台的实现:
客户端流程:如果需要登录首先跳到
服务端进行登录授权,成功后服务端返回
,然后客户端使用
去服务器端换取
access token
,最好根据
access token
获取用户信息进行客户端的登录绑定。这个可以参照如很多网站的新浪微博登录功能,或其他的第三方帐号登录功能。
此处我们使用apache oltu oauth2客户端实现。& &&&
&dependency&
&groupId&org.apache.oltu.oauth2&/groupId&
&artifactId&org.apache.oltu.oauth2.client&/artifactId&
&version&0.31&/version&
&/dependency&
其他的请参考
OAuth2Token
类似于UsernamePasswordToken和CasToken;用于存储oauth2服务端返回的auth code。&&
public class OAuth2Token implements AuthenticationToken {
private String authC
public OAuth2Token(String authCode) {
this.authCode = authC
//省略getter/setter
OAuth2AuthenticationFilter
该filter的作用类似于FormAuthenticationFilter用于oauth2客户端的身份验证控制;如果当前用户还没有身份验证,首先会判断url中是否有code(服务端返回的auth code),如果没有则重定向到服务端进行登录并授权,然后返回auth code;接着OAuth2AuthenticationFilter会用auth code创建OAuth2Token,然后提交给Subject.login进行登录;接着OAuth2Realm会根据OAuth2Token进行相应的登录逻辑。&
public class OAuth2AuthenticationFilter extends AuthenticatingFilter {
//oauth2 authc code参数名
private String authcCodeParam = &code&;
//客户端id
private String clientId;
//服务器端登录成功/失败后重定向到的客户端地址
private String redirectU
//oauth2服务器响应类型
private String responseType = &code&;
private String failureU
//省略setter
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpRequest = (HttpServletRequest)
String code = httpRequest.getParameter(authcCodeParam);
return new OAuth2Token(code);
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
String error = request.getParameter(&error&);
String errorDescription = request.getParameter(&error_description&);
if(!StringUtils.isEmpty(error)) {//如果服务端返回了错误
WebUtils.issueRedirect(request, response, failureUrl + &?error=& + error + &error_description=& + errorDescription);
Subject subject = getSubject(request, response);
if(!subject.isAuthenticated()) {
if(StringUtils.isEmpty(request.getParameter(authcCodeParam))) {
//如果用户没有身份验证,且没有auth code,则重定向到服务端授权
saveRequestAndRedirectToLogin(request, response);
//执行父类里的登录逻辑,调用Subject.login登录
return executeLogin(request, response);
//登录成功后的回调方法 重定向到成功页面
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
ServletResponse response) throws Exception {
issueSuccessRedirect(request, response);
//登录失败后的回调
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ae, ServletRequest request,
ServletResponse response) {
Subject subject = getSubject(request, response);
if (subject.isAuthenticated() || subject.isRemembered()) {
try { //如果身份验证成功了 则也重定向到成功页面
issueSuccessRedirect(request, response);
} catch (Exception e) {
e.printStackTrace();
try { //登录失败时重定向到失败页面
WebUtils.issueRedirect(request, response, failureUrl);
} catch (IOException e) {
e.printStackTrace();
该拦截器的作用:
、首先判断有没有服务端返回的
参数,如果有则直接重定向到失败页面;
、接着如果用户还没有身份验证,判断是否有
参数(即是不是服务端授权之后返回的),如果没有则重定向到服务端进行授权;
、否则调用
executeLogin
进行登录,通过
OAuth2Token
进行登录;
、登录成功将回调
onLoginSuccess
方法重定向到成功页面;
、登录失败则回调
onLoginFailure
重定向到失败页面。
OAuth2Realm
public class OAuth2Realm extends AuthorizingRealm {
private String clientId;
private String clientS
private String accessTokenU
private String userInfoU
private String redirectU
//省略setter
public boolean supports(AuthenticationToken token) {
return token instanceof OAuth2T //表示此Realm只支持OAuth2Token类型
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
return authorizationI
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
OAuth2Token oAuth2Token = (OAuth2Token)
String code = oAuth2Token.getAuthCode(); //获取 auth code
String username = extractUsername(code); // 提取用户名
SimpleAuthenticationInfo authenticationInfo =
new SimpleAuthenticationInfo(username, code, getName());
return authenticationI
private String extractUsername(String code) {
OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
OAuthClientRequest accessTokenRequest = OAuthClientRequest
.tokenLocation(accessTokenUrl)
.setGrantType(GrantType.AUTHORIZATION_CODE)
.setClientId(clientId).setClientSecret(clientSecret)
.setCode(code).setRedirectURI(redirectUrl)
.buildQueryMessage();
//获取access token
OAuthAccessTokenResponse oAuthResponse =
oAuthClient.accessToken(accessTokenRequest, OAuth.HttpMethod.POST);
String accessToken = oAuthResponse.getAccessToken();
Long expiresIn = oAuthResponse.getExpiresIn();
//获取user info
OAuthClientRequest userInfoRequest =
new OAuthBearerClientRequest(userInfoUrl)
.setAccessToken(accessToken).buildQueryMessage();
OAuthResourceResponse resourceResponse = oAuthClient.resource(
userInfoRequest, OAuth.HttpMethod.GET, OAuthResourceResponse.class);
String username = resourceResponse.getBody();
} catch (Exception e) {
throw new OAuth2AuthenticationException(e);
首先只支持
OAuth2Token
;然后通过传入的
access token
access token
去获取用户信息(用户名),然后根据此信息创建
AuthenticationInfo
;如果需要
AuthorizationInfo
信息,可以根据此处获取的用户名再根据自己的业务规则去获取。
Spring shiro
spring-config-shiro.xml
&bean id=&oAuth2Realm&
class=&com.github.zhangkaitao.shiro.chapter18.oauth2.OAuth2Realm&&
&property name=&cachingEnabled& value=&true&/&
&property name=&authenticationCachingEnabled& value=&true&/&
&property name=&authenticationCacheName& value=&authenticationCache&/&
&property name=&authorizationCachingEnabled& value=&true&/&
&property name=&authorizationCacheName& value=&authorizationCache&/&
&property name=&clientId& value=&c1ebe466-1cdc-4bd3-ab69-77c3561b9dee&/&
&property name=&clientSecret& value=&d7-43ed-ad68-19c0f971738b&/&
&property name=&accessTokenUrl&
value=&http://localhost:8080/chapter17-server/accessToken&/&
&property name=&userInfoUrl& value=&http://localhost:8080/chapter17-server/userInfo&/&
&property name=&redirectUrl& value=&http://localhost:9080/chapter17-client/oauth2-login&/&
OAuth2Realm
需要配置在服务端申请的
clientSecret
;及用于根据
access token
accessTokenUrl
地址;及用于根据
access token
换取用户信息(受保护资源)的
userInfoUrl
&bean id=&oAuth2AuthenticationFilter&
class=&com.github.zhangkaitao.shiro.chapter18.oauth2.OAuth2AuthenticationFilter&&
&property name=&authcCodeParam& value=&code&/&
&property name=&failureUrl& value=&/oauth2Failure.jsp&/&
此OAuth2AuthenticationFilter用于拦截服务端重定向回来的auth code。&&
&bean id=&shiroFilter& class=&org.apache.shiro.spring.web.ShiroFilterFactoryBean&&
&property name=&securityManager& ref=&securityManager&/&
&property name=&loginUrl& value=&http://localhost:8080/chapter17-server/authorize?client_id=c1ebe466-1cdc-4bd3-ab69-77c3561b9dee&response_type=code&redirect_uri=http://localhost:9080/chapter17-client/oauth2-login&/&
&property name=&successUrl& value=&/&/&
&property name=&filters&&
&util:map&
&entry key=&oauth2Authc& value-ref=&oAuth2AuthenticationFilter&/&
&/util:map&
&/property&
&property name=&filterChainDefinitions&&
/oauth2Failure.jsp = anon
/oauth2-login = oauth2Authc
/logout = logout
/** = user
&/property&
http://localhost:8080/chapter17-server/authorize
?client_id=c1ebe466-1cdc-4bd3-ab69-77c3561b9dee&response_type=code&redirect_uri=http://localhost:9080/chapter17-client/oauth2-login&
;其会自动设置到所有的
AccessControlFilter
oAuth2AuthenticationFilter
/oauth2-login = oauth2Authc
/oauth2-login
oauth2Authc
拦截器拦截并进行
客户端授权。
、首先访问
,然后点击登录按钮进行登录,会跳到如下页面:&
、输入用户名进行登录并授权;
、如果登录成功,服务端会重定向到客户端,即之前客户端提供的地址
http://localhost:9080/chapter17-client/oauth2-login?code=473d56015bcf576f2ca03eac1a5bcc11
、客户端的
OAuth2AuthenticationFilter
OAuth2Token
进行客户端登录;
、客户端的
OAuth2Realm
进行身份验证;此时
OAuth2Realm
access token
access token
获取受保护的用户信息;然后进行客户端登录。
的集成就完成了,此处的服务端和客户端相对比较简单,没有进行一些异常检测,请参考如新浪微博进行相应
及异常错误码的设计。&&&
示例源代码:
;可加群 &探讨Spring/Shiro技术。
请你欣赏春天美景&
已发表评论数()
&&登&&&录&&
已收藏到推刊!
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见

我要回帖

更多关于 oauth原理 的文章

 

随机推荐