Spring Social微信登录

在这里插入图片描述
微信登录的appId获得可在微信开放平台申请,以下用测试号

1、完成WeixinProperties

用测试账号登录

public class WeixinProperties {

	private String appId = "wxd99431bbff8305a0";
	private String appSecret = "60f78681d063590a469f1b297feff3c4";
	
	private String prividerId = "weixin";
	private String filterProcessesUrl = "/qqLogin";
	
	//get/set
}
package com.zzz.blog.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "blog.security")
public class BlogSecurityProperties {
	
	private WeixinProperties weixinProperties = new WeixinProperties();
		
	//get/set
}
package com.zzz.blog.properties;

import ...

@Configuration
//让我们的配置生效
@EnableConfigurationProperties (BlogSecurityProperties.class)
public class BlogSecurityConfig {
	
}

2、创建微信包和api的功能

在这里插入图片描述
微信signup包与qq登录的signup共享一份文件(查看前面qq登录的文章)

package com.zzz.blog.social.weixin.api;

public interface Weixin {

	WeixinUserInfo getUserInfo(String openId);
}
package com.zzz.blog.social.weixin.api;

public class WeixinUserInfo {

	private String language;
	private String openid;
	private String nickname;
	private String sex;
	private String province;
	private String city;
	private String country; 
	private String headimgurl;
	private String[] privilege;
	private String unionid;
	
	//get/set
	
}

3、完成WeixinImpl的实现

package com.zzz.blog.social.weixin.api;

import ...

public class WeixinImpl extends AbstractOAuth2ApiBinding implements Weixin{

	private static final String URL_GET_USERINFO = "https://api.weixin.qq.com/sns/userinfo?openid=";
	
	private ObjectMapper objectMapper = new ObjectMapper();
	
	//自动在url中拼接令牌
	public WeixinImpl(String accessToken) {
		super(accessToken,TokenStrategy.ACCESS_TOKEN_PARAMETER);
	}
	
	@Override
	public WeixinUserInfo getUserInfo(String openId) {

		String url = URL_GET_USERINFO + openId;
		//发送请求,获得weixinUserInfo数据
		String response = getRestTemplate().getForObject(url, String.class);
		
		WeixinUserInfo weixinUserInfo = null;
		
		try {
			weixinUserInfo = objectMapper.readValue(response, WeixinUserInfo.class);
		} catch (JsonProcessingException e) {
			throw new RuntimeException("json解析失败"+response);
		}
		
		return weixinUserInfo;
	}

	//解决收到的字符串是乱码,原因是,默认实现的HttpMessageConverter编码是ISO- 8859-1,而微信给我们的是UTF-8
	@Override
	protected List<HttpMessageConverter<?>> getMessageConverters() {

		List<HttpMessageConverter<?>> messageConverters = super.getMessageConverters();
		messageConverters.remove(0);
		messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
		return messageConverters;
	}

}

4、完成拼接获取令牌的请求,完成WeixinOAuth2Template

因微信获取令牌格式包含openid,需要重写获取令牌的方法

package com.zzz.blog.social.weixin.connect;

import ...

//微信特有的令牌,带openId
//正常的令牌是不带由openid的, 所以我们要自己实现一个特有的令牌
public class WeixinAccessGrant extends AccessGrant{

	private String openId;
	
	public WeixinAccessGrant(String accessToken, String scope, String refreshToken, Long expiresIn) {
		super(accessToken, scope, refreshToken, expiresIn);
		// TODO Auto-generated constructor stub
	}

	//get/set
}
package com.zzz.blog.social.weixin.template;

import ...

public class WeixinOAuth2Template extends OAuth2Template{

	private String accessTokenUrl;
	private String clientId;
	private String clientSecret;
	
	private ObjectMapper objectMapper = new ObjectMapper();
	
	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);
		
		this.accessTokenUrl = accessTokenUrl;
		this.clientId = clientId;
		this.clientSecret = clientSecret;
	}

	@Override
	public AccessGrant exchangeForAccess(String authorizationCode, String redirectUri,
			MultiValueMap<String, String> additionalParameters) {
		//获取accessTokenUrl
		StringBuilder accessTokenRequestUrl = new StringBuilder(accessTokenUrl);
		//拼接参数
		//https://api. weixin. qq. com/ sns/oauth2/access_ token
		// ?appid=APPID
		accessTokenRequestUrl.append("?appid="+clientId);
		//&secret=SECRET
		accessTokenRequestUrl.append("&secret="+clientSecret);
		//&code=CODE
		accessTokenRequestUrl.append("&code="+authorizationCode);
		//&grant_type=authorization_code
		accessTokenRequestUrl.append("&grant_type=authorization_code");
		//&redirect_uri=
		accessTokenRequestUrl.append("&redirect_uri="+redirectUri);
		
		return getAccessToken(accessTokenRequestUrl);
	}

	//发送请求,获取令牌
	private AccessGrant getAccessToken(StringBuilder accessTokenRequestUrl) {
		String response = getRestTemplate().getForObject(accessTokenRequestUrl.toString(), String.class);
//	{
//		"access_token": "ACCESS_ TOKEN",
//		"expires_in":7200,
//		"refresh_token": "REFRESH TOKEN",
//		'scope": "SCOPE",
//		"unionid": "o6_ bmasdasdsad6_ 2sgVt7hMZOPfL",
//		
//		"openid" : "OPENID"
//	}
		Map<String,Object> result = null;
		try {
			result = objectMapper.readValue(response, Map.class);
		} catch (JsonProcessingException e) {
			// TODO Auto-generated catch block
			throw new RuntimeException("objectMapper解析失败"+response);
		}
		//获得令牌
		WeixinAccessGrant accessToken = new WeixinAccessGrant(
				(String)result.get("access_token"),
				(String)result.get("scope"),
				(String)result.get("refresh_token"),
				new Long((Integer)result.get("expires_in")));
		accessToken.setOpenId((String)result.get("openid"));		
		//返回令牌
		return accessToken;
	}
	
	//解决收到的字符串是乱码
	@Override
	protected RestTemplate createRestTemplate() {
		RestTemplate template = super.createRestTemplate();
		template.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
		return template;
	}
	
	//当令牌失效后重新授权
	@Override
	public AccessGrant refreshAccess(String refreshToken, String scope,
			MultiValueMap<String, String> additionalParameters) {
		StringBuilder refreshTokenUrl = new StringBuilder(REFRESH_TOKEN_URL);
		//?appid=APPID
		refreshTokenUrl.append("?appid="+clientId);
		//&grant_type=refresh_token
		refreshTokenUrl.append("&grant_type=refresh_token");
		//&refresh_token=REFRESH_TOKEN
		refreshTokenUrl.append("&refresh_token="+refreshToken);
		
		return getAccessToken(refreshTokenUrl);
	}
	//授权的url
	@Override
	public String buildAuthenticateUrl(OAuth2Parameters parameters) {
		String url = super.buildAuthenticateUrl(parameters);
		url = url + "&appid=" + clientId + "&scope=snsapi_login";
		return url;
	}
}

5、完成WeixinServiceProvider与WeixinAdapter

package com.zzz.blog.social.weixin.connect;

import ...

public class WeixinServiceProvider extends AbstractOAuth2ServiceProvider<Weixin>{

	//获取授权码
	private static final String URL_AUTHORIZE="https://open.weixin.qq.com/connect/qrconnect";
	//获取令牌
	private static final String URL_ACCESS_TOKEN="https://api.weixin.qq.com/sns/oauth2/access_token";
	
	public WeixinServiceProvider(String appId,String appSecret) {
		super(new WeixinOAuth2Template(appId, appSecret, URL_AUTHORIZE, URL_ACCESS_TOKEN));
		// TODO Auto-generated constructor stub
	}

	@Override
	public Weixin getApi(String accessToken) {
		// TODO Auto-generated method stub
		return new WeixinImpl(accessToken);
	}
	
}
package com.zzz.blog.social.weixin.connect;

import ...

public class WeixinAdapter implements ApiAdapter<Weixin>{

	private String openId;
	
	public WeixinAdapter(String openId) {
		this.openId = openId;
	}

	@Override
	public boolean test(Weixin api) {
		//改为true
		return true;
	}

	//适配
	@Override
	public void setConnectionValues(Weixin api, ConnectionValues values) {
		WeixinUserInfo info = api.getUserInfo(openId);
		values.setDisplayName(info.getNickname());
		values.setProviderUserId(info.getOpenid());
		values.setImageUrl(info.getHeadimgurl());
	}

	@Override
	public UserProfile fetchUserProfile(Weixin api) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void updateStatus(Weixin api, String message) {
		// TODO Auto-generated method stub
		
	}

}

6、完成WeixinConnectionFactory

package com.zzz.blog.social.weixin.connect;

import ...

import com.zzz.blog.social.weixin.api.Weixin;

public class WeixinConnectionFactory extends OAuth2ConnectionFactory<Weixin>{

	public WeixinConnectionFactory(String providerId, String appId, String appSecret) {
		super(providerId, new WeixinServiceProvider(appId, appSecret), new WeixinAdapter());
		// TODO Auto-generated constructor stub
	}

	@Override
	public Connection<Weixin> createConnection(AccessGrant accessGrant) {
		// TODO Auto-generated method stub
		return new OAuth2Connection<>(getProviderId(),
				extractProviderUserId(accessGrant), 
				accessGrant.getAccessToken(), 
				accessGrant.getRefreshToken(), 
				accessGrant.getExpireTime(), 
				getOAuth2ServiceProvider(), 
				getApiAdapter(extractProviderUserId(accessGrant)));
	}

	@Override
	public Connection<Weixin> createConnection(ConnectionData data) {
		return new OAuth2Connection<>(data, getOAuth2ServiceProvider(), getApiAdapter(data.getProviderUserId()));
	}

	@Override
	protected String extractProviderUserId(AccessGrant accessGrant) {
		if(accessGrant instanceof WeixinAccessGrant) {
			return ((WeixinAccessGrant)accessGrant).getOpenId();
		}
		return null;
	}

	public OAuth2ServiceProvider<Weixin> getOAuth2ServiceProvider() {
		return (OAuth2ServiceProvider<Weixin>)getServiceProvider();
	}

	public ApiAdapter<Weixin> getApiAdapter(String providerUserId) {
		return new WeixinAdapter(providerUserId);
	}
	
}

7、完成WeixinConfiguration

package com.zzz.blog.social.weixin.config;

import ...

@Configuration
@EnableSocial
public class WeixinConfiguration extends SocialConfigurerAdapter{
	
	@Autowired
	private BlogSecurityProperties blogSecurityProperties;

	@Override
	public void addConnectionFactories(ConnectionFactoryConfigurer connectionFactoryConfigurer,
			Environment environment) {
		WeixinProperties weixinProperties = blogSecurityProperties.getWeixinProperties();
		WeixinConnectionFactory weixinConnectionFactory = new WeixinConnectionFactory(weixinProperties.getPrividerId(), weixinProperties.getAppId(), weixinProperties.getAppSecret());
		connectionFactoryConfigurer.addConnectionFactory(weixinConnectionFactory);
	}
	
}
<a href="/qqLogin/weixin" class="login100-social-item bg1">
							<i class="fa fa-wechat"></i>
						</a>

8、完成添加数据表visitor和表userconnection

package com.zzz.blog.social.qq.config;

import javax.sql.DataSource;

import ...

@Configuration
@EnableSocial
@Order(1)
public class SocialConfig extends SocialConfigurerAdapter{
	
	@Autowired
	private DataSource dataSource;
	
	@Autowired
	private BlogSecurityProperties blogSecurityProperties;
	
	@Autowired
	private ConnectionFactoryLocator connectionFactoryLocator;
	
	@Autowired
	private ConnectionSignUp connectionSignUp;
	
	//打开ConnectController
	@Bean
	public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator,ConnectionRepository connectionRepository) {
		return new ConnectController(connectionFactoryLocator, connectionRepository);
	}
	//登录之后,直接将QQ或微信的数据保存在数据库,保存在usersconnection表和visitor表中
	@Override
	public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
		JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
		repository.setConnectionSignUp(connectionSignUp);
		return repository;
	}	
	
	//改变拦截的请求 /auth -> /qqLogin,注入到SecurityConfig中
	@Bean
	public SpringSocialConfigurer zzzSocialSecurityConfig() {
		String filterProcessesUrl = blogSecurityProperties.getQqProperties().getFilterProcessesUrl();
		ZZZSpringSocialConfigurer zzzSpringSocialConfigurer = new ZZZSpringSocialConfigurer(filterProcessesUrl);
		return zzzSpringSocialConfigurer;
	}
	
	//在注册的过程中,拿到了这个SpringSocial中的信息
	//业务完成之后,把用户的id传给了SpringSocial
	@Bean
	public ProviderSignInUtils providerSignInUtils() {
		return new ProviderSignInUtils(connectionFactoryLocator, getUsersConnectionRepository(connectionFactoryLocator));
	}
}	

使zzzSocialSecurityConfig生效

package com.zzz.blog.config;

import ...

//安全配置类
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{

	//SpringSecurity加密方法返回值
	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
	
	@Autowired
	private SpringSocialConfigurer zzzSocialSecurityConfig;
	
	//做拦截
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// 请求授权
		http.formLogin().and().authorizeRequests()
		//授权放行
		.antMatchers("/visitorLogin","/index","/mood","/findMood","/findAllBlog","/findAllAlbum","/findAllArchives","/link",
				"/css/**","/editor.md/**","/images/**","/js/**","/layer/**","/social/**","/statics/**","/upload/**").permitAll()
		//所有请求
		.anyRequest()
		//都需要身份认证
		.authenticated().and()
		//43、使用Layer打开select-mood子页面并配置SpringSecurity允许Iframe嵌入页面 
		.headers().frameOptions().disable().and()
		//跨站请求伪造的防护
		.csrf().disable()
		//添加我们所写的spring social配置
		.apply(zzzSocialSecurityConfig);
	}
	
}
package com.zzz.blog.social.qq.config;

import ...

public class ZZZSpringSocialConfigurer extends SpringSocialConfigurer{

	private String filterProcessesUrl;

	public ZZZSpringSocialConfigurer(String filterProcessesUrl) {
		this.filterProcessesUrl = filterProcessesUrl;
	}

	
	//将默认的拦截改为qqLogin
	@Override
	protected <T> T postProcess(T object) {
		//获得filter
		SocialAuthenticationFilter filter = (SocialAuthenticationFilter)super.postProcess(object);
		//设置字段
		filter.setFilterProcessesUrl(filterProcessesUrl);
		return (T) filter;
	}
	
}

创建Jdbc的userconnection表,在mysql中执行

create table UserConnection (userId varchar(255) not null,
	providerId varchar(255) not null,
	providerUserId varchar(255),
	rank int not null,
	displayName varchar(255),
	profileUrl varchar(512),
	imageUrl varchar(512),
	accessToken varchar(512) not null,
	secret varchar(512),
	refreshToken varchar(512),
	expireTime bigint,
	primary key (userId, providerId, providerUserId));
create unique index UserConnectionRank on UserConnection(userId, providerId, rank);

visitor的创建

@Entity
public class Visitor {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	private String username;
	private String password;
	private String image;

	//get/set
}
package com.zzz.blog.social.qq.signup;

import ...

@Component
public class DemoConnectionSignUp implements ConnectionSignUp{

	@Autowired
	private VisitorService visitorService;
	
	//根据社交用户的信息,创建一个Visitor并返回唯一标识
	@Override
	public String execute(Connection<?> connection) {
		
		Visitor visitor = new Visitor(null, connection.getDisplayName(), "123456", connection.getImageUrl());
		
		visitor = visitorService.saveVisitory(visitor);
		
		return visitor.getId().toString();
	}

}
package com.zzz.blog.service;

import ...

@Component
public class VisitorServiceImpl implements VisitorService{

	@Autowired
	private VisitorRepository visitorRepository;
	
	@Override
	public Visitor saveVisitory(Visitor visitor) {
		return visitorRepository.save(visitor);
	}

}
package com.zzz.blog.service;

import ...

@Service
public interface VisitorService {

	Visitor saveVisitory(Visitor visitor);

}

10、完成测试登录

因为使用测试号,系统修改hosts

127.0.0.1   www.pinzhi365.com

application.properties文件修改端口

server.port=80

登录后visitor表和userconnection表都获得用户数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值