OAuth2 集成
目前很多开放平台如新浪微博开放平台都在使用提供开放 API 接口供开发者使用,随之带来了第三方应用要到开放平台进行授权的问题,OAuth 就是干这个的,OAuth2 是 OAuth 协议的下一个版本,相比 OAuth1,OAuth2 整个授权流程更简单安全了,但不兼容 OAuth1,具体可以到 OAuth2 官网 http://oauth.net/2/ 查看,OAuth2 协议规范可以参考 http://tools.ietf.org/html/rfc6749。目前有好多参考实现供选择,可以到其官网查看下载。
本文使用 Apache Oltu,其之前的名字叫 Apache Amber ,是 Java 版的参考实现。使用文档可参考 https://cwiki.apache.org/confluence/display/OLTU/Documentation。
OAuth 角色
资源拥有者(resource owner):能授权访问受保护资源的一个实体,可以是一个人,那我们称之为最终用户;如新浪微博用户 zhangsan;
资源服务器(resource server):存储受保护资源,客户端通过 access token 请求资源,资源服务器响应受保护资源给客户端;存储着用户 zhangsan 的微博等信息。
授权服务器(authorization server):成功验证资源拥有者并获取授权之后,授权服务器颁发授权令牌(Access Token)给客户端。
客户端(client):如新浪微博客户端 weico、微格等第三方应用,也可以是它自己的官方应用;其本身不存储资源,而是资源拥有者授权通过后,使用它的授权(授权令牌)访问受保护资源,然后客户端把相应的数据展示出来 / 提交到服务器。“客户端” 术语不代表任何特定实现(如应用运行在一台服务器、桌面、手机或其他设备)。
- 客户端从资源拥有者那请求授权。授权请求可以直接发给资源拥有者,或间接的通过授权服务器这种中介,后者更可取。
- 客户端收到一个授权许可,代表资源服务器提供的授权。
- 客户端使用它自己的私有证书及授权许可到授权服务器验证。
- 如果验证成功,则下发一个访问令牌。
- 客户端使用访问令牌向资源服务器请求受保护资源。
- 资源服务器会验证访问令牌的有效性,如果成功则下发受保护资源。
更多流程的解释请参考 OAuth2 的协议规范 http://tools.ietf.org/html/rfc6749。
服务器端
本文把授权服务器和资源服务器整合在一起实现。
POM 依赖
此处我们使用 apache oltu oauth2 服务端实现,需要引入 authzserver(授权服务器依赖)和 resourceserver(资源服务器依赖)。
1 | <dependency> |
其他的请参考 pom.xml。
数据字典
用户 (oauth2_user)
名称 | 类型 | 长度 | 描述 |
---|---|---|---|
id | bigint | 10 | 编号 主键 |
username | varchar | 100 | 用户名 |
password | varchar | 100 | 密码 |
salt | varchar | 50 | 盐 |
客户端 (oauth2_client)
名称 | 类型 | 长度 | 描述 |
---|---|---|---|
id | bigint | 10 | 编号 主键 |
client_name | varchar | 100 | 客户端名称 |
client_id | varchar | 100 | 客户端 id |
client_secret | varchar | 100 | 客户端安全 key |
用户表存储着认证 / 资源服务器的用户信息,即资源拥有者;比如用户名 / 密码;客户端表存储客户端的的客户端 id 及客户端安全 key;在进行授权时使用。
表及数据 SQL
具体请参考
- sql/ shiro-schema.sql (表结构)
- sql/ shiro-data.sql (初始数据)
默认用户名 / 密码是 admin/123456。
实体
具体请参考 com.github.zhangkaitao.shiro.chapter17.entity 包下的实体,此处就不列举了。
DAO
具体请参考 com.github.zhangkaitao.shiro.chapter17.dao 包下的 DAO 接口及实现。
Service
具体请参考 com.github.zhangkaitao.shiro.chapter17.service 包下的 Service 接口及实现。以下是出了基本 CRUD 之外的关键接口:
1 | public interface UserService { |
此处通过 OAuthService 实现进行 auth code 和 access token 的维护。
后端数据维护控制器
具体请参考 com.github.zhangkaitao.shiro.chapter17.web.controller 包下的 IndexController、LoginController、UserController 和 ClientController,其用于维护后端的数据,如用户及客户端数据;即相当于后台管理。
授权控制器 AuthorizeController
1 | @Controller |
如上代码的作用:
- 首先通过如
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
访问授权页面; - 该控制器首先检查 clientId 是否正确;如果错误将返回相应的错误信息;
- 然后判断用户是否登录了,如果没有登录首先到登录页面登录;
- 登录成功后生成相应的 auth code 即授权码,然后重定向到客户端地址,如
http://localhost:9080/chapter17-client/oauth2-login?code=52b1832f5dff68122f4f00ae995da0ed
;在重定向到的地址中会带上 code 参数(授权码),接着客户端可以根据授权码去换取 access token。
访问令牌控制器 AccessTokenController
1 | @RestController |
如上代码的作用:
- 首先通过如
http://localhost:8080/chapter17-server/accessToken
,POST 提交如下数据:client_id= c1ebe466-1cdc-4bd3-ab69-77c3561b9dee& client_secret= d8346ea2-6017-43ed-ad68-19c0f971738b&grant_type=authorization_code&code=828beda907066d058584f37bcfd597b6&redirect_uri=http://localhost:9080/chapter17-client/oauth2-login
访问; - 该控制器会验证 client_id、client_secret、auth code 的正确性,如果错误会返回相应的错误;
- 如果验证通过会生成并返回相应的访问令牌 access token。
资源控制器 UserInfoController
1 | @RestController |
如上代码的作用:
- 首先通过如
http://localhost:8080/chapter17-server/userInfo? access_token=828beda907066d058584f37bcfd597b6
进行访问; - 该控制器会验证 access token 的有效性;如果无效了将返回相应的错误,客户端再重新进行授权;
- 如果有效,则返回当前登录用户的用户名。
Spring 配置文件
具体请参考 resources/spring*.xml
,此处只列举 spring-config-shiro.xml 中的 shiroFilter 的 filterChainDefinitions 属性:
1 | <property name="filterChainDefinitions"> |
对于 oauth2 的几个地址 /authorize、/accessToken、/userInfo 都是匿名可访问的。
其他源码请直接下载文档查看。
服务器维护
访问 localhost:8080/chapter17-server/
,登录后进行客户端管理和用户管理。
客户端管理就是进行客户端的注册,如新浪微博的第三方应用就需要到新浪微博开发平台进行注册;用户管理就是进行如新浪微博用户的管理。
对于授权服务和资源服务的实现可以参考新浪微博开发平台的实现:
- http://open.weibo.com/wiki / 授权机制说明
- http://open.weibo.com/wiki/ 微博 API
客户端
客户端流程:如果需要登录首先跳到 oauth2 服务端进行登录授权,成功后服务端返回 auth code,然后客户端使用 auth code 去服务器端换取 access token,最好根据 access token 获取用户信息进行客户端的登录绑定。这个可以参照如很多网站的新浪微博登录功能,或其他的第三方帐号登录功能。
POM 依赖
此处我们使用 apache oltu oauth2 客户端实现。
1 | <dependency> |
其他的请参考 pom.xml。
OAuth2Token
类似于 UsernamePasswordToken 和 CasToken;用于存储 oauth2 服务端返回的 auth code。
1 | public class OAuth2Token implements AuthenticationToken { |
OAuth2AuthenticationFilter
该 filter 的作用类似于 FormAuthenticationFilter 用于 oauth2 客户端的身份验证控制;如果当前用户还没有身份验证,首先会判断 url 中是否有 code(服务端返回的 auth code),如果没有则重定向到服务端进行登录并授权,然后返回 auth code;接着 OAuth2AuthenticationFilter 会用 auth code 创建 OAuth2Token,然后提交给 Subject.login 进行登录;接着 OAuth2Realm 会根据 OAuth2Token 进行相应的登录逻辑。
1 | public class OAuth2AuthenticationFilter extends AuthenticatingFilter { |
该拦截器的作用:
- 首先判断有没有服务端返回的 error 参数,如果有则直接重定向到失败页面;
- 接着如果用户还没有身份验证,判断是否有 auth code 参数(即是不是服务端授权之后返回的),如果没有则重定向到服务端进行授权;
- 否则调用 executeLogin 进行登录,通过 auth code 创建 OAuth2Token 提交给 Subject 进行登录;
- 登录成功将回调 onLoginSuccess 方法重定向到成功页面;
- 登录失败则回调 onLoginFailure 重定向到失败页面。
OAuth2Realm
1 | public class OAuth2Realm extends AuthorizingRealm { |
此 Realm 首先只支持 OAuth2Token 类型的 Token;然后通过传入的 auth code 去换取 access token;再根据 access token 去获取用户信息(用户名),然后根据此信息创建 AuthenticationInfo;如果需要 AuthorizationInfo 信息,可以根据此处获取的用户名再根据自己的业务规则去获取。
Spring shiro 配置(spring-config-shiro.xml)
1 | <bean id="oAuth2Realm" class="com.github.zhangkaitao.shiro.chapter18.oauth2.OAuth2Realm"> |
此 OAuth2Realm 需要配置在服务端申请的 clientId 和 clientSecret;及用于根据 auth code 换取 access token 的 accessTokenUrl 地址;及用于根据 access token 换取用户信息(受保护资源)的 userInfoUrl 地址。
1 | <bean id="oAuth2AuthenticationFilter" class="com.github.zhangkaitao.shiro.chapter18.oauth2.OAuth2AuthenticationFilter"> |
此 OAuth2AuthenticationFilter 用于拦截服务端重定向回来的 auth code。
1 | <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> |
此处设置 loginUrl 为 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 拦截器拦截并进行 oauth2 客户端授权。
测试
1、首先访问 http://localhost:9080/chapter17-client/
,然后点击登录按钮进行登录,会跳到如下页面:
2、输入用户名进行登录并授权;
3、如果登录成功,服务端会重定向到客户端,即之前客户端提供的地址 http://localhost:9080/chapter17-client/oauth2-login?code=473d56015bcf576f2ca03eac1a5bcc11
,并带着 auth code 过去;
4、客户端的 OAuth2AuthenticationFilter 会收集此 auth code,并创建 OAuth2Token 提交给 Subject 进行客户端登录;
5、客户端的 Subject 会委托给 OAuth2Realm 进行身份验证;此时 OAuth2Realm 会根据 auth code 换取 access token,再根据 access token 获取受保护的用户信息;然后进行客户端登录。