diff --git a/README.md b/README.md index 49b8dc5a22b1d1654775be4a389438887afbfc7a..97d57983c75317c3c3410dc0b8aca965ff222c8b 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,6 @@ - ------------------------------------------------------------------------------- @@ -106,6 +105,7 @@ authRequest.login("code"); | | [AuthDouyinRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthDouyinRequest.java) | 参考文档 | | | [AuthLinkedinRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthLinkedinRequest.java) | 参考文档 | | | [AuthCsdnRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthCsdnRequest.java) | 无 | +| | [AuthMicrosoftRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java) | 参考文档 | _请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经下线。如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了_ @@ -208,7 +208,7 @@ _请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经 | | | **QQ群** - + - JustAuth交流群 (230017570):专业交流该项目 - 开源总群 (190886500):各个开源项目的都有,也有博客建设等方面的朋友。(注意,该群需付费进入,防止发垃圾广告、垃圾推广等人士) @@ -216,6 +216,6 @@ _请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经 ## 请喝咖啡 -| 支付宝 | 微信 | -| :------------: | :------------: | +| 支付宝 | 微信 | +| :------------: | :------------: | | | | \ No newline at end of file diff --git a/src/main/java/me/zhyd/oauth/authorization/Authorization.java b/src/main/java/me/zhyd/oauth/authorization/Authorization.java index 0db3cb9d380ef0e9460c945d53fb2711fbe3851f..f8508c42670c6576ce62d9f59428a66139671af8 100644 --- a/src/main/java/me/zhyd/oauth/authorization/Authorization.java +++ b/src/main/java/me/zhyd/oauth/authorization/Authorization.java @@ -11,5 +11,11 @@ import me.zhyd.oauth.config.AuthConfig; */ public interface Authorization { + /** + * 获取授权页面地址 + * + * @param config 授权基础配置 + * @return 授权页面地址 + */ String getAuthorizeUrl(AuthConfig config); } diff --git a/src/main/java/me/zhyd/oauth/authorization/AuthorizationFactory.java b/src/main/java/me/zhyd/oauth/authorization/AuthorizationFactory.java index 83d33d8d94c690c95dc7e5115ccbc76c907e1e92..380c7984b3a3262819b4812c774be97e9edb8185 100644 --- a/src/main/java/me/zhyd/oauth/authorization/AuthorizationFactory.java +++ b/src/main/java/me/zhyd/oauth/authorization/AuthorizationFactory.java @@ -69,6 +69,7 @@ public class AuthorizationFactory { AuthorizationFactory.register(AuthSource.FACEBOOK, new FacebookAuthorization()); AuthorizationFactory.register(AuthSource.DOUYIN, new DouyinAuthorization()); AuthorizationFactory.register(AuthSource.LINKEDIN, new LinkedinAuthorization()); + AuthorizationFactory.register(AuthSource.MICROSOFT, new MicrosoftAuthorization()); loader = true; } diff --git a/src/main/java/me/zhyd/oauth/authorization/MicrosoftAuthorization.java b/src/main/java/me/zhyd/oauth/authorization/MicrosoftAuthorization.java new file mode 100644 index 0000000000000000000000000000000000000000..4dff7244fc6f9f9783926175f04be5cfcd221814 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/authorization/MicrosoftAuthorization.java @@ -0,0 +1,19 @@ +package me.zhyd.oauth.authorization; + +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.utils.UrlBuilder; + +/** + * 微软授权 + * + * @author yangkai.shen (https://xkcoding.com) + * @version 1.5 + * @since 1.5 + */ +public class MicrosoftAuthorization implements Authorization { + + @Override + public String getAuthorizeUrl(AuthConfig config) { + return UrlBuilder.getMicrosoftAuthorizeUrl(config.getClientId(), config.getRedirectUri()); + } +} diff --git a/src/main/java/me/zhyd/oauth/consts/ApiUrl.java b/src/main/java/me/zhyd/oauth/consts/ApiUrl.java index 77b617feb380c82daa2b900b50028a1892273954..846ce9306ce0793d98660b14e4c4fd1aad67b6dd 100644 --- a/src/main/java/me/zhyd/oauth/consts/ApiUrl.java +++ b/src/main/java/me/zhyd/oauth/consts/ApiUrl.java @@ -503,6 +503,35 @@ public enum ApiUrl { public String refresh() { return "https://www.linkedin.com/oauth/v2/accessToken"; } + }, + /** + * 微软 + */ + MICROSOFT { + @Override + public String authorize() { + return "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; + } + + @Override + public String accessToken() { + return "https://login.microsoftonline.com/common/oauth2/v2.0/token"; + } + + @Override + public String userInfo() { + return "https://graph.microsoft.com/v1.0/me"; + } + + @Override + public String revoke() { + throw new AuthException(ResponseStatus.UNSUPPORTED); + } + + @Override + public String refresh() { + return "https://login.microsoftonline.com/common/oauth2/v2.0/token"; + } }; /** diff --git a/src/main/java/me/zhyd/oauth/model/AuthSource.java b/src/main/java/me/zhyd/oauth/model/AuthSource.java index c259b063090a6421b32832527cca3e760034878a..852427c764d80c7d66c599859ece9ad41d22d689 100644 --- a/src/main/java/me/zhyd/oauth/model/AuthSource.java +++ b/src/main/java/me/zhyd/oauth/model/AuthSource.java @@ -25,4 +25,5 @@ public enum AuthSource { FACEBOOK, DOUYIN, LINKEDIN, + MICROSOFT } diff --git a/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java b/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..dc8801e78bb5447ff9e1bb10a422630b16653ea8 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java @@ -0,0 +1,106 @@ +package me.zhyd.oauth.request; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSONObject; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.model.AuthResponse; +import me.zhyd.oauth.model.AuthSource; +import me.zhyd.oauth.model.AuthToken; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.utils.UrlBuilder; + +import java.util.HashMap; +import java.util.Map; + +/** + * 微软登录 + * + * @author yangkai.shen (https://xkcoding.com) + * @version 1.5 + * @since 1.5 + */ +public class AuthMicrosoftRequest extends BaseAuthRequest { + public AuthMicrosoftRequest(AuthConfig config) { + super(config, AuthSource.MICROSOFT); + } + + @Override + protected AuthToken getAccessToken(String code) { + String accessTokenUrl = UrlBuilder.getMicrosoftAccessTokenUrl(config.getClientId(), config.getClientSecret(), config + .getRedirectUri(), code); + + return getToken(accessTokenUrl); + } + + /** + * 获取token,适用于获取access_token和刷新token + * + * @param accessTokenUrl 实际请求token的地址 + * @return token对象 + */ + private AuthToken getToken(String accessTokenUrl) { + Map paramMap = new HashMap<>(6); + HttpUtil.decodeParamMap(accessTokenUrl, "UTF-8").forEach(paramMap::put); + HttpResponse response = HttpRequest.post(accessTokenUrl) + .header("Host", "https://login.microsoftonline.com") + .header("Content-Type", "application/x-www-form-urlencoded") + .form(paramMap) + .execute(); + String accessTokenStr = response.body(); + JSONObject object = JSONObject.parseObject(accessTokenStr); + + this.checkResponse(object); + + return AuthToken.builder() + .accessToken(object.getString("access_token")) + .expireIn(object.getIntValue("expires_in")) + .scope(object.getString("scope")) + .tokenType(object.getString("token_type")) + .refreshToken(object.getString("refresh_token")) + .build(); + } + + private void checkResponse(JSONObject response) { + if (response.containsKey("error")) { + throw new AuthException(response.getString("error_description")); + } + } + + @Override + protected AuthUser getUserInfo(AuthToken authToken) { + String token = authToken.getAccessToken(); + String tokenType = authToken.getTokenType(); + String jwt = tokenType + " " + token; + HttpResponse response = HttpRequest.get(UrlBuilder.getMicrosoftUserInfoUrl()) + .header("Authorization", jwt) + .execute(); + String userInfo = response.body(); + JSONObject object = JSONObject.parseObject(userInfo); + return AuthUser.builder() + .uuid(object.getString("id")) + .username(object.getString("userPrincipalName")) + .nickname(object.getString("displayName")) + .location(object.getString("officeLocation")) + .email(object.getString("mail")) + .token(authToken) + .source(AuthSource.MICROSOFT) + .build(); + } + + /** + * 刷新access token (续期) + * + * @param authToken 登录成功后返回的Token信息 + * @return AuthResponse + */ + @Override + public AuthResponse refresh(AuthToken authToken) { + String refreshTokenUrl = UrlBuilder.getMicrosoftRefreshUrl(config.getClientId(), config.getClientSecret(), config + .getRedirectUri(), authToken.getRefreshToken()); + + return AuthResponse.builder().code(ResponseStatus.SUCCESS.getCode()).data(getToken(refreshTokenUrl)).build(); + } +} diff --git a/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java b/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java index 91ace184588d70d28cc613278058ed093a7c8fe0..91996b7ea7902543c907c57ccae1b21ff3b80590 100644 --- a/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java +++ b/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java @@ -620,8 +620,6 @@ public class UrlBuilder { return MessageFormat.format(DOUYIN_REFRESH_TOKEN_PATTERN, ApiUrl.DOUYIN.refresh(), clientId, refreshToken); } - - private static final String LINKEDIN_AUTHORIZE_PATTERN = "{0}?client_id={1}&redirect_uri={2}&state={3}&response_type=code&scope=r_liteprofile%20r_emailaddress%20w_member_social"; private static final String LINKEDIN_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&code={3}&redirect_uri={4}&grant_type=authorization_code"; private static final String LINKEDIN_USER_INFO_PATTERN = "{0}?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))"; @@ -635,7 +633,8 @@ public class UrlBuilder { * @return full url */ public static String getLinkedinAuthorizeUrl(String clientId, String redirectUrl) { - return MessageFormat.format(LINKEDIN_AUTHORIZE_PATTERN, ApiUrl.LINKEDIN.authorize(), clientId, redirectUrl, System.currentTimeMillis()); + return MessageFormat.format(LINKEDIN_AUTHORIZE_PATTERN, ApiUrl.LINKEDIN.authorize(), clientId, redirectUrl, System + .currentTimeMillis()); } /** @@ -644,7 +643,7 @@ public class UrlBuilder { * @param clientId Linkedin 应用的Client ID * @param clientSecret Linkedin 应用的Client Secret * @param code Linkedin 授权前的code,用来换token - * @param redirectUrl google 应用授权成功后的回调地址 + * @param redirectUrl google 应用授权成功后的回调地址 * @return full url */ public static String getLinkedinAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUrl) { @@ -671,4 +670,57 @@ public class UrlBuilder { public static String getLinkedinRefreshUrl(String clientId, String clientSecret, String refreshToken) { return MessageFormat.format(LINKEDIN_REFRESH_TOKEN_PATTERN, ApiUrl.LINKEDIN.refresh(), clientId, clientSecret, refreshToken); } + + + private static final String MICROSOFT_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}&response_mode=query&scope=offline_access%20user.read%20mail.read&state={3}"; + private static final String MICROSOFT_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&scope=user.read%20mail.read&redirect_uri={3}&code={4}&grant_type=authorization_code"; + private static final String MICROSOFT_USER_INFO_PATTERN = "{0}"; + private static final String MICROSOFT_REFRESH_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&scope=user.read%20mail.read&redirect_uri={3}&refresh_token={4}&grant_type=refresh_token"; + + /** + * 获取微软授权地址 + * + * @param clientId 微软 应用的Client ID + * @param redirectUrl 微软 应用授权成功后的回调地址 + * @return full url + */ + public static String getMicrosoftAuthorizeUrl(String clientId, String redirectUrl) { + return MessageFormat.format(MICROSOFT_AUTHORIZE_PATTERN, ApiUrl.MICROSOFT.authorize(), clientId, redirectUrl, System + .currentTimeMillis()); + } + + /** + * 获取微软 token的接口地址 + * + * @param clientId 微软 应用的Client ID + * @param clientSecret 微软 应用的Client Secret + * @param redirectUrl 微软 应用授权成功后的回调地址 + * @param code 微软 授权前的code,用来换token + * @return full url + */ + public static String getMicrosoftAccessTokenUrl(String clientId, String clientSecret,String redirectUrl, String code) { + return MessageFormat.format(MICROSOFT_ACCESS_TOKEN_PATTERN, ApiUrl.MICROSOFT.accessToken(), clientId, clientSecret, redirectUrl, code); + } + + /** + * 获取微软用户详情的接口地址 + * + * @return full url + */ + public static String getMicrosoftUserInfoUrl() { + return MessageFormat.format(MICROSOFT_USER_INFO_PATTERN, ApiUrl.MICROSOFT.userInfo()); + } + + /** + * 获取微软 刷新令牌 地址 + * + * @param clientId 微软应用的client_key + * @param clientSecret 微软 应用的Client Secret + * @param redirectUrl 微软 应用授权成功后的回调地址 + * @param refreshToken 微软应用返回的refresh_token + * @return full url + */ + public static String getMicrosoftRefreshUrl(String clientId, String clientSecret, String redirectUrl, String refreshToken) { + return MessageFormat.format(MICROSOFT_REFRESH_TOKEN_PATTERN, ApiUrl.MICROSOFT.refresh(), clientId, clientSecret, redirectUrl, refreshToken); + } } diff --git a/src/test/java/me/zhyd/oauth/AuthRequestTest.java b/src/test/java/me/zhyd/oauth/AuthRequestTest.java index 4f57d2607f2fa942c2b509cd3a6783754dd78ee0..c37553782bd861f3104a6ff1a10d70e4293afbb4 100644 --- a/src/test/java/me/zhyd/oauth/AuthRequestTest.java +++ b/src/test/java/me/zhyd/oauth/AuthRequestTest.java @@ -167,4 +167,17 @@ public class AuthRequestTest { // 授权登录后会返回一个code,用这个code进行登录 AuthResponse login = authRequest.login("code"); } + + @Test + public void microsoftTest() { + AuthRequest authRequest = new AuthMicrosoftRequest(AuthConfig.builder() + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); + // 返回授权页面,可自行调整 + String url = authRequest.authorize(); + // 授权登录后会返回一个code,用这个code进行登录 + AuthResponse login = authRequest.login("code"); + } }