博客之QQ登录功能(一)

流程图


上图spring social 封装了1-8步需要的工作

1、新建包和书写配置文件


java 复制代码
public class QQProperties {

	//App唯一标 识
	private String appId = "100550231";
	private String appSecret = "69b6ab57b22f3c2fe6a6149274e3295e";
	
	//QQ供应商
	private String providerId = "callback.do";
	//拦截器拦截的请求
	private String filterProcessesUrl = "/qqLogin";
	
	//get set 方法
	//...
}
java 复制代码
@ConfigurationProperties(prefix = "blog.security")
public class BlogSecurityProperties {
	
	private QQProperties qqProperties = new QQProperties();

	//get set...
}
java 复制代码
@Configuration
//让我们的配置生效
@EnableConfigurationProperties (BlogSecurityProperties.class)
public class BlogSecurityConfig {

}

2、获取QQ用户信息

java 复制代码
package com.zzz.blog.social.qq.api;

public interface QQ {

	//返回一个QQ的用户信息
	QQUserInfo getUserInfo();
}
java 复制代码
package com.zzz.blog.social.qq.api;

import org.springframework.social.oauth2.AbstractOAuth2ApiBinding;

public class QQImpl extends AbstractOAuth2ApiBinding implements QQ{

	private static final String URL_GET_USERINFO = "https://graph.qq.com/user/get_user_info?oauth_consumer_key=%s&openid=%s";
	
	//外界赋值
	private String appId;
	//用户的唯一 标识,url
	private String openId;
	
	private ObjectMapper objectMapper = new ObjectMapper();

	@Override
	public QQUserInfo getUserInfo() {
		// TODO Auto-generated method stub
		return null;
	}

}
java 复制代码
package com.zzz.blog.social.qq.api;

public class QQUserInfo {

	private String is_lost;
	private String province;
	private String city;
	private String year;
	private String constellation;
	
	private String ret;
	private String msg;
	private String nickname;
	
	private String figureurl;
	private String figureurl_1;
	private String figureurl_2;
	private String figureurl_qq_1;
	private String figureurl_qq_2;

	private String figureurl_qq;
	private String figureurl_type;
	private String gender_type;
	
	private String gender;
	private String is_yellow_vip;
	private String vip;
	private String yellow_vip_level;
	private String level;
	private String is_yellow_year_vip;
	
	private String openId;
	
	//get/set...
}

修改代码:

java 复制代码
	//获取用户信息
	@Override
	public QQUserInfo getUserInfo() {
		
		//拼接参数
		String url = String.format(URL_GET_USERINFO, appId, openId);
		//发送请求
		String result = getRestTemplate().getForObject(url, String.class);
		
		//处理返回值
		QQUserInfo userInfo = null;

			try {
				userInfo = objectMapper.readValue(result, QQUserInfo.class);
				userInfo. setOpenId(openId);
			} catch (JsonProcessingException e) {
				throw new RuntimeException(" 获取用户信息失败!");
			}

		return userInfo;
	}

3、如何获得OpenId以及AppId

java 复制代码
public class QQImpl extends AbstractOAuth2ApiBinding implements QQ{
	
	private static final String URL_GET_USERINFO = "https://graph.qq.com/user/get_user_info?pauth_consumer_key=%s&openid=%s";
	
	private static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";
	
	//外界赋值
	private String appId;
	//用户的唯一 标识,url
	private String openId;

	private ObjectMapper objectMapper = new ObjectMapper();

	public QQImpl(String accessToken, String appId) {
		//自动拼接一个参数
		super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER) ;
		//赋值appid 
		this.appId = appId;
		//赋值openid
		//通过url获得openid
		//拼接参数
		String url = String.format(URL_GET_OPENID, accessToken);
		
		//发送请求
		String result = getRestTemplate().getForObject(url, String.class);
		//处理返回值
		//callback( {"client_ id":"100550231", "openid":"CDF1A28F8698E326D173DE17437FB098"} );
		result = StringUtils.replace(result, "callback( ","");
		result = StringUtils.replace(result, " );","");
		//{"client_ id": "100550231","openid": "CDF1A28F8698E326D173DE17437FB098"}
		OpenId id = null;
		
		try {
			id = objectMapper.readValue(result, OpenId.class);
		} catch (JsonProcessingException e) {
			throw new RuntimeException( "获取OpenId失败! ! ");
		}
		
		//赋值openid
		this.openId = id.getOpenid();
	}
	
	//获取用户信息
	@Override
	public QQUserInfo getUserInfo() {
		...
	}
	
}
java 复制代码
package com.zzz.blog.social.qq.api;

public class OpenId {

	private String client_id;
	private String openid;

	//get/set
}

4、完成QQOAuth2Template

java 复制代码
package com.zzz.blog.social.qq.template;

import ...

public class QQOAuth2Template extends OAuth2Template{

	public QQOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
		super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
		//使clientId、clientSecret可以拼接到一起
		setUseParametersForClientAuthentication(true);
	}

	//添加text/html
	@Override
	protected RestTemplate createRestTemplate() {
		RestTemplate template = super.createRestTemplate();
		template.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
		
		return template;
	}

	//把请求的格式按照qq的标准,做了一些自定义信息 自己处理请求 按&分割字符,分割后如下
	//access_token=FE04***** *****************CCE2  items[0]
	//expires_in=7776000 item[0]
	//refresh_token=88E4********* **************BE14 item[1]
	@Override
	protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {
		
		String responseStr = getRestTemplate().postForObject(accessTokenUrl, parameters, String.class);
		
		//StringUtils.split只切割了一次,坑
		String[] items = StringUtils.split(responseStr, "&");
		String[] item = StringUtils.split(items[1], "&");
		
		String access_token =StringUtils.replace(items[0], "access_token=", "");
		Long expires_in = new Long(StringUtils.replace(item[0], "expires_in=", ""));
		String refresh_token = StringUtils.replace(item[1], "refresh_token=", "");
		
		
		return new AccessGrant(access_token, null, refresh_token, expires_in);
	}
	
}

5、完成QQAdapter与ServiceProvider

java 复制代码
package com.zzz.blog.social.qq.connection;

import ...

public class QQAdapter implements ApiAdapter<QQ>{

	@Override
	public boolean test(QQ api) {
		// 始终为true
		return true;
	}

	@Override
	public void setConnectionValues(QQ api, ConnectionValues values) {
		//获取userinfo
		QQUserInfo userInfo = api. getUserInfo();
		
		//获取用户名称
		values.setDisplayName(userInfo.getNickname());
		//获取头像
		values.setImageUrl(userInfo.getFigureurl_qq_1());
		//获取个人主页
		values.setProfileUrl(null);
		//openid,用户在服务商中的唯一标识
		values.setProviderUserId(userInfo.getOpenId());
		
	}

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

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

}
java 复制代码
package com.zzz.blog.social.qq.connection;

import ...

public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ>{

	//将用户导向认证服务器中的ur1地址,用户在该地址上进行授权
	private static final String URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";
	//在获取令牌的时候,需要访问的url
	private static final String URL_ACCESSTOEKN = "https://graph.qq.com/oauth2.0/token";
	
	private String appId;
	
	//1-6
	public QQServiceProvider(String appId,String appSecret) {
		super(new QQOAuth2Template(appId, appSecret, URL_AUTHORIZE, URL_ACCESSTOEKN));
		this.appId = appId;
	}

	//7-8
	@Override
	public QQ getApi(String accessToken) {
		// TODO Auto-generated method stub
		return new QQImpl(accessToken, appId);
	}
	
}

6、完成QQConfig与ConnectionFactory

java 复制代码
package com.zzz.blog.social.qq.connection;

import ...

public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ>{

	public QQConnectionFactory(String providerId, String appId,String appSecret) {
		super(providerId, new QQServiceProvider(appId, appSecret), new QQAdapter());
	}

}
java 复制代码
package com.zzz.blog.social.qq.config;

import ...

@Configuration
@EnableSocial
@Order(2)
public class QQConfig extends SocialConfigurerAdapter{

	@Autowired
	private BlogSecurityProperties blogSecurityProperties;

	//添加qq创建connection的工厂
	@Override
	public void addConnectionFactories(ConnectionFactoryConfigurer connectionFactoryConfigurer,
			Environment environment) {
		
		QQProperties qqConfig = blogSecurityProperties.getQqProperties();

		QQConnectionFactory qqConnectionFactory = new QQConnectionFactory(qqConfig.getProviderId(), qqConfig.getAppId(), qqConfig.getAppSecret());
		
		connectionFactoryConfigurer.addConnectionFactory(qqConnectionFactory);
	}
	
	//获取登陆人
	@Override
	public UserIdSource getUserIdSource() {
		return new AuthenticationNameUserIdSource();
	}
	
}

7、创建表以及创建操作表的类JdbcUsersConnectionRepository

java 复制代码
package com.zzz.blog.social.qq.config;

import ...

@Configuration
@EnableSocial
@Order(1)
public class SocialConfig extends SocialConfigurerAdapter{
	
	@Autowired
	private DataSource dataSource;
	
	//登录之后,直接将QQ的数据保存在数据库
	@Override
	public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
		JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
		return repository;
	}	
	
	//改变拦截的请求
	
	//在注册的过程中,拿到了这个SpringSocial中的信息
	//业务完成之后,把用户的id传给了SpringSocial
	
	//打开ConnectController
}

找到socailJDBC表格式,在数据库中执行sql

8、改变拦截的请求

java 复制代码
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;
	}
	
}
java 复制代码
	//改变拦截的请求 /auth -> /qqLogin
	@Bean
	public SpringSocialConfigurer zzzSocialSecurityConfig() {
		String filterProcessesUrl = blogSecurityProperties.getQqProperties().getFilterProcessesUrl();
		ZZZSpringSocialConfigurer zzzSpringSocialConfigurer = new ZZZSpringSocialConfigurer(filterProcessesUrl);
		return zzzSpringSocialConfigurer;
	}

9、将Social中的配置生效到SpringSecurity中

在SocialConfig类中添加代码

java 复制代码
	//在注册的过程中,拿到了这个SpringSocial中的信息
	//业务完成之后,把用户的id传给了SpringSocial
	@Bean
	public ProviderSignInUtils providerSignInUtils() {
		return new ProviderSignInUtils(connectionFactoryLocator, getUsersConnectionRepository(connectionFactoryLocator));
	}
	
	//打开ConnectController
	@Bean
	public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator,ConnectionRepository connectionRepository) {
		return new ConnectController(connectionFactoryLocator, connectionRepository);
	}

添加apply配置socialconfig

java 复制代码
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("/*.html").permitAll()
		//所有请求
		.anyRequest()
		//都需要身份认证
		.authenticated().and()
		//43、使用Layer打开select-mood子页面并配置SpringSecurity允许Iframe嵌入页面 
		.headers().frameOptions().disable().and()
		//跨站请求伪造的防护
		.csrf().disable()
		//添加我们所写的spring social配置
		.apply(zzzSocialSecurityConfig);
	}
	
}

10、创建Visitor实体并实现SocialUserDetailsService接口查找Visitor

java 复制代码
package com.zzz.blog.domain;

import ...

@Entity
public class Visitor {

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

	public Visitor(Long id, String username, String password, String image) {
		super();
		this.id = id;
		this.username = username;
		this.password = password;
		this.image = image;
	}

	//get/set
}
java 复制代码
package com.zzz.blog.repository;

import ...

public interface VisitorRepository extends CrudRepository<Visitor, Long>{

}
java 复制代码
@Component
public class SocialVisitorServiceImpl implements SocialUserDetailsService{

	@Autowired
	private VisitorRepository visitorRepository;
	
	@Autowired
	private PasswordEncoder passwordEncoder;
	
	@Override
	public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException {

		//根据userId查找访客
		Optional<Visitor> optional = visitorRepository.findById(new Long(userId));
		Visitor visitor = optional.get();
		if (visitor == null) {
			throw new UsernameNotFoundException(userId);
		}
		
		return new SocialUser(visitor.getUsername(), passwordEncoder.encode(visitor.getPassword()), true, true, true, true, AuthorityUtils.commaSeparatedStringToAuthorityList("VISITOR"));
	}

}

11、实现ConnectionSignUp接口添加Visitor

java 复制代码
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();
	}

}
java 复制代码
package com.zzz.blog.service;

import ...

@Service
public interface VisitorService {

	Visitor saveVisitory(Visitor visitor);

}
java 复制代码
package com.zzz.blog.service;

import ...

@Service
public interface VisitorService {

	Visitor saveVisitory(Visitor visitor);

}
java 复制代码
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);
	}

}

SocialConfig添加setConnectionSignUp执行方法

java 复制代码
	@Autowired
	private ConnectionSignUp connectionSignUp;
	
	//登录之后,直接将QQ的数据保存在数据库
	@Override
	public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
		JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
		repository.setConnectionSignUp(connectionSignUp);
		return repository;
	}

12、测试QQ登录

application.properties添加代码如下(修改端口):

复制代码
server.port=80

login.html修改超链接代码如下:

html 复制代码
						<a href="/qqLogin/callback.do" class="login100-social-item bg2">
							<i class="fa fa-qq"></i>
						</a>

修改C:\Windows\System32\drivers\etc\hosts文件

复制代码
127.0.0.1        www.pinzhi365.com

这是别人提供的测试地址。我们也可以到QQ互联官网https://connect.qq.com/上注册用户,创建应用。

其中回调地址的写法:网站地址/拦截器拦截的路径/服务提供商。

创建完修改QQProperties类上的对应配置即可。

测试通过,控制台打印了添加visitor数据的sql。

相关推荐
李慕婉学姐3 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
奋进的芋圆4 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin5 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model20055 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉5 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国5 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_941882485 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
華勳全栈6 小时前
两天开发完成智能体平台
java·spring·go
alonewolf_996 小时前
Spring MVC重点功能底层源码深度解析
java·spring·mvc
沛沛老爹6 小时前
Java泛型擦除:原理、实践与应对策略
java·开发语言·人工智能·企业开发·发展趋势·技术原理