1.QQ实现
1.1 SocialConfig
1.1.1 @EnableSocial
1.1.2 引入SecurityProperties
1.1.3 引入ConnectionSignUp
required = false
如果ConnectionSignUp 不存在则不引入
@Autowired(required = false)
private ConnectionSignUp connectionSignUp;
1.1.4 getUsersConnectionRepository
- 返回JdbcUsersConnectionRepository,用于管理数据库中的表UserConnection
- 设置connectionSignUp
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource,
connectionFactoryLocator, Encryptors.noOpText());
//repository.setTablePrefix("imooc_");
if(connectionSignUp != null) {
repository.setConnectionSignUp(connectionSignUp);
}
return repository;
}
1.1.5 imoocSocialSecurityConfig
@Bean引入ImoocSpringSocialConfigurer
1.1.6 providerSignInUtils
@Bean
创建并返回一个ProviderSignInUtils
/**
*
*/
package com.wxm.spring.security.core.social;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.social.config.annotation.EnableSocial;
import org.springframework.social.config.annotation.SocialConfigurerAdapter;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.ConnectionSignUp;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository;
import org.springframework.social.connect.web.ProviderSignInUtils;
import org.springframework.social.security.SpringSocialConfigurer;
import org.springframework.social.security.provider.OAuth2AuthenticationService;
import com.wxm.spring.security.core.properties.SecurityProperties;
/**
* @author zhailiang
*
*/
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {
//OAuth2AuthenticationService<S>
@Autowired
private DataSource dataSource;
@Autowired
private SecurityProperties securityProperties;
@Autowired(required = false)
private ConnectionSignUp connectionSignUp;
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource,
connectionFactoryLocator, Encryptors.noOpText());
if(connectionSignUp != null) {
repository.setConnectionSignUp(connectionSignUp);
}
return repository;
}
//增加社交登录的过滤器
@Bean
public SpringSocialConfigurer imoocSocialSecurityConfig() {
//return new SpringSocialConfigurer();
String filterProcessesUrl = securityProperties.getSocial().getFilterProcessesUrl();
ImoocSpringSocialConfigurer configurer = new ImoocSpringSocialConfigurer(filterProcessesUrl);
configurer.signupUrl(securityProperties.getBrowser().getSignUpUrl());
return configurer;
}
@Bean
public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator) {
return new ProviderSignInUtils(connectionFactoryLocator,
getUsersConnectionRepository(connectionFactoryLocator)) {
};
}
}
1.2 QQ
/**
*
*/
package com.wxm.spring.security.core.social.qq.api;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
/**
* @author zhailiang
*
*/
public interface QQ {
QQUserInfo getUserInfo() ;
}
1.3 QQImpl
- extends AbstractOAuth2ApiBinding implements QQ
- QQImpl
对应第6步,用accessToken获取用户openId - getUserInfo
对应第6步,用openId获取用户信息
package com.wxm.spring.security.core.social.qq.api;
import static org.assertj.core.api.Assertions.setAllowComparingPrivateFields;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.social.oauth2.AbstractOAuth2ApiBinding;
import org.springframework.social.oauth2.TokenStrategy;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class QQImpl extends AbstractOAuth2ApiBinding implements QQ {
private Logger logger = LoggerFactory.getLogger(getClass());
private ObjectMapper objectMapper = new ObjectMapper();
private static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";
//access_token由父类AbstractOAuth2ApiBinding处理,故在URL_GET_USERINFO中删掉他
private static final String URL_GET_USERINFO = "https://graph.qq.com/user/get_user_info?oauth_consumer_key=%s&openid=%s";
private String appId;
private String openId;
public QQImpl(String accessToken,String appId) {
//ACCESS_TOKEN_PARAMETER指把accessToken放到url参数中,默认是ACCESS_TOKEN_HEADER,放到http头中
super(accessToken,TokenStrategy.ACCESS_TOKEN_PARAMETER);
this.appId = appId;
String url = String.format(URL_GET_OPENID, accessToken);
String result = getRestTemplate().getForObject(url, String.class);
logger.info(result);
this.openId = StringUtils.substringBetween(result, "\"openid\":","}");
}
@Override
public QQUserInfo getUserInfo(){
String url = String.format(URL_GET_USERINFO, appId,openId);
String result = getRestTemplate().getForObject(url, String.class);
logger.info(result);
try {
return objectMapper.readValue(result, QQUserInfo.class);
} catch (Exception e) {
throw new RuntimeException("获取用户信息失败");
}
}
}
1.4 QQUserInfo
保存用户信息
1.5 QQAdapter
1.5.1 implements ApiAdapter
1.5.2 setConnectionValues
- 调用api.getUserInfo()获取QQUserInfo
- 用QQUserInfo初始化ConnectionValues
/**
*
*/
package com.wxm.spring.security.core.social.qq.connect;
import org.springframework.social.connect.ApiAdapter;
import org.springframework.social.connect.ConnectionValues;
import org.springframework.social.connect.UserProfile;
import com.wxm.spring.security.core.social.qq.api.QQ;
import com.wxm.spring.security.core.social.qq.api.QQUserInfo;
/**
* @author zhailiang
*
*/
public class QQAdapter implements ApiAdapter<QQ> {
@Override
public boolean test(QQ api) {
return true;
}
@Override
public void setConnectionValues(QQ api, ConnectionValues values) {
QQUserInfo userInfo = api.getUserInfo();
values.setDisplayName(userInfo.getNickname());
values.setImageUrl(userInfo.getFigureurl_qq_1());
values.setProfileUrl(null); //个人主页
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) {
//do noting
}
}
1.6 QQConnectionFactory
1.6.1 extends OAuth2ConnectionFactory
1.6.2 初始化
- providerId
- 创建QQServiceProvider
- 创建QQAdapter
- 初始化
/**
*
*/
package com.wxm.spring.security.core.social.qq.connect;
import org.springframework.social.connect.support.OAuth2ConnectionFactory;
import com.wxm.spring.security.core.social.qq.api.QQ;
public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> {
public QQConnectionFactory(String providerId, String appId, String appSecret) {
super(providerId, new QQServiceProvider(appId, appSecret), new QQAdapter());
}
}
1.7 QQOAuth2Template
1.7.1 初始化
- super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
- setUseParametersForClientAuthentication(true);
此处设置为true,才会在第4步中申请令牌时,传递client_id和client_secret过去,qq要求要有这两项,若为false,则值传递grant_type、code(第3步,由qq认证服务器回传回来的授权码,由于获取accessToken)、redirect_url
1.7.2 postForAccessGrant
此对应OAuth2第4步,申请AccessToken,qq授权服务器返回accessToken、expiresIn、refreshToken,将这3个参数封装为AccessGrant,并返回
1.7.3 createRestTemplate
restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName(“UTF-8”)));
增加对Html/text的转换支持
/**
*
*/
package com.wxm.spring.security.core.social.qq.connect;
import java.nio.charset.Charset;
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.OAuth2Template;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
public class QQOAuth2Template extends OAuth2Template {
private Logger logger = LoggerFactory.getLogger(getClass());
public QQOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
//此处设置为true,才会在第4步中申请令牌时,传递client_id和client_secret过去,qq要求要有这两项,若为false,则值传递grant_type、code(第3步,由qq认证服务器回传回来的授权码,由于获取accessToken)、redirect_url
setUseParametersForClientAuthentication(true);
}
@Override
protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {
String responseStr = getRestTemplate().postForObject(accessTokenUrl, parameters, String.class);
logger.info("获取accessToke的响应:"+responseStr);
String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(responseStr, "&");
String accessToken = StringUtils.substringAfterLast(items[0], "=");
Long expiresIn = new Long(StringUtils.substringAfterLast(items[1], "="));
String refreshToken = StringUtils.substringAfterLast(items[2], "=");
return new AccessGrant(accessToken, null, refreshToken, expiresIn);
}
@Override
protected RestTemplate createRestTemplate() {
RestTemplate restTemplate = super.createRestTemplate();
restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
return restTemplate;
}
}
1.8 QQServiceProvider
1.8.1 extends AbstractOAuth2ServiceProvider
1.8.2 getApi
创建并返回QQImpl
/**
*
*/
package com.wxm.spring.security.core.social.qq.connect;
import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider;
import com.wxm.spring.security.core.social.qq.api.QQ;
import com.wxm.spring.security.core.social.qq.api.QQImpl;
/**
* @author zhailiang
*
*/
public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> {
private String appId;
private static final String URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";
private static final String URL_ACCESS_TOKEN = "https://graph.qq.com/oauth2.0/token";
public QQServiceProvider(String appId, String appSecret) {
super(new QQOAuth2Template(appId, appSecret, URL_AUTHORIZE, URL_ACCESS_TOKEN));
this.appId = appId;
}
@Override
public QQ getApi(String accessToken) {
return new QQImpl(accessToken, appId);
}
}
1.9 QQAutoConfig
- extends SocialAutoConfigurerAdapter
- createConnectionFactory
用ProviderId、AppId、AppSecret创建ConnectionFactory并返回 - @ConditionalOnProperty(prefix = “imooc.security.social.qq”, name = “app-id”)
配置文件中有imooc.security.social.qq.app-id项目时,才调用本配置类
/**
*
*/
package com.wxm.spring.security.core.qq.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.social.connect.ConnectionFactory;
import com.wxm.spring.security.core.properties.QQProperties;
import com.wxm.spring.security.core.properties.SecurityProperties;
import com.wxm.spring.security.core.social.SocialAutoConfigurerAdapter;
import com.wxm.spring.security.core.social.qq.connect.QQConnectionFactory;
/**
* @author zhailiang
*
*/
@Configuration
//application.prefix中由配置项imooc.security.social.qq.app-id时,才生效
@ConditionalOnProperty(prefix = "imooc.security.social.qq", name = "app-id")
public class QQAutoConfig extends SocialAutoConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties;
/*
* (non-Javadoc)
*
* @see
* org.springframework.boot.autoconfigure.social.SocialAutoConfigurerAdapter
* #createConnectionFactory()
*/
@Override
protected ConnectionFactory<?> createConnectionFactory() {
QQProperties qqConfig = securityProperties.getSocial().getQq();
return new QQConnectionFactory(qqConfig.getProviderId(), qqConfig.getAppId(), qqConfig.getAppSecret());
}
}
1.10 ImoocSpringSocialConfigurer
1.10.1 extends SpringSocialConfigurer
1.10.2 postProcess
创建SocialAuthenticationFilter,设置其filterProcessesUrl
1.11 BrowserSecurityConfig
configure中
http.apply(imoocSocialSecurityConfig)
1.12 DemoConnectionSignUp
qq登录后自动注册
1.基础框架
1. Connection
包含用户认证信息
2. ConnectionFactory
用于与服务器端交互,获取用户信息,并封装为一个Connection
包含ServiceProvider和ApiAdapter
2.1 ServiceProvider
与服务端交互,完成第1-6步,包含:OAuth2Operations和Api
2.1.1 OAuth2Operations
实现1-5步功能
2.1.2 Api
实现第6步功能,与客户端交互,提供用户认证信息
基础实现类:AbstractOAuth2ApiBinding,包含:
accessToken:令牌
restTemplate:步骤1-6中,用于发送http请求
2.2 ApiAdapter
将第六步获取到的用户认证信息,转换为Connection
3. DB UserConnection表
数据库中的表,存储Connection中的用户信息
4. UsersConnectionRepository
操作UserConnection表
/**
*
*/
package com.wxm.spring.security.core.social;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.social.config.annotation.EnableSocial;
import org.springframework.social.config.annotation.SocialConfigurerAdapter;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.ConnectionSignUp;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository;
import org.springframework.social.connect.web.ProviderSignInUtils;
import org.springframework.social.security.SpringSocialConfigurer;
import com.wxm.spring.security.core.properties.SecurityProperties;
/**
* @author zhailiang
*
*/
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private SecurityProperties securityProperties;
@Autowired(required = false)
private ConnectionSignUp connectionSignUp;
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource,
connectionFactoryLocator, Encryptors.noOpText());
repository.setTablePrefix("spring_security_social_");
if(connectionSignUp != null) {
repository.setConnectionSignUp(connectionSignUp);
}
return repository;
}
@Bean
public SpringSocialConfigurer imoocSocialSecurityConfig() {
String filterProcessesUrl = securityProperties.getSocial().getFilterProcessesUrl();
ImoocSpringSocialConfigurer configurer = new ImoocSpringSocialConfigurer(filterProcessesUrl);
configurer.signupUrl(securityProperties.getBrowser().getSignUpUrl());
return configurer;
}
@Bean
public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator) {
return new ProviderSignInUtils(connectionFactoryLocator,
getUsersConnectionRepository(connectionFactoryLocator)) {
};
}
}
2. QQ
-- This SQL contains a "create table" that can be used to create a table that JdbcUsersConnectionRepository can persist
-- connection in. It is, however, not to be assumed to be production-ready, all-purpose SQL. It is merely representative
-- of the kind of table that JdbcUsersConnectionRepository works with. The table and column names, as well as the general
-- column types, are what is important. Specific column types and sizes that work may vary across database vendors and
-- the required sizes may vary across API providers.
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);
2.1 增加SocialConfig
- @EnableSocial
2 getUsersConnectionRepository
/**
*
*/
package com.wxm.spring.security.core.social;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.social.config.annotation.EnableSocial;
import org.springframework.social.config.annotation.SocialConfigurerAdapter;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.ConnectionSignUp;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository;
import org.springframework.social.connect.web.ProviderSignInUtils;
import org.springframework.social.security.SpringSocialConfigurer;
import com.wxm.spring.security.core.properties.SecurityProperties;
/**
* @author zhailiang
*
*/
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private SecurityProperties securityProperties;
@Autowired(required = false)
private ConnectionSignUp connectionSignUp;
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource,
connectionFactoryLocator, Encryptors.noOpText());
//repository.setTablePrefix("imooc_");
if(connectionSignUp != null) {
repository.setConnectionSignUp(connectionSignUp);
}
return repository;
}
@Bean
public SpringSocialConfigurer imoocSocialSecurityConfig() {
String filterProcessesUrl = securityProperties.getSocial().getFilterProcessesUrl();
ImoocSpringSocialConfigurer configurer = new ImoocSpringSocialConfigurer(filterProcessesUrl);
configurer.signupUrl(securityProperties.getBrowser().getSignUpUrl());
return configurer;
}
@Bean
public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator) {
return new ProviderSignInUtils(connectionFactoryLocator,
getUsersConnectionRepository(connectionFactoryLocator)) {
};
}
}
2.2 扩展MyUserDetailsService
2.2.1 implements SocialUserDetailsService
实现loadUserByUserId方法
https://blog.csdn.net/nrsc272420199/article/details/99686809
2.3 创建QQAutoCOnfig
在createConnectionFactory中,用配置文件中的appId、appSecret和providerId创建QQConnectionFactory
2.4 扩展SocialCOnfig
- 增加社交登录的过滤器
//增加社交登录的过滤器
@Bean
public SpringSocialConfigurer imoocSocialSecurityConfig() {
String filterProcessesUrl = securityProperties.getSocial().getFilterProcessesUrl();
ImoocSpringSocialConfigurer configurer = new ImoocSpringSocialConfigurer(filterProcessesUrl);
configurer.signupUrl(securityProperties.getBrowser().getSignUpUrl());
return configurer;
}
- 在browser的BrowserSecurityConfig中引入SpringSocialConfigurer imoocSocialSecurityConfig,加到configure方法中的路由链表中
@Autowired
private SpringSocialConfigurer imoocSocialSecurityConfig;
.apply(imoocSocialSecurityConfig)
2.5 增加ImoocSpringSocialConfigurer
- 继承SpringSocialConfigurer
- 重载postProcess
spring在把filter插入到过滤器链之前被调用该函数,
filterProcessesUrl对应qq认证回调url中的"auth"部分,默认是"auth",在此处把它设置为别的值
例如:http://www.ictgu.cn/auth/QQ
把filterProcessesUrl设置为login,则qq认证回调变为:http://www.ictgu.cn/login/QQ
2.6 修改providerId
对应qq认证回调url中的"QQ"部分,在application.properties中设置:
imooc.security.social.qq.providerId = QQ
SocialProperties读取它,QQAutoConfig中使用providerId、appId和appSecret创建QQConnectionFactory
2.7 QQOAuth2Template
extends OAuth2Template
- createRestTemplate
默认的OAuth2Template,处理回传contentType为text/json格式,而qq认证服务器返回的是text/html格式,所以自定义一个QQOAuth2Template类,在createRestTemplate中添加对text/html字符串格式返回值得处理
@Override
protected RestTemplate createRestTemplate() {
RestTemplate restTemplate = super.createRestTemplate();
restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
return restTemplate;
}
- postForAccessGrant
默认的OAuth2Template,按json格式处理回传数据,获取access_token、scope、refresh_token、expires_in4个字段,用这4个字段创建AccessGrant,但qq认证服务器返回的是一个用“&”分隔开的一个字符串,所以要自定义一个对这个字符串的处理,抽取出4个变量,创建AccessGrant
@Override
protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {
String responseStr = getRestTemplate().postForObject(accessTokenUrl, parameters, String.class);
logger.info("获取accessToke的响应:"+responseStr);
String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(responseStr, "&");
String accessToken = StringUtils.substringAfterLast(items[0], "=");
Long expiresIn = new Long(StringUtils.substringAfterLast(items[1], "="));
String refreshToken = StringUtils.substringAfterLast(items[2], "=");
return new AccessGrant(accessToken, null, refreshToken, expiresIn);
}
- setUseParametersForClientAuthentication
此处设置为true,才会在第4步中申请令牌时,传递client_id和client_secret过去,qq要求要有这两项,若为false,则值传递grant_type、code(第3步,由qq认证服务器回传回来的授权码,由于获取accessToken)、redirect_url
public QQOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
//此处设置为true,才会在第4步中申请令牌时,传递client_id和client_secret过去,qq要求要有这两项,若为false,则值传递grant_type、code(第3步,由qq认证服务器回传回来的授权码,由于获取accessToken)、redirect_url
setUseParametersForClientAuthentication(true);
}
- 在QQServiceProvider中替换原生的OAuth2Template
public QQServiceProvider(String appId, String appSecret) {
super(new QQOAuth2Template(appId, appSecret, URL_AUTHORIZE, URL_ACCESS_TOKEN));
this.appId = appId;
}
2.8 获取用户信息
在QQImpl类中实现
- extends AbstractOAuth2ApiBinding implements QQ
3. 注册
3.1 SocialAuthenticationProvider
3.1.1 注册流程
- authenticate方法在收到Authentication后,从中抽取出Connection,用其调用toUserId
toUserId会用userConnectionRepository.findUserIdsWithConnection(connection)去获取userId,这一步是去数据库表(UserConnection,之前用sql语句建的)中找userId
如果没找到userId,则抛出 BadCredentialsException异常,该异常被SocialAuthenticationFilter捕获
SocialAuthenticationFilter会把请求转发到signupUrl(默认为/signup)
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.social.security;
import java.util.Collection;
import java.util.List;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.social.ServiceProvider;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.util.Assert;
/**
* {@link AuthenticationProvider} for spring-social based {@link ServiceProvider}s
*
* @author Stefan Fussennegger
* @author Yuan Ji
*/
public class SocialAuthenticationProvider implements AuthenticationProvider {
private UsersConnectionRepository usersConnectionRepository;
private SocialUserDetailsService userDetailsService;
public SocialAuthenticationProvider(UsersConnectionRepository usersConnectionRepository, SocialUserDetailsService userDetailsService) {
this.usersConnectionRepository = usersConnectionRepository;
this.userDetailsService = userDetailsService;
}
public boolean supports(Class<? extends Object> authentication) {
return SocialAuthenticationToken.class.isAssignableFrom(authentication);
}
/**
* Authenticate user based on {@link SocialAuthenticationToken}
*/
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(SocialAuthenticationToken.class, authentication, "unsupported authentication type");
Assert.isTrue(!authentication.isAuthenticated(), "already authenticated");
SocialAuthenticationToken authToken = (SocialAuthenticationToken) authentication;
String providerId = authToken.getProviderId();
Connection<?> connection = authToken.getConnection();
String userId = toUserId(connection);
if (userId == null) {
throw new BadCredentialsException("Unknown access token");
}
UserDetails userDetails = userDetailsService.loadUserByUserId(userId);
if (userDetails == null) {
throw new UsernameNotFoundException("Unknown connected account id");
}
return new SocialAuthenticationToken(connection, userDetails, authToken.getProviderAccountData(), getAuthorities(providerId, userDetails));
}
protected String toUserId(Connection<?> connection) {
List<String> userIds = usersConnectionRepository.findUserIdsWithConnection(connection);
// only if a single userId is connected to this providerUserId
return (userIds.size() == 1) ? userIds.iterator().next() : null;
}
/**
* Override to grant authorities based on {@link ServiceProvider} id and/or a user's account id
* @param providerId {@link ServiceProvider} id
* @param userDetails {@link UserDetails} as returned by {@link SocialUserDetailsService}
* @return extra authorities of this user not already returned by {@link UserDetails#getAuthorities()}
*/
protected Collection<? extends GrantedAuthority> getAuthorities(String providerId, UserDetails userDetails) {
return userDetails.getAuthorities();
}
}
3.1.2 实现注册页
3.1.2.1 browser:imooc-signUp.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h2>标准注册页面</h2>
<h3>这是系统注册页面,请配置imooc.security.browser.signUpUrl属性来设置自己的注册页</h3>
</body>
</html>
3.1.2.2 BrowserProperties中添加signUpUrl属性
3.1.2.3 在demo:application.properties中添加signUpUrl属性
imooc.security.browser.signUpUrl = /demo-signUp.html
3.1.2.4 在demo:添加demo-signUp.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h2>Demo注册页</h2>
<form action="/user/regist" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td colspan="2">
<button type="submit" name="type" value="regist">注册</button>
<button type="submit" name="type" value="binding">绑定</button>
</td>
</tr>
</table>
</form>
</body>
</html>
3.1.2.5 在UserController中添加regist
@PostMapping("/regist")
public void regist(User user, HttpServletRequest request) {
//不管是注册用户还是绑定用户,都会拿到一个用户唯一标识。
/*
String userId = user.getUsername();
providerSignInUtils.doPostSignUp(userId, new ServletWebRequest(request));
*/
}
3.1.2.6 在BrowserSecurityConfig.configure中增加路由配置
http.
...
securityProperties.getBrowser().getSignUpUrl(),
...
.permitAll()
3.1.2.7 在SocialConfig中设置signupUrl
configurer.signupUrl(securityProperties.getBrowser().getSignUpUrl());
//增加社交登录的过滤器
@Bean
public SpringSocialConfigurer imoocSocialSecurityConfig() {
//return new SpringSocialConfigurer();
String filterProcessesUrl = securityProperties.getSocial().getFilterProcessesUrl();
ImoocSpringSocialConfigurer configurer = new ImoocSpringSocialConfigurer(filterProcessesUrl);
configurer.signupUrl(securityProperties.getBrowser().getSignUpUrl());
return configurer;
}
3.1.3 实现用户注册信息写入到数据库
ProviderSignInUtils实现
3.1.3.1 在SocialConfig中引入ProviderSignInUtils
ConnectionFactoryLocator参数用于定位ConnectionFactory
@Bean
public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator) {
return new ProviderSignInUtils(connectionFactoryLocator,
getUsersConnectionRepository(connectionFactoryLocator)) {
};
}
3.1.3.2 创建SocialUserInfo
包含用户信息
3.1.3.3 扩展BrowserSecurityController
- 引入ProviderSignInUtils
- 增加@GetMapping("/social/user")
调用providerSignInUtils.getConnectionFromSession,从session中获取Connection信息,用该Connection生成UserInfo,这样在注册页中发送"/social/user"请求,就能抽取出授权登录的qq用户信息了
session中的Connection是什么时候放进去的?SocialAuthenticationFilter中,捕获到BadCredentialsException时
3.1.3.4 扩展UserController
- 引入ProviderSignInUtils
- 添加“regist”
此处将用户信息插入到数据库中
@PostMapping("/regist")
public void regist(User user, HttpServletRequest request) {
//不管是注册用户还是绑定用户,都会拿到一个用户唯一标识。
String userId = user.getUsername();
//将用户信息插入数据库
providerSignInUtils.doPostSignUp(userId, new ServletWebRequest(request));
}
- 将“/user/regist”添加到BrowserSecurityConfig.configure路由配置中
3.1.4 实现qq授权后跳过注册页,直接登录
3.1.4.1 DemoConnectionSignUp
根据社交用户信息默认创建用户并返回用户唯一标识
3.1.4.2 SocialConfig
- 引入ConnectionSignUp
@Autowired(required = false)表示ConnectionSignUp不存在时,则不引入
@Autowired(required = false)
private ConnectionSignUp connectionSignUp;
- 修改getUsersConnectionRepository
if(connectionSignUp != null) {
repository.setConnectionSignUp(connectionSignUp);
}
关于回调地址
在一次认证过程中,会被调用2次
- 第1次对应第3步,用户点击表单中的qq登录时,将用户导向到qq登录授权页面,所以表单中的超链接应该是授权回调地址(http://www.ictgu.cn/login/qq)
- 第2次对应第3步,用户同意授权后,qq认证服务器回调该地址,返回clientToken
0. 测试用appId和appSecret
https://my.oschina.net/liuyuantao/blog/1931013
http://www.spring4all.com/article/424
APP ID 101386962
APP Key 2a0f820407df400b84a854d054be8b6a
回调地址 http://www.ictgu.cn/login/qq