微信和QQ登陸的大致流程相同,但存在一些細節上的差別html
appid
和appsecret
爲了方便你們測試,博主在某寶租用了一個月的appid和appSecretjava
appid | wxfd6965ab1fc6adb2 |
---|---|
appsecret | 66bb4566de776ac699ec1dbed0cc3dd1 |
參考git
api
定義api綁定的公共接口config
微信的一些配置信息connect
與服務提供商創建鏈接所需的一些類。public interface Weixin { WeixinUserInfo getUserInfo(String openId); }
這裏咱們看到相對於QQ的getUserInfo
微信多了一個參數openId
。這是由於微信文檔中在OAuth2.0的認證流程示意圖第五步時,微信的openid
同access_token
一塊兒返回。而Spring Social
獲取access_token
的類AccessGrant.java
中沒有openid
。所以咱們本身須要擴展一下Spring Social
獲取令牌的類(AccessGrant.java
);github
@Data 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); } }
public class WeiXinImpl extends AbstractOAuth2ApiBinding implements Weixin { /** * 獲取用戶信息的url */ private static final String WEIXIN_URL_GET_USER_INFO = "https://api.weixin.qq.com/sns/userinfo?openid="; private ObjectMapper objectMapper = new ObjectMapper(); public WeiXinImpl(String accessToken) { super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER); } /** * 獲取用戶信息 * * @param openId * @return */ @Override public WeixinUserInfo getUserInfo(String openId) { String url = WEIXIN_URL_GET_USER_INFO + openId; String result = getRestTemplate().getForObject(url, String.class); if(StringUtils.contains(result, "errcode")) { return null; } WeixinUserInfo userInfo = null; try{ userInfo = objectMapper.readValue(result,WeixinUserInfo.class); }catch (Exception e){ e.printStackTrace(); } return userInfo; } /** * 使用utf-8 替換默認的ISO-8859-1編碼 * @return */ @Override protected List<HttpMessageConverter<?>> getMessageConverters() { List<HttpMessageConverter<?>> messageConverters = super.getMessageConverters(); messageConverters.remove(0); messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); return messageConverters; } }
與QQ
獲取用戶信息相比,微信
的實現類中少了一步經過access_token
獲取openid
的請求。openid
由本身定義的擴展類WeixinAccessGrant
中獲取;web
@Slf4j 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"; 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; } /* (non-Javadoc) * @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<String, String> 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); } public AccessGrant refreshAccess(String refreshToken, MultiValueMap<String, String> 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) { log.info("獲取access_token, 請求URL: "+accessTokenRequestUrl.toString()); String response = getRestTemplate().getForObject(accessTokenRequestUrl.toString(), String.class); log.info("獲取access_token, 響應內容: "+response); Map<String, Object> 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; } /** * 構建獲取受權碼的請求。也就是引導用戶跳轉到微信的地址。 */ public String buildAuthenticateUrl(OAuth2Parameters parameters) { String url = super.buildAuthenticateUrl(parameters); url = url + "&appid="+clientId+"&scope=snsapi_login"; return url; } public String buildAuthorizeUrl(OAuth2Parameters parameters) { return buildAuthenticateUrl(parameters); } /** * 微信返回的contentType是html/text,添加相應的HttpMessageConverter來處理。 */ protected RestTemplate createRestTemplate() { RestTemplate restTemplate = super.createRestTemplate(); restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); return restTemplate; } }
與QQ
處理令牌類相比多了三個全局變量而且複寫了exchangeForAccess
方法。這是由於微信
在經過code
獲取access_token
是傳遞的參數是appid
和secret
而不是標準的client_id
和client_secret
。spring
public class WeixinServiceProvider extends AbstractOAuth2ServiceProvider<Weixin> { /** * 微信獲取受權碼的url */ private static final String WEIXIN_URL_AUTHORIZE = "https://open.weixin.qq.com/connect/qrconnect"; /** * 微信獲取accessToken的url(微信在獲取accessToken時也已經返回openId) */ private static final String WEIXIN_URL_ACCESS_TOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token"; public WeixinServiceProvider(String appId, String appSecret) { super(new WeixinOAuth2Template(appId, appSecret, WEIXIN_URL_AUTHORIZE, WEIXIN_URL_ACCESS_TOKEN)); } @Override public Weixin getApi(String accessToken) { return new WeiXinImpl(accessToken); } }
public class WeixinConnectionFactory extends OAuth2ConnectionFactory<Weixin> { /** * @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; } /* (non-Javadoc) * @see org.springframework.social.connect.support.OAuth2ConnectionFactory#createConnection(org.springframework.social.oauth2.AccessGrant) */ public Connection<Weixin> createConnection(AccessGrant accessGrant) { return new OAuth2Connection<Weixin>(getProviderId(), extractProviderUserId(accessGrant), accessGrant.getAccessToken(), accessGrant.getRefreshToken(), accessGrant.getExpireTime(), getOAuth2ServiceProvider(), getApiAdapter(extractProviderUserId(accessGrant))); } /* (non-Javadoc) * @see org.springframework.social.connect.support.OAuth2ConnectionFactory#createConnection(org.springframework.social.connect.ConnectionData) */ public Connection<Weixin> createConnection(ConnectionData data) { return new OAuth2Connection<Weixin>(data, getOAuth2ServiceProvider(), getApiAdapter(data.getProviderUserId())); } private ApiAdapter<Weixin> getApiAdapter(String providerUserId) { return new WeixinAdapter(providerUserId); } private OAuth2ServiceProvider<Weixin> getOAuth2ServiceProvider() { return (OAuth2ServiceProvider<Weixin>) getServiceProvider(); } }
public class WeixinAdapter implements ApiAdapter<Weixin> { private String openId; public WeixinAdapter() { } public WeixinAdapter(String openId) { this.openId = openId; } @Override public boolean test(Weixin api) { return true; } @Override public void setConnectionValues(Weixin api, ConnectionValues values) { WeixinUserInfo userInfo = api.getUserInfo(openId); values.setProviderUserId(userInfo.getOpenid()); values.setDisplayName(userInfo.getNickname()); values.setImageUrl(userInfo.getHeadimgurl()); } @Override public UserProfile fetchUserProfile(Weixin api) { return null; } @Override public void updateStatus(Weixin api, String message) { } }
@Configuration public class WeixinAuthConfig extends SocialAutoConfigurerAdapter { @Autowired private DataSource dataSource; @Autowired private ConnectionSignUp myConnectionSignUp; @Override protected ConnectionFactory<?> createConnectionFactory() { return new WeixinConnectionFactory(DEFAULT_SOCIAL_WEIXIN_PROVIDER_ID, SecurityConstants.DEFAULT_SOCIAL_WEIXIN_APP_ID, SecurityConstants.DEFAULT_SOCIAL_WEIXIN_APP_SECRET); } @Override public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) { JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText()); if (myConnectionSignUp != null) { repository.setConnectionSignUp(myConnectionSignUp); } return repository; } /** * /connect/weixin POST請求,綁定微信返回connect/weixinConnected視圖 * /connect/weixin DELETE請求,解綁返回connect/weixinConnect視圖 * @return */ @Bean({"connect/weixinConnect", "connect/weixinConnected"}) @ConditionalOnMissingBean(name = "weixinConnectedView") public View weixinConnectedView() { return new SocialConnectView(); } }
配置SocialAuthenticationFilter
過濾器攔截。api