diff --git a/src/main/java/me/zhyd/oauth/authorization/AuthorizationFactory.java b/src/main/java/me/zhyd/oauth/authorization/AuthorizationFactory.java index 380c7984b3a3262819b4812c774be97e9edb8185..bcb941e939df556cef705d7171b6e9f8e9cfb134 100644 --- a/src/main/java/me/zhyd/oauth/authorization/AuthorizationFactory.java +++ b/src/main/java/me/zhyd/oauth/authorization/AuthorizationFactory.java @@ -70,6 +70,7 @@ public class AuthorizationFactory { AuthorizationFactory.register(AuthSource.DOUYIN, new DouyinAuthorization()); AuthorizationFactory.register(AuthSource.LINKEDIN, new LinkedinAuthorization()); AuthorizationFactory.register(AuthSource.MICROSOFT, new MicrosoftAuthorization()); + AuthorizationFactory.register(AuthSource.MI, new MiAuthorization()); loader = true; } diff --git a/src/main/java/me/zhyd/oauth/authorization/MiAuthorization.java b/src/main/java/me/zhyd/oauth/authorization/MiAuthorization.java new file mode 100644 index 0000000000000000000000000000000000000000..2fb0ff678e23f145e7d1da003cdd9b238394916c --- /dev/null +++ b/src/main/java/me/zhyd/oauth/authorization/MiAuthorization.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 MiAuthorization implements Authorization { + + @Override + public String getAuthorizeUrl(AuthConfig config) { + return UrlBuilder.getMiAuthorizeUrl(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 846ce9306ce0793d98660b14e4c4fd1aad67b6dd..0125eb5892cf0bc95139ff709b2554f0bd5cfb93 100644 --- a/src/main/java/me/zhyd/oauth/consts/ApiUrl.java +++ b/src/main/java/me/zhyd/oauth/consts/ApiUrl.java @@ -532,6 +532,35 @@ public enum ApiUrl { public String refresh() { return "https://login.microsoftonline.com/common/oauth2/v2.0/token"; } + }, + /** + * 小米 + */ + MI { + @Override + public String authorize() { + return "https://account.xiaomi.com/oauth2/authorize"; + } + + @Override + public String accessToken() { + return "https://account.xiaomi.com/oauth2/token"; + } + + @Override + public String userInfo() { + return "https://open.account.xiaomi.com/user/profile"; + } + + @Override + public String revoke() { + throw new AuthException(ResponseStatus.UNSUPPORTED); + } + + @Override + public String refresh() { + return "https://account.xiaomi.com/oauth2/token"; + } }; /** diff --git a/src/main/java/me/zhyd/oauth/model/AuthSource.java b/src/main/java/me/zhyd/oauth/model/AuthSource.java index 852427c764d80c7d66c599859ece9ad41d22d689..33f844e6e14677374585bf17e54a466debc2931b 100644 --- a/src/main/java/me/zhyd/oauth/model/AuthSource.java +++ b/src/main/java/me/zhyd/oauth/model/AuthSource.java @@ -25,5 +25,6 @@ public enum AuthSource { FACEBOOK, DOUYIN, LINKEDIN, - MICROSOFT + MICROSOFT, + MI } diff --git a/src/main/java/me/zhyd/oauth/model/AuthToken.java b/src/main/java/me/zhyd/oauth/model/AuthToken.java index 56ecb203ea833bdaa0b27ea2fdf5cd2a51e5ed2f..96f1d2c8978083d6f57ef26ceb178989e7251816 100644 --- a/src/main/java/me/zhyd/oauth/model/AuthToken.java +++ b/src/main/java/me/zhyd/oauth/model/AuthToken.java @@ -27,4 +27,10 @@ public class AuthToken { private String tokenType; private String idToken; + /** + * 小米附带属性 + */ + private String macAlgorithm; + private String macKey; + } diff --git a/src/main/java/me/zhyd/oauth/request/AuthMiRequest.java b/src/main/java/me/zhyd/oauth/request/AuthMiRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..e86c2f0768ad942344a3a8e67feb6b2cfb250231 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/request/AuthMiRequest.java @@ -0,0 +1,108 @@ +package me.zhyd.oauth.request; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +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.text.MessageFormat; + +/** + * 小米登录 + * + * @author yangkai.shen (https://xkcoding.com) + * @version 1.5 + * @since 1.5 + */ +public class AuthMiRequest extends BaseAuthRequest { + private static final String PREFIX = "&&&START&&&"; + + public AuthMiRequest(AuthConfig config) { + super(config, AuthSource.MI); + } + + @Override + protected AuthToken getAccessToken(String code) { + String accessTokenUrl = UrlBuilder.getMiAccessTokenUrl(config.getClientId(), config.getClientSecret(), config.getRedirectUri(), code); + return getToken(accessTokenUrl); + } + + private AuthToken getToken(String accessTokenUrl) { + HttpResponse response = HttpRequest.get(accessTokenUrl).execute(); + String jsonStr = StrUtil.replace(response.body(), PREFIX, StrUtil.EMPTY); + JSONObject object = JSONObject.parseObject(jsonStr); + + if (object.containsKey("error")) { + throw new AuthException(object.getString("error_description")); + } + + 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")) + .openId(object.getString("openId")) + .macAlgorithm(object.getString("mac_algorithm")) + .macKey(object.getString("mac_key")) + .build(); + } + + @Override + protected AuthUser getUserInfo(AuthToken authToken) { + // 获取用户信息 + HttpResponse userResponse = HttpRequest.get(UrlBuilder.getMiUserInfoUrl(config.getClientId(), authToken.getAccessToken())) + .execute(); + + JSONObject userProfile = JSONObject.parseObject(userResponse.body()); + if (StrUtil.equalsIgnoreCase(userProfile.getString("result"), "error")) { + throw new AuthException(userProfile.getString("description")); + } + + JSONObject user = userProfile.getJSONObject("data"); + + AuthUser authUser = AuthUser.builder() + .uuid(authToken.getOpenId()) + .username(user.getString("miliaoNick")) + .nickname(user.getString("miliaoNick")) + .avatar(user.getString("miliaoIcon")) + .email(user.getString("mail")) + .token(authToken) + .source(AuthSource.MI) + .build(); + + // 获取用户邮箱手机号等信息 + String emailPhoneUrl = MessageFormat.format("{0}?clientId={1}&token={2}", "https://open.account.xiaomi.com/user/phoneAndEmail", config + .getClientId(), authToken.getAccessToken()); + + HttpResponse emailResponse = HttpRequest.get(emailPhoneUrl).execute(); + JSONObject userEmailPhone = JSONObject.parseObject(emailResponse.body()); + if (!StrUtil.equalsIgnoreCase(userEmailPhone.getString("result"), "error")) { + JSONObject emailPhone = userEmailPhone.getJSONObject("data"); + authUser.setEmail(emailPhone.getString("email")); + } + + return authUser; + } + + /** + * 刷新access token (续期) + * + * @param authToken 登录成功后返回的Token信息 + * @return AuthResponse + */ + @Override + public AuthResponse refresh(AuthToken authToken) { + String miRefreshUrl = UrlBuilder.getMiRefreshUrl(config.getClientId(), config.getClientSecret(), config.getRedirectUri(), authToken + .getRefreshToken()); + + return AuthResponse.builder().code(ResponseStatus.SUCCESS.getCode()).data(getToken(miRefreshUrl)).build(); + } +} diff --git a/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java b/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java index 91996b7ea7902543c907c57ccae1b21ff3b80590..5d645eb8346950fc4a8bac332397e4ae8aa56cc4 100644 --- a/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java +++ b/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java @@ -671,7 +671,6 @@ public class UrlBuilder { 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}"; @@ -698,7 +697,7 @@ public class UrlBuilder { * @param code 微软 授权前的code,用来换token * @return full url */ - public static String getMicrosoftAccessTokenUrl(String clientId, String clientSecret,String redirectUrl, String code) { + 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); } @@ -723,4 +722,57 @@ public class UrlBuilder { 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); } + + private static final String MI_AUTHORIZE_PATTERN = "{0}?client_id={1}&redirect_uri={2}&response_type=code&scope=user/profile%20user/openIdV2%20user/phoneAndEmail&state={3}&skip_confirm=false"; + private static final String MI_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&redirect_uri={3}&code={4}&grant_type=authorization_code"; + private static final String MI_USER_INFO_PATTERN = "{0}?clientId={1}&token={2}"; + private static final String MI_REFRESH_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&redirect_uri={3}&refresh_token={4}&grant_type=refresh_token"; + + /** + * 获取小米授权地址 + * + * @param clientId 小米 应用的Client ID + * @param redirectUrl 小米 应用授权成功后的回调地址 + * @return full url + */ + public static String getMiAuthorizeUrl(String clientId, String redirectUrl) { + return MessageFormat.format(MI_AUTHORIZE_PATTERN, ApiUrl.MI.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 getMiAccessTokenUrl(String clientId, String clientSecret, String redirectUrl, String code) { + return MessageFormat.format(MI_ACCESS_TOKEN_PATTERN, ApiUrl.MI.accessToken(), clientId, clientSecret, redirectUrl, code); + } + + /** + * 获取小米用户详情的接口地址 + * + * @param clientId 小米 应用的client_key + * @param token token + * @return full url + */ + public static String getMiUserInfoUrl(String clientId, String token) { + return MessageFormat.format(MI_USER_INFO_PATTERN, ApiUrl.MI.userInfo(), clientId, token); + } + + /** + * 获取小米 刷新令牌 地址 + * + * @param clientId 小米 应用的client_key + * @param clientSecret 小米 应用的Client Secret + * @param redirectUrl 小米 应用授权成功后的回调地址 + * @param refreshToken 小米 应用返回的refresh_token + * @return full url + */ + public static String getMiRefreshUrl(String clientId, String clientSecret, String redirectUrl, String refreshToken) { + return MessageFormat.format(MI_REFRESH_TOKEN_PATTERN, ApiUrl.MI.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 c37553782bd861f3104a6ff1a10d70e4293afbb4..0bd0df6743047fd50fba374c91789f5a1ec302b3 100644 --- a/src/test/java/me/zhyd/oauth/AuthRequestTest.java +++ b/src/test/java/me/zhyd/oauth/AuthRequestTest.java @@ -180,4 +180,17 @@ public class AuthRequestTest { // 授权登录后会返回一个code,用这个code进行登录 AuthResponse login = authRequest.login("code"); } + + @Test + public void miTest() { + AuthRequest authRequest = new AuthMiRequest(AuthConfig.builder() + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); + // 返回授权页面,可自行调整 + String url = authRequest.authorize(); + // 授权登录后会返回一个code,用这个code进行登录 + AuthResponse login = authRequest.login("code"); + } }