流程图
上图spring social 封装了1-8步需要的工作
public class QQProperties {
//App唯一标 识
private String appId = "100550231";
private String appSecret = "69b6ab57b22f3c2fe6a6149274e3295e";
//QQ供应商
private String providerId = "callback.do";
//拦截器拦截的请求
private String filterProcessesUrl = "/qqLogin";
//get set 方法
//...
}
@ConfigurationProperties(prefix = "blog.security")
public class BlogSecurityProperties {
private QQProperties qqProperties = new QQProperties();
//get set...
}
@Configuration
//让我们的配置生效
@EnableConfigurationProperties (BlogSecurityProperties.class)
public class BlogSecurityConfig {
}
package com.zzz.blog.social.qq.api;
public interface QQ {
//返回一个QQ的用户信息
QQUserInfo getUserInfo();
}
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;
}
}
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...
}
修改代码:
//获取用户信息
@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;
}
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() {
...
}
}
package com.zzz.blog.social.qq.api;
public class OpenId {
private String client_id;
private String openid;
//get/set
}
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);
}
}
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
}
}
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);
}
}
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());
}
}
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();
}
}
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
}
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;
}
}
//改变拦截的请求 /auth -> /qqLogin
@Bean
public SpringSocialConfigurer zzzSocialSecurityConfig() {
String filterProcessesUrl = blogSecurityProperties.getQqProperties().getFilterProcessesUrl();
ZZZSpringSocialConfigurer zzzSpringSocialConfigurer = new ZZZSpringSocialConfigurer(filterProcessesUrl);
return zzzSpringSocialConfigurer;
}
在SocialConfig类中添加代码
//在注册的过程中,拿到了这个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
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);
}
}
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
}
package com.zzz.blog.repository;
import ...
public interface VisitorRepository extends CrudRepository<Visitor, Long>{
}
@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"));
}
}
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 ...
@Service
public interface VisitorService {
Visitor saveVisitory(Visitor visitor);
}
package com.zzz.blog.service;
import ...
@Service
public interface VisitorService {
Visitor saveVisitory(Visitor visitor);
}
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执行方法
@Autowired
private ConnectionSignUp connectionSignUp;
//登录之后,直接将QQ的数据保存在数据库
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
repository.setConnectionSignUp(connectionSignUp);
return repository;
}
application.properties添加代码如下(修改端口):
server.port=80
login.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。