diff --git a/sa-token-demo/sa-token-demo-oauth2-client/src/main/resources/templates/index.html b/sa-token-demo/sa-token-demo-oauth2-client/src/main/resources/templates/index.html index ebaef79a..ee520f89 100644 --- a/sa-token-demo/sa-token-demo-oauth2-client/src/main/resources/templates/index.html +++ b/sa-token-demo/sa-token-demo-oauth2-client/src/main/resources/templates/index.html @@ -30,6 +30,7 @@
当前Openid:
当前Access-Token:
当前Refresh-Token:
+
当前令牌包含Scope:
当前Client-Token:
@@ -58,7 +59,7 @@ http://sa-oauth-server.com:8001/oauth2/refresh?grant_type=refresh_token&client_id={value}&client_secret={value}&refresh_token={value} - 使用 Access-Token 置换资源: 获取账号昵称、头像、性别等信息 + 使用 Access-Token 置换资源: 获取账号昵称、头像、性别等信息 (Access-Token具备userinfo权限时才可以获取成功) http://sa-oauth-server.com:8001/oauth2/userinfo?access_token={value}
@@ -202,7 +203,11 @@ data: {accessToken: accessToken}, dataType: 'json', success: function(res) { - layer.alert(JSON.stringify(res.data)); + if(res.code == 200) { + layer.alert(JSON.stringify(res.data)); + } else { + layer.alert(res.msg); + } }, error: function(xhr, type, errorThrown){ return layer.alert("异常:" + JSON.stringify(xhr)); diff --git a/sa-token-demo/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java b/sa-token-demo/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java index e40bc4d0..00b53033 100644 --- a/sa-token-demo/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java +++ b/sa-token-demo/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java @@ -76,6 +76,9 @@ public class SaOAuth2ServerController { Object loginId = SaOAuth2Util.getLoginIdByAccessToken(accessToken); System.out.println("-------- 此Access-Token对应的账号id: " + loginId); + // 校验 Access-Token 是否具有权限: userinfo + SaOAuth2Util.checkScope(accessToken, "userinfo"); + // 模拟账号信息 (真实环境需要查询数据库获取信息) Map map = new LinkedHashMap(); map.put("nickname", "shengzhang_"); diff --git a/sa-token-doc/doc/oauth2/oauth2-api.md b/sa-token-doc/doc/oauth2/oauth2-api.md index 4f94652e..2f8cdfbc 100644 --- a/sa-token-doc/doc/oauth2/oauth2-api.md +++ b/sa-token-doc/doc/oauth2/oauth2-api.md @@ -105,7 +105,35 @@ http://sa-oauth-server.com:8001/oauth2/refresh 接口返回值同章节1.2,此处不再赘述 -### 1.4、根据 Access-Token 获取相应用户的账号信息 +### 1.4、回收 Access-Token (如果需要的话) +在Access-Token过期前主动将其回收 + +``` url +http://sa-oauth-server.com:8001/oauth2/revoke + ?client_id={value} + &client_secret={value} + &access_token={value} +``` + +参数详解: + +| 参数 | 是否必填 | 说明 | +| :-------- | :-------- | :-------- | +| client_id | 是 | 应用id | +| client_secret | 是 | 应用秘钥 | +| access_token | 是 | 步骤1.2中获取到的`Access-Token`值 | + +返回值样例: +``` js +{ + "code": 200, + "msg": "ok", + "data": null +} +``` + + +### 1.5、根据 Access-Token 获取相应用户的账号信息 注:此接口为官方仓库模拟接口,正式项目中大家可以根据此样例,自定义需要的接口及参数 ``` url diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Consts.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Consts.java index f84082c6..afd8108e 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Consts.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Consts.java @@ -15,6 +15,7 @@ public class SaOAuth2Consts { public static String authorize = "/oauth2/authorize"; public static String token = "/oauth2/token"; public static String refresh = "/oauth2/refresh"; + public static String revoke = "/oauth2/revoke"; public static String client_token = "/oauth2/client_token"; public static String doLogin = "/oauth2/doLogin"; public static String doConfirm = "/oauth2/doConfirm"; @@ -33,6 +34,7 @@ public class SaOAuth2Consts { public static String state = "state"; public static String code = "code"; public static String token = "token"; + public static String access_token = "access_token"; public static String refresh_token = "refresh_token"; public static String grant_type = "grant_type"; public static String username = "username"; diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Handle.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Handle.java index 9d983f08..fda31bc0 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Handle.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Handle.java @@ -51,6 +51,11 @@ public class SaOAuth2Handle { if(req.isPath(Api.refresh) && req.isParam(Param.grant_type, GrantType.refresh_token)) { return refreshToken(req); } + + // 回收 Access-Token + if(req.isPath(Api.revoke)) { + return revokeToken(req); + } // doLogin 登录接口 if(req.isPath(Api.doLogin)) { @@ -167,10 +172,34 @@ public class SaOAuth2Handle { SaOAuth2Util.checkRefreshTokenParam(clientId, clientSecret, refreshToken); // 获取新Token返回 - Object data = SaOAuth2Util.saOAuth2Template.refreshAccessToken(refreshToken).toLineMap(); + Object data = SaOAuth2Util.refreshAccessToken(refreshToken).toLineMap(); return SaResult.data(data); } + /** + * 回收 Access-Token + * @param req 请求对象 + * @return 处理结果 + */ + public static Object revokeToken(SaRequest req) { + // 获取参数 + String clientId = req.getParamNotNull(Param.client_id); + String clientSecret = req.getParamNotNull(Param.client_secret); + String accessToken = req.getParamNotNull(Param.access_token); + + // 如果 Access-Token 不存在,直接返回 + if(SaOAuth2Util.getAccessToken(accessToken) == null) { + return SaResult.ok("access_token不存在:" + accessToken); + } + + // 校验参数 + SaOAuth2Util.checkAccessTokenParam(clientId, clientSecret, accessToken); + + // 获取新Token返回 + SaOAuth2Util.revokeAccessToken(accessToken); + return SaResult.ok(); + } + /** * doLogin 登录接口 * @param req 请求对象 diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Template.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Template.java index 12b60fc5..ea93e634 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Template.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Template.java @@ -41,7 +41,7 @@ public class SaOAuth2Template { return null; } - // ------------------- 资源获取 + // ------------------- 资源校验API /** * 根据id获取Client信息, 如果Client为空,则抛出异常 * @param clientId 应用id @@ -54,14 +54,6 @@ public class SaOAuth2Template { } return clientModel; } - /** - * 获取 access_token 所代表的LoginId - * @param accessToken access_token - * @return LoginId - */ - public Object getLoginIdByAccessToken(String accessToken) { - return checkAccessToken(accessToken).loginId; - } /** * 获取 Access-Token,如果AccessToken为空则抛出异常 * @param accessToken . @@ -82,6 +74,29 @@ public class SaOAuth2Template { SaOAuth2Exception.throwBy(ct == null, "无效:client_token" + ct); return ct; } + /** + * 获取 Access-Token 所代表的LoginId + * @param accessToken Access-Token + * @return LoginId + */ + public Object getLoginIdByAccessToken(String accessToken) { + return checkAccessToken(accessToken).loginId; + } + /** + * 校验:指定 Access-Token 是否具有指定 Scope + * @param accessToken Access-Token + * @param scopes 需要校验的权限列表 + */ + public void checkScope(String accessToken, String... scopes) { + if(scopes == null || scopes.length == 0) { + return; + } + AccessTokenModel at = checkAccessToken(accessToken); + List scopeList = SaFoxUtil.convertStringToList(at.scope); + for (String scope : scopes) { + SaOAuth2Exception.throwBy(scopeList.contains(scope) == false, "该 Access-Token 不具备 Scope:" + scope); + } + } // ------------------- generate 构建数据 /** @@ -277,7 +292,28 @@ public class SaOAuth2Template { } return url; } - + /** + * 回收 Access-Token + * @param accessToken Access-Token值 + */ + public void revokeAccessToken(String accessToken) { + + // 如果查不到任何东西, 直接返回 + AccessTokenModel at = getAccessToken(accessToken); + if(at == null) { + return; + } + + // 删除 Access-Token + deleteAccessToken(accessToken); + deleteAccessTokenIndex(at.clientId, at.accessToken); + + // 删除对应的 Refresh-Token + String refreshToken = getRefreshTokenValue(at.clientId, at.loginId); + deleteRefreshToken(refreshToken); + deleteRefreshTokenIndex(at.clientId, at.loginId); + } + // ------------------- check 数据校验 /** * 判断:指定 loginId 是否对一个 Client 授权给了指定 Scope @@ -389,6 +425,19 @@ public class SaOAuth2Template { // 返回Refresh-Token return rt; } + /** + * 校验:Access-Token、clientId、clientSecret 三者是否匹配成功 + * @param clientId 应用id + * @param clientSecret 秘钥 + * @param accessToken Access-Token + * @return SaClientModel对象 + */ + public AccessTokenModel checkAccessTokenParam(String clientId, String clientSecret, String accessToken) { + AccessTokenModel at = checkAccessToken(accessToken); + SaOAuth2Exception.throwBy(at.clientId.equals(clientId) == false, "无效client_id:" + clientId); + checkClientSecret(clientId, clientSecret); + return at; + } // ------------------- conver 数据转换 /** diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Util.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Util.java index 472b055c..068f9001 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Util.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Util.java @@ -21,7 +21,7 @@ public class SaOAuth2Util { public static SaOAuth2Template saOAuth2Template = new SaOAuth2Template(); - // ------------------- 资源获取 + // ------------------- 资源校验API /** * 根据id获取Client信息, 如果Client为空,则抛出异常 @@ -32,15 +32,6 @@ public class SaOAuth2Util { return saOAuth2Template.checkClientModel(clientId); } - /** - * 获取 access_token 所代表的LoginId - * @param accessToken access_token - * @return LoginId - */ - public static Object getLoginIdByAccessToken(String accessToken) { - return saOAuth2Template.getLoginIdByAccessToken(accessToken); - } - /** * 获取 Access-Token,如果AccessToken为空则抛出异常 * @param accessToken . @@ -58,7 +49,24 @@ public class SaOAuth2Util { public static ClientTokenModel checkClientToken(String clientToken) { return saOAuth2Template.checkClientToken(clientToken); } + + /** + * 获取 Access-Token 所代表的LoginId + * @param accessToken Access-Token + * @return LoginId + */ + public static Object getLoginIdByAccessToken(String accessToken) { + return saOAuth2Template.getLoginIdByAccessToken(accessToken); + } + /** + * 校验:指定 Access-Token 是否具有指定 Scope + * @param accessToken Access-Token + * @param scopes 需要校验的权限列表 + */ + public static void checkScope(String accessToken, String... scopes) { + saOAuth2Template.checkScope(accessToken, scopes); + } // ------------------- generate 构建数据 @@ -141,6 +149,13 @@ public class SaOAuth2Util { return saOAuth2Template.buildImplicitRedirectUri(redirectUri, token, state); } + /** + * 回收 Access-Token + * @param accessToken Access-Token值 + */ + public static void revokeAccessToken(String accessToken) { + saOAuth2Template.revokeAccessToken(accessToken); + } // ------------------- 数据校验 @@ -172,6 +187,7 @@ public class SaOAuth2Util { public static void checkRightUrl(String clientId, String url) { saOAuth2Template.checkRightUrl(clientId, url); } + /** * 校验:clientId 与 clientSecret 是否正确 * @param clientId 应用id @@ -205,6 +221,16 @@ public class SaOAuth2Util { return saOAuth2Template.checkRefreshTokenParam(clientId, clientSecret, refreshToken); } + /** + * 校验:Access-Token、clientId、clientSecret 三者是否匹配成功 + * @param clientId 应用id + * @param clientSecret 秘钥 + * @param accessToken Access-Token + * @return SaClientModel对象 + */ + public static AccessTokenModel checkAccessTokenParam(String clientId, String clientSecret, String accessToken) { + return saOAuth2Template.checkAccessTokenParam(clientId, clientSecret, accessToken); + } // ------------------- save 数据