From 71a9415afb761f565c82d7c7b122bc2f7db45227 Mon Sep 17 00:00:00 2001 From: winLsy <18332763730@163.com> Date: Wed, 17 Jan 2018 13:57:41 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E7=99=BB=E5=BD=95=E5=88=9D?= =?UTF-8?q?=E6=AD=A5=E5=BC=80=E5=8F=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../boot-starter-security-browser/README.md | 1 + .../browser/config/BrowserSecurityConfig.java | 2 +- .../boot-starter-security-core/README.md | 8 +- .../core/social/SocialProperties.java | 21 +++ .../social/wechat/WeChatSocialProperties.java | 23 +++ .../core/social/wechat/api/WeChat.java | 13 ++ .../social/wechat/api/WeChatUserInfo.java | 170 ++++++++++++++++++ .../social/wechat/api/impl/WeChatImpl.java | 61 +++++++ .../social/wechat/api/impl/package-info.java | 1 + .../core/social/wechat/api/package-info.java | 1 + .../config/WeixinAutoConfiguration.java | 34 ++++ .../social/wechat/config/package-info.java | 1 + .../wechat/connet/WeixinAccessGrant.java | 40 +++++ .../social/wechat/connet/WeixinAdapter.java | 65 +++++++ .../connet/WeixinConnectionFactory.java | 72 ++++++++ .../wechat/connet/WeixinOAuth2Template.java | 132 ++++++++++++++ .../wechat/connet/WeixinServiceProvider.java | 43 +++++ .../social/wechat/connet/package-info.java | 1 + 18 files changed, 686 insertions(+), 3 deletions(-) create mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/WeChatSocialProperties.java create mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/WeChat.java create mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/WeChatUserInfo.java create mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/impl/WeChatImpl.java create mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/impl/package-info.java create mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/package-info.java create mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/config/WeixinAutoConfiguration.java create mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/config/package-info.java create mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinAccessGrant.java create mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinAdapter.java create mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinConnectionFactory.java create mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinOAuth2Template.java create mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinServiceProvider.java create mode 100644 boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/package-info.java diff --git a/boot-starter/boot-starter-security/boot-starter-security-browser/README.md b/boot-starter/boot-starter-security/boot-starter-security-browser/README.md index 160c476..5249b2b 100644 --- a/boot-starter/boot-starter-security/boot-starter-security-browser/README.md +++ b/boot-starter/boot-starter-security/boot-starter-security-browser/README.md @@ -8,6 +8,7 @@ - `/authentication/require`处理身份认证跳转的接口地址 - 需要注入`userServiceImpl`获取用户详细信息的bean (必须参数) - 需要注入数据源`masterDataSource`用于记住我功能的bean,记住我功能需要在登录的时候传入(必须参数) +### 第三方方登录的配置 - 使用这个模块的时候需要借助providerSignInUtils.doPostSignUp();方法实现一个接口将用户的注册或绑定信息插入到数据表中,这里的注 册指的是创建本系统自己的用户体系,第三方的用户体系springSocial会自动帮助我们创建。并将url 配置可以直接访问 diff --git a/boot-starter/boot-starter-security/boot-starter-security-browser/src/main/java/com/micro/fast/security/browser/config/BrowserSecurityConfig.java b/boot-starter/boot-starter-security/boot-starter-security-browser/src/main/java/com/micro/fast/security/browser/config/BrowserSecurityConfig.java index 3576c5d..7b97ded 100644 --- a/boot-starter/boot-starter-security/boot-starter-security-browser/src/main/java/com/micro/fast/security/browser/config/BrowserSecurityConfig.java +++ b/boot-starter/boot-starter-security/boot-starter-security-browser/src/main/java/com/micro/fast/security/browser/config/BrowserSecurityConfig.java @@ -130,7 +130,7 @@ public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter { , "/upms/user" //用户注册或绑定的页面 ,securityProperties.getBrowser().getSignUpUrl() - ,"用户的注册绑定url") + ,"处理用户的注册绑定请求的url") .permitAll() //任何请求 .anyRequest() diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/README.md b/boot-starter/boot-starter-security/boot-starter-security-core/README.md index d8e5a36..c04e887 100644 --- a/boot-starter/boot-starter-security/boot-starter-security-core/README.md +++ b/boot-starter/boot-starter-security/boot-starter-security-core/README.md @@ -24,9 +24,13 @@ - `ms.security.createRememberTable` 是否创建记住我表结构默认为`true` ### 本项目提供的第三方登录的配置 +qq 第三方登录 - `ms.security.social.qq.appId` qq互联注册的id - `ms.security.social.qq.appSecret` qq互联注册的密码 - `ms.security.social.qq.providerId` 第三方登录的标识,例如qq为`qq`登陆默认 -- `ms.security.social.qq.filterProcessUrl` 进行第三方登录处理的基础url前缀,这个前缀会和providerId进行拼接来进行明确 +- `ms.security.social.filterProcessUrl` 进行第三方登录处理的基础url前缀,这个前缀会和providerId进行拼接来进行明确 的第三方登录,这个地址也是从第三方应用携带授权码返回的时候进行处理的地址,如果没有授权码就重定向到第三方登录 - +weChat 第三方登录 +- `ms.security.social.weChat.appId` 类比qq登录 +- `ms.security.social.weChat.appSecret` 类比qq登录 +- `ms.security.social.weChat.providerId` 类比qq登录 diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/SocialProperties.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/SocialProperties.java index f994a08..d3e4b37 100644 --- a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/SocialProperties.java +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/SocialProperties.java @@ -1,16 +1,37 @@ package com.micro.fast.security.core.social; import com.micro.fast.security.core.social.qq.QQSocialProperties; +import com.micro.fast.security.core.social.wechat.WeChatSocialProperties; +import com.micro.fast.security.core.social.wechat.api.WeChat; /** * 第三方登录配置类 * @author lsy */ public class SocialProperties { + /** + * QQ登录配置 + */ private QQSocialProperties qq = new QQSocialProperties(); + /** + * 微信登录配置 + */ + private WeChatSocialProperties weChat = new WeChatSocialProperties(); + + /** + * 第三方登录拦截url的配置 + */ private String filterProcessUrl = "/auth"; + public WeChatSocialProperties getWeChat() { + return weChat; + } + + public void setWeChat(WeChatSocialProperties weChat) { + this.weChat = weChat; + } + public QQSocialProperties getQq() { return qq; } diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/WeChatSocialProperties.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/WeChatSocialProperties.java new file mode 100644 index 0000000..7824217 --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/WeChatSocialProperties.java @@ -0,0 +1,23 @@ +package com.micro.fast.security.core.social.wechat; + + +import org.springframework.boot.autoconfigure.social.SocialProperties; + +/** + * weChat第三方登录配置类 + * @author lsy + */ +public class WeChatSocialProperties extends SocialProperties { + /** + * 第三方登录标识 + */ + private String providerId = "weChat"; + + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } +} diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/WeChat.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/WeChat.java new file mode 100644 index 0000000..3fdc7cb --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/WeChat.java @@ -0,0 +1,13 @@ +package com.micro.fast.security.core.social.wechat.api; + +/** + * weChat 获取用户信息的接口 + * @author lsy + */ +public interface WeChat { + /** + * 获取微信登录用户的信息 + * @return + */ + WeChatUserInfo getWeChatUserInfo(String openId); +} diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/WeChatUserInfo.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/WeChatUserInfo.java new file mode 100644 index 0000000..01d3994 --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/WeChatUserInfo.java @@ -0,0 +1,170 @@ +package com.micro.fast.security.core.social.wechat.api; + +/** + * 微信登录用户的第三方信息 + * @author lsy + */ +public class WeChatUserInfo { + /** + * 普通用户的标识,对当前开发者帐号唯一 + */ + private String openid; + /** + * 普通用户昵称 + */ + private String nickname; + /** + * 语言 + */ + private String language; + /** + * 普通用户性别,1为男性,2为女性 + */ + private String sex; + /** + * 普通用户个人资料填写的省份 + */ + private String province; + /** + * 普通用户个人资料填写的城市 + */ + private String city; + /** + * 国家,如中国为CN + */ + private String country; + /** + * 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空 + */ + private String headimgurl; + /** + * 用户特权信息,json数组,如微信沃卡用户为(chinaunicom) + */ + private String[] privilege; + /** + * 用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的。 + */ + private String unionid; + + /** + * @return the openid + */ + public String getOpenid() { + return openid; + } + /** + * @param openid the openid to set + */ + public void setOpenid(String openid) { + this.openid = openid; + } + /** + * @return the nickname + */ + public String getNickname() { + return nickname; + } + /** + * @param nickname the nickname to set + */ + public void setNickname(String nickname) { + this.nickname = nickname; + } + /** + * @return the sex + */ + public String getSex() { + return sex; + } + /** + * @param sex the sex to set + */ + public void setSex(String sex) { + this.sex = sex; + } + /** + * @return the province + */ + public String getProvince() { + return province; + } + /** + * @param province the province to set + */ + public void setProvince(String province) { + this.province = province; + } + /** + * @return the city + */ + public String getCity() { + return city; + } + /** + * @param city the city to set + */ + public void setCity(String city) { + this.city = city; + } + /** + * @return the country + */ + public String getCountry() { + return country; + } + /** + * @param country the country to set + */ + public void setCountry(String country) { + this.country = country; + } + /** + * @return the headimgurl + */ + public String getHeadimgurl() { + return headimgurl; + } + /** + * @param headimgurl the headimgurl to set + */ + public void setHeadimgurl(String headimgurl) { + this.headimgurl = headimgurl; + } + /** + * @return the privilege + */ + public String[] getPrivilege() { + return privilege; + } + /** + * @param privilege the privilege to set + */ + public void setPrivilege(String[] privilege) { + this.privilege = privilege; + } + /** + * @return the unionid + */ + public String getUnionid() { + return unionid; + } + /** + * @param unionid the unionid to set + */ + public void setUnionid(String unionid) { + this.unionid = unionid; + } + /** + * @return the language + */ + public String getLanguage() { + return language; + } + /** + * @param language the language to set + */ + public void setLanguage(String language) { + this.language = language; + } + +} diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/impl/WeChatImpl.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/impl/WeChatImpl.java new file mode 100644 index 0000000..442a353 --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/impl/WeChatImpl.java @@ -0,0 +1,61 @@ +package com.micro.fast.security.core.social.wechat.api.impl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.micro.fast.security.core.social.wechat.api.WeChat; +import com.micro.fast.security.core.social.wechat.api.WeChatUserInfo; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.social.oauth2.AbstractOAuth2ApiBinding; +import org.springframework.social.oauth2.TokenStrategy; + +import java.nio.charset.Charset; +import java.util.List; + +/** + * weChat获取用户信息接口的实现类,微信实现的并不是标准的OAuth2协议 + * @author lsy + */ +public class WeChatImpl extends AbstractOAuth2ApiBinding implements WeChat { + + /** + * 微信接口获取用户信息的url + */ + private static final String URL_GET_USER_INFO = "https://api.weixin.qq.com/sns/userinfo?openid="; + + private ObjectMapper objectMapper = new ObjectMapper(); + + /** + * @param accessToken + */ + public WeChatImpl(String accessToken) { + super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER); + } + + /** + * 默认注册的StringHttpMessageConverter字符集为ISO-8859-1,而微信返回的是UTF-8的,所以覆盖了原来的方法。 + */ + @Override + protected List> getMessageConverters() { + List> messageConverters = super.getMessageConverters(); + messageConverters.remove(0); + messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); + return messageConverters; + } + + @Override + public WeChatUserInfo getWeChatUserInfo(String openId) { + String url = URL_GET_USER_INFO + openId; + String response = getRestTemplate().getForObject(url, String.class); + if(StringUtils.contains(response, "errcode")) { + return null; + } + WeChatUserInfo profile = null; + try { + profile = objectMapper.readValue(response, WeChatUserInfo.class); + } catch (Exception e) { + throw new RuntimeException("获取用户信息失败"); + } + return profile; + } +} diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/impl/package-info.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/impl/package-info.java new file mode 100644 index 0000000..ea06be7 --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/impl/package-info.java @@ -0,0 +1 @@ +package com.micro.fast.security.core.social.wechat.api.impl; \ No newline at end of file diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/package-info.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/package-info.java new file mode 100644 index 0000000..fbec417 --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/api/package-info.java @@ -0,0 +1 @@ +package com.micro.fast.security.core.social.wechat.api; \ No newline at end of file diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/config/WeixinAutoConfiguration.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/config/WeixinAutoConfiguration.java new file mode 100644 index 0000000..8307389 --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/config/WeixinAutoConfiguration.java @@ -0,0 +1,34 @@ +/** + * + */ +package com.micro.fast.security.core.social.wechat.config; + +import com.micro.fast.security.core.master.SecurityProperties; +import com.micro.fast.security.core.social.wechat.WeChatSocialProperties; +import com.micro.fast.security.core.social.wechat.connet.WeixinConnectionFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.social.SocialAutoConfigurerAdapter; +import org.springframework.context.annotation.Configuration; +import org.springframework.social.connect.ConnectionFactory; + + + +/** + * 微信登录配置 + * @author lsy + */ +@Configuration +@ConditionalOnProperty(prefix = "ms.security.social.weChat", name = "app-id") +public class WeixinAutoConfiguration extends SocialAutoConfigurerAdapter { + + @Autowired + private SecurityProperties securityProperties; + + @Override + protected ConnectionFactory createConnectionFactory() { + WeChatSocialProperties weChatConfig = securityProperties.getSocial().getWeChat(); + return new WeixinConnectionFactory(weChatConfig.getProviderId(), weChatConfig.getAppId(), + weChatConfig.getAppSecret()); + } +} diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/config/package-info.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/config/package-info.java new file mode 100644 index 0000000..15ea5d4 --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/config/package-info.java @@ -0,0 +1 @@ +package com.micro.fast.security.core.social.wechat.config; \ No newline at end of file diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinAccessGrant.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinAccessGrant.java new file mode 100644 index 0000000..46b258f --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinAccessGrant.java @@ -0,0 +1,40 @@ +/** + * + */ +package com.micro.fast.security.core.social.wechat.connet; + +import org.springframework.social.oauth2.AccessGrant; + +/** + * 微信的access_token信息。与标准OAuth2协议不同,微信在获取access_token时会同时返回openId,并没有单独的通过accessToke换取openId的服务 + * 所以在这里继承了标准AccessGrant,添加了openId字段,作为对微信access_token信息的封装。 + * @author lsy + */ +public class WeixinAccessGrant extends AccessGrant { + + + private String openId; + + public WeixinAccessGrant() { + super(""); + } + + public WeixinAccessGrant(String accessToken, String scope, String refreshToken, Long expiresIn) { + super(accessToken, scope, refreshToken, expiresIn); + } + + /** + * @return the openId + */ + public String getOpenId() { + return openId; + } + + /** + * @param openId the openId to set + */ + public void setOpenId(String openId) { + this.openId = openId; + } + +} diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinAdapter.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinAdapter.java new file mode 100644 index 0000000..5183846 --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinAdapter.java @@ -0,0 +1,65 @@ +/** + * + */ +package com.micro.fast.security.core.social.wechat.connet; + +import com.micro.fast.security.core.social.wechat.api.WeChat; +import com.micro.fast.security.core.social.wechat.api.WeChatUserInfo; +import org.springframework.social.connect.ApiAdapter; +import org.springframework.social.connect.ConnectionValues; +import org.springframework.social.connect.UserProfile; + +/** + * 微信 api适配器,将微信 api的数据模型转为spring social的标准模型。 + * @author lsy + */ +public class WeixinAdapter implements ApiAdapter { + + private String openId; + + public WeixinAdapter() {} + + public WeixinAdapter(String openId){ + this.openId = openId; + } + + /** + * @param api + * @return + */ + @Override + public boolean test(WeChat api) { + return true; + } + + /** + * @param api + * @param values + */ + @Override + public void setConnectionValues(WeChat api, ConnectionValues values) { + WeChatUserInfo profile = api.getWeChatUserInfo(openId); + values.setProviderUserId(profile.getOpenid()); + values.setDisplayName(profile.getNickname()); + values.setImageUrl(profile.getHeadimgurl()); + } + + /** + * @param api + * @return + */ + @Override + public UserProfile fetchUserProfile(WeChat api) { + return null; + } + + /** + * @param api + * @param message + */ + @Override + public void updateStatus(WeChat api, String message) { + //do nothing + } + +} diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinConnectionFactory.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinConnectionFactory.java new file mode 100644 index 0000000..a37ac99 --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinConnectionFactory.java @@ -0,0 +1,72 @@ +/** + * + */ +package com.micro.fast.security.core.social.wechat.connet; + +import com.micro.fast.security.core.social.wechat.api.WeChat; +import org.springframework.social.connect.ApiAdapter; +import org.springframework.social.connect.Connection; +import org.springframework.social.connect.ConnectionData; +import org.springframework.social.connect.support.OAuth2Connection; +import org.springframework.social.connect.support.OAuth2ConnectionFactory; +import org.springframework.social.oauth2.AccessGrant; +import org.springframework.social.oauth2.OAuth2ServiceProvider; + +/** + * 微信连接工厂 + * + * + * @author lsy + */ +public class WeixinConnectionFactory extends OAuth2ConnectionFactory { + + /** + * @param appId + * @param appSecret + */ + public WeixinConnectionFactory(String providerId, String appId, String appSecret) { + super(providerId, new WeixinServiceProvider(appId, appSecret), new WeixinAdapter()); + } + + /** + * 由于微信的openId是和accessToken一起返回的,所以在这里直接根据accessToken设置providerUserId即可,不用像QQ那样通过QQAdapter来获取 + */ + @Override + protected String extractProviderUserId(AccessGrant accessGrant) { + if(accessGrant instanceof WeixinAccessGrant) { + return ((WeixinAccessGrant)accessGrant).getOpenId(); + } + return null; + } + + + /** + * @param accessGrant + * @return + */ + @Override + public Connection createConnection(AccessGrant accessGrant) { + return new OAuth2Connection(getProviderId(), extractProviderUserId(accessGrant), accessGrant.getAccessToken(), + accessGrant.getRefreshToken(), accessGrant.getExpireTime(), getOAuth2ServiceProvider(), getApiAdapter(extractProviderUserId(accessGrant))); + } + + + /** + * @param data + * @return + */ + @Override + public Connection createConnection(ConnectionData data) { + return new OAuth2Connection(data, getOAuth2ServiceProvider(), getApiAdapter(data.getProviderUserId())); + } + + private ApiAdapter getApiAdapter(String providerUserId) { + return new WeixinAdapter(providerUserId); + } + + private OAuth2ServiceProvider getOAuth2ServiceProvider() { + return (OAuth2ServiceProvider) getServiceProvider(); + } + + +} diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinOAuth2Template.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinOAuth2Template.java new file mode 100644 index 0000000..a00f4bc --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinOAuth2Template.java @@ -0,0 +1,132 @@ +/** + * + */ +package com.micro.fast.security.core.social.wechat.connet; + +import java.nio.charset.Charset; +import java.util.Map; + +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.social.oauth2.AccessGrant; +import org.springframework.social.oauth2.OAuth2Parameters; +import org.springframework.social.oauth2.OAuth2Template; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * 完成微信的OAuth2认证流程的模板类。国内厂商实现的OAuth2每个都不同, spring默认提供的OAuth2Template适应不了,只能针对每个厂商自己微调。 + * @author lsy + */ +public class WeixinOAuth2Template extends OAuth2Template { + + private String clientId; + + private String clientSecret; + + private String accessTokenUrl; + + private static final String REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token"; + + private Logger logger = LoggerFactory.getLogger(getClass()); + + public WeixinOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) { + super(clientId, clientSecret, authorizeUrl, accessTokenUrl); + setUseParametersForClientAuthentication(true); + this.clientId = clientId; + this.clientSecret = clientSecret; + this.accessTokenUrl = accessTokenUrl; + } + + /** + * @see org.springframework.social.oauth2.OAuth2Template#exchangeForAccess(java.lang.String, java.lang.String, org.springframework.util.MultiValueMap) + */ + @Override + public AccessGrant exchangeForAccess(String authorizationCode, String redirectUri, + MultiValueMap parameters) { + StringBuilder accessTokenRequestUrl = new StringBuilder(accessTokenUrl); + accessTokenRequestUrl.append("?appid="+clientId); + accessTokenRequestUrl.append("&secret="+clientSecret); + accessTokenRequestUrl.append("&code="+authorizationCode); + accessTokenRequestUrl.append("&grant_type=authorization_code"); + accessTokenRequestUrl.append("&redirect_uri="+redirectUri); + return getAccessToken(accessTokenRequestUrl); + } + + @Override + public AccessGrant refreshAccess(String refreshToken, MultiValueMap additionalParameters) { + + StringBuilder refreshTokenUrl = new StringBuilder(REFRESH_TOKEN_URL); + + refreshTokenUrl.append("?appid="+clientId); + refreshTokenUrl.append("&grant_type=refresh_token"); + refreshTokenUrl.append("&refresh_token="+refreshToken); + + return getAccessToken(refreshTokenUrl); + } + + @SuppressWarnings("unchecked") + private AccessGrant getAccessToken(StringBuilder accessTokenRequestUrl) { + + logger.info("获取access_token, 请求URL: "+accessTokenRequestUrl.toString()); + + String response = getRestTemplate().getForObject(accessTokenRequestUrl.toString(), String.class); + + logger.info("获取access_token, 响应内容: "+response); + + Map result = null; + try { + result = new ObjectMapper().readValue(response, Map.class); + } catch (Exception e) { + e.printStackTrace(); + } + + //返回错误码时直接返回空 + if(StringUtils.isNotBlank(MapUtils.getString(result, "errcode"))){ + String errcode = MapUtils.getString(result, "errcode"); + String errmsg = MapUtils.getString(result, "errmsg"); + throw new RuntimeException("获取access token失败, errcode:"+errcode+", errmsg:"+errmsg); + } + + WeixinAccessGrant accessToken = new WeixinAccessGrant( + MapUtils.getString(result, "access_token"), + MapUtils.getString(result, "scope"), + MapUtils.getString(result, "refresh_token"), + MapUtils.getLong(result, "expires_in")); + + accessToken.setOpenId(MapUtils.getString(result, "openid")); + + return accessToken; + } + + /** + * 构建获取授权码的请求。也就是引导用户跳转到微信的地址。 + */ + @Override + public String buildAuthenticateUrl(OAuth2Parameters parameters) { + String url = super.buildAuthenticateUrl(parameters); + url = url + "&appid="+clientId+"&scope=snsapi_login"; + return url; + } + + @Override + public String buildAuthorizeUrl(OAuth2Parameters parameters) { + return buildAuthenticateUrl(parameters); + } + + /** + * 微信返回的contentType是html/text,添加相应的HttpMessageConverter来处理。 + */ + @Override + protected RestTemplate createRestTemplate() { + RestTemplate restTemplate = super.createRestTemplate(); + restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); + return restTemplate; + } + +} diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinServiceProvider.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinServiceProvider.java new file mode 100644 index 0000000..03d4d82 --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/WeixinServiceProvider.java @@ -0,0 +1,43 @@ +/** + * + */ +package com.micro.fast.security.core.social.wechat.connet; + +import com.micro.fast.security.core.social.wechat.api.WeChat; +import com.micro.fast.security.core.social.wechat.api.impl.WeChatImpl; +import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider; + +/** + * + * 微信的OAuth2流程处理器的提供器,供spring social的connect体系调用 + * + * @author zhailiang + * + */ +public class WeixinServiceProvider extends AbstractOAuth2ServiceProvider { + + /** + * 微信获取授权码的url + */ + private static final String URL_AUTHORIZE = "https://open.weixin.qq.com/connect/qrconnect"; + /** + * 微信获取accessToken的url + */ + private static final String URL_ACCESS_TOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token"; + + /** + * @param appId + * @param appSecret + */ + public WeixinServiceProvider(String appId, String appSecret) { + super(new WeixinOAuth2Template(appId, appSecret,URL_AUTHORIZE,URL_ACCESS_TOKEN)); + } + + + + @Override + public WeChat getApi(String accessToken) { + return new WeChatImpl(accessToken); + } + +} diff --git a/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/package-info.java b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/package-info.java new file mode 100644 index 0000000..c636a96 --- /dev/null +++ b/boot-starter/boot-starter-security/boot-starter-security-core/src/main/java/com/micro/fast/security/core/social/wechat/connet/package-info.java @@ -0,0 +1 @@ +package com.micro.fast.security.core.social.wechat.connet; \ No newline at end of file