oauth協(xié)議簡(jiǎn)介
授權(quán)碼模式流程:
授權(quán)碼模式的特殊之處在于:用戶同意授權(quán)的動(dòng)作是在認(rèn)證服務(wù)器上完成的,其他的模式是在第三方上完成的驳规,這樣避免了偽造用戶授權(quán)。
授權(quán)碼模式也是功能最完整,最嚴(yán)密的模式旭蠕,也是使用最廣泛的協(xié)議。
總結(jié):就是為了不用將用戶的用戶名和密碼交給第三方應(yīng)用旷坦,就可以有權(quán)限用戶存在服務(wù)器上的一些資源掏熬。
上圖就是整個(gè)第三方登錄的整個(gè)流程,Spring Social將這個(gè)流程封裝到了SocialAuthenticationFilter中秒梅。
上圖是整個(gè)第三方登錄的代碼流程
1旗芬、ServiceProvider:服務(wù)提供商的抽象,如使用qq,wx就需要提供不同的實(shí)現(xiàn)類
2捆蜀、OAuth2Operations:封裝了邏輯流程圖中1-5步疮丛,因?yàn)?-5步是固定的,所以Spring Social提供了默認(rèn)實(shí)現(xiàn)類OAuth2Template
3辆它、Api :封裝了第6步的服務(wù)提供商的信息
4誊薄、Connection:封裝前6步獲取到的用戶信息。我們使用的就是OAuth2Connection锰茉。
5呢蔫、ConnectionFactory: 創(chuàng)建Connection時(shí),先調(diào)用serviceProvider去訪問服務(wù)提供商接口洞辣,得到信息咐刨。
6、ApiAdapter:Connection的字段都是固定的扬霜,但是每個(gè)服務(wù)提供商提供的字段都是不一樣的定鸟,在Connection和服務(wù)提供商提供的字段進(jìn)行轉(zhuǎn)換匹配,就需要使用ApiAdapter
7著瓶、UsersConnectionRepository:封裝的Connection和自己業(yè)務(wù)中的user怎么一一對(duì)應(yīng)呢联予?它們的對(duì)應(yīng)關(guān)系存儲(chǔ)在了UsersConnection這張表當(dāng)中,使用UsersConnectionRepository進(jìn)行查詢材原。
@Data
public class QQUserInfo {
/**
* 返回碼
*/
private String ret;
/**
* 如果ret<0沸久,會(huì)有相應(yīng)的錯(cuò)誤信息提示,返回?cái)?shù)據(jù)全部用UTF-8編碼余蟹。
*/
private String msg;
/**
*
*/
private String openId;
/**
* 不知道什么東西卷胯,文檔上沒寫,但是實(shí)際api返回里有威酒。
*/
private String is_lost;
/**
* 省(直轄市)
*/
private String province;
/**
* 市(直轄市區(qū))
*/
private String city;
/**
* 出生年月
*/
private String year;
/**
* 用戶在QQ空間的昵稱窑睁。
*/
private String nickname;
/**
* 大小為30×30像素的QQ空間頭像URL挺峡。
*/
private String figureurl;
/**
* 大小為50×50像素的QQ空間頭像URL。
*/
private String figureurl_1;
/**
* 大小為100×100像素的QQ空間頭像URL担钮。
*/
private String figureurl_2;
/**
* 大小為40×40像素的QQ頭像URL橱赠。
*/
private String figureurl_qq_1;
/**
* 大小為100×100像素的QQ頭像URL。需要注意箫津,不是所有的用戶都擁有QQ的100×100的頭像狭姨,但40×40像素則是一定會(huì)有。
*/
private String figureurl_qq_2;
/**
* 性別苏遥。 如果獲取不到則默認(rèn)返回”男”
*/
private String gender;
/**
* 標(biāo)識(shí)用戶是否為黃鉆用戶(0:不是饼拍;1:是)。
*/
private String is_yellow_vip;
/**
* 標(biāo)識(shí)用戶是否為黃鉆用戶(0:不是暖眼;1:是)
*/
private String vip;
/**
* 黃鉆等級(jí)
*/
private String yellow_vip_level;
/**
* 黃鉆等級(jí)
*/
private String level;
/**
* 標(biāo)識(shí)是否為年費(fèi)黃鉆用戶(0:不是惕耕; 1:是)
*/
private String is_yellow_year_vip;
}
public interface QQ {
QQUserInfo getQQUserInfo ();
}
public class QQImpl extends AbstractOAuth2ApiBinding implements QQ {
private String appId;
private String openid;
private static final String URL_GET_OPENID="https://graph.qq.com/oauth2.0/me?access_token=s%";
private static final String URL_GET_USERINFO="https://graph.qq.com/user/get_user_info?oauth_consumer_key=s%&openid=s%";
private ObjectMapper objectMapper=new ObjectMapper();
public QQImpl(String accessToken,String appId) {
super(accessToken,TokenStrategy.ACCESS_TOKEN_PARAMETER);
this.appId=appId;
String url=String.format(URL_GET_OPENID, accessToken);
String result=getRestTemplate().getForObject(url, String.class);
this.openid=StringUtils.substringBetween(result, "\"openid\"","}");
}
@Override
public QQUserInfo getQQUserInfo(){
String url=String.format(URL_GET_USERINFO, appId, openid);
String result=getRestTemplate().getForObject(url, String.class);
try {
return objectMapper.readValue(result, QQUserInfo.class);
} catch (Exception e) {
throw new RuntimeException("獲取用戶信息失敗",e);
}
}
}
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.getQQUserInfo();
values.setDisplayName(userInfo.getNickname());
values.setImageUrl(userInfo.getFigureurl_qq_1());
values.setProfileUrl(null);
values.setProviderUserId(userInfo.getOpenId());
}
@Override
public UserProfile fetchUserProfile(QQ api) {
return null;
}
@Override
public void updateStatus(QQ api, String message) {
}
}
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 OAuth2Template(appId, appSecret, URL_AUTHORIZE, URL_ACCESS_TOKEN));
}
@Override
public QQ getApi(String accessToken) {
return new QQImpl(accessToken, appId);
}
}
public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> {
public QQConnectionFactory(String providerId,String appId,String appSecret) {
super(providerId, new QQServiceProvider(appId, appSecret), new QQAdapter());
}
}
由于我用的mysql8.0所以提供的UserConnection建表語句纺裁,rank字段不能使用诫肠,只好重寫JdbcConnectionRepository
public class RavenJdbcConnectionRepository implements ConnectionRepository {
private final String userId;
private final JdbcTemplate jdbcTemplate;
private final ConnectionFactoryLocator connectionFactoryLocator;
private final TextEncryptor textEncryptor;
private final String tablePrefix;
public RavenJdbcConnectionRepository(String userId, JdbcTemplate jdbcTemplate,
ConnectionFactoryLocator connectionFactoryLocator, TextEncryptor textEncryptor, String tablePrefix) {
this.userId = userId;
this.jdbcTemplate = jdbcTemplate;
this.connectionFactoryLocator = connectionFactoryLocator;
this.textEncryptor = textEncryptor;
this.tablePrefix = tablePrefix;
}
public MultiValueMap<String, Connection<?>> findAllConnections() {
List<Connection<?>> resultList = jdbcTemplate.query(
selectFromUserConnection() + " where userId = ? order by providerId, grade", connectionMapper, userId);
MultiValueMap<String, Connection<?>> connections = new LinkedMultiValueMap<String, Connection<?>>();
Set<String> registeredProviderIds = connectionFactoryLocator.registeredProviderIds();
for (String registeredProviderId : registeredProviderIds) {
connections.put(registeredProviderId, Collections.<Connection<?>>emptyList());
}
for (Connection<?> connection : resultList) {
String providerId = connection.getKey().getProviderId();
if (connections.get(providerId).size() == 0) {
connections.put(providerId, new LinkedList<Connection<?>>());
}
connections.add(providerId, connection);
}
return connections;
}
public List<Connection<?>> findConnections(String providerId) {
return jdbcTemplate.query(selectFromUserConnection() + " where userId = ? and providerId = ? order by grade",
connectionMapper, userId, providerId);
}
@SuppressWarnings("unchecked")
public <A> List<Connection<A>> findConnections(Class<A> apiType) {
List<?> connections = findConnections(getProviderId(apiType));
return (List<Connection<A>>) connections;
}
public MultiValueMap<String, Connection<?>> findConnectionsToUsers(MultiValueMap<String, String> providerUsers) {
if (providerUsers == null || providerUsers.isEmpty()) {
throw new IllegalArgumentException("Unable to execute find: no providerUsers provided");
}
StringBuilder providerUsersCriteriaSql = new StringBuilder();
MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("userId", userId);
for (Iterator<Entry<String, List<String>>> it = providerUsers.entrySet().iterator(); it.hasNext();) {
Entry<String, List<String>> entry = it.next();
String providerId = entry.getKey();
providerUsersCriteriaSql.append("providerId = :providerId_").append(providerId)
.append(" and providerUserId in (:providerUserIds_").append(providerId).append(")");
parameters.addValue("providerId_" + providerId, providerId);
parameters.addValue("providerUserIds_" + providerId, entry.getValue());
if (it.hasNext()) {
providerUsersCriteriaSql.append(" or ");
}
}
List<Connection<?>> resultList = new NamedParameterJdbcTemplate(jdbcTemplate).query(selectFromUserConnection()
+ " where userId = :userId and " + providerUsersCriteriaSql + " order by providerId, grade", parameters,
connectionMapper);
MultiValueMap<String, Connection<?>> connectionsForUsers = new LinkedMultiValueMap<String, Connection<?>>();
for (Connection<?> connection : resultList) {
String providerId = connection.getKey().getProviderId();
List<String> userIds = providerUsers.get(providerId);
List<Connection<?>> connections = connectionsForUsers.get(providerId);
if (connections == null) {
connections = new ArrayList<Connection<?>>(userIds.size());
for (int i = 0; i < userIds.size(); i++) {
connections.add(null);
}
connectionsForUsers.put(providerId, connections);
}
String providerUserId = connection.getKey().getProviderUserId();
int connectionIndex = userIds.indexOf(providerUserId);
connections.set(connectionIndex, connection);
}
return connectionsForUsers;
}
public Connection<?> getConnection(ConnectionKey connectionKey) {
try {
return jdbcTemplate.queryForObject(
selectFromUserConnection() + " where userId = ? and providerId = ? and providerUserId = ?",
connectionMapper, userId, connectionKey.getProviderId(), connectionKey.getProviderUserId());
} catch (EmptyResultDataAccessException e) {
throw new NoSuchConnectionException(connectionKey);
}
}
@SuppressWarnings("unchecked")
public <A> Connection<A> getConnection(Class<A> apiType, String providerUserId) {
String providerId = getProviderId(apiType);
return (Connection<A>) getConnection(new ConnectionKey(providerId, providerUserId));
}
@SuppressWarnings("unchecked")
public <A> Connection<A> getPrimaryConnection(Class<A> apiType) {
String providerId = getProviderId(apiType);
Connection<A> connection = (Connection<A>) findPrimaryConnection(providerId);
if (connection == null) {
throw new NotConnectedException(providerId);
}
return connection;
}
@SuppressWarnings("unchecked")
public <A> Connection<A> findPrimaryConnection(Class<A> apiType) {
String providerId = getProviderId(apiType);
return (Connection<A>) findPrimaryConnection(providerId);
}
@Transactional
public void addConnection(Connection<?> connection) {
try {
ConnectionData data = connection.createData();
int grade = jdbcTemplate.queryForObject(
"select coalesce(max(grade) + 1, 1) as grade from " + tablePrefix
+ "UserConnection where userId = ? and providerId = ?",
new Object[] { userId, data.getProviderId() }, Integer.class);
jdbcTemplate.update("insert into " + tablePrefix
+ "UserConnection (userId, providerId, providerUserId, grade, displayName, profileUrl, imageUrl, accessToken, secret, refreshToken, expireTime) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
userId, data.getProviderId(), data.getProviderUserId(), grade, data.getDisplayName(),
data.getProfileUrl(), data.getImageUrl(), encrypt(data.getAccessToken()), encrypt(data.getSecret()),
encrypt(data.getRefreshToken()), data.getExpireTime());
} catch (DuplicateKeyException e) {
throw new DuplicateConnectionException(connection.getKey());
}
}
@Transactional
public void updateConnection(Connection<?> connection) {
ConnectionData data = connection.createData();
jdbcTemplate.update("update " + tablePrefix
+ "UserConnection set displayName = ?, profileUrl = ?, imageUrl = ?, accessToken = ?, secret = ?, refreshToken = ?, expireTime = ? where userId = ? and providerId = ? and providerUserId = ?",
data.getDisplayName(), data.getProfileUrl(), data.getImageUrl(), encrypt(data.getAccessToken()),
encrypt(data.getSecret()), encrypt(data.getRefreshToken()), data.getExpireTime(), userId,
data.getProviderId(), data.getProviderUserId());
}
@Transactional
public void removeConnections(String providerId) {
jdbcTemplate.update("delete from " + tablePrefix + "UserConnection where userId = ? and providerId = ?", userId,
providerId);
}
@Transactional
public void removeConnection(ConnectionKey connectionKey) {
jdbcTemplate.update(
"delete from " + tablePrefix
+ "UserConnection where userId = ? and providerId = ? and providerUserId = ?",
userId, connectionKey.getProviderId(), connectionKey.getProviderUserId());
}
// internal helpers
private String selectFromUserConnection() {
return "select userId, providerId, providerUserId, displayName, profileUrl, imageUrl, accessToken, secret, refreshToken, expireTime from "
+ tablePrefix + "UserConnection";
}
private Connection<?> findPrimaryConnection(String providerId) {
List<Connection<?>> connections = jdbcTemplate.query(
selectFromUserConnection() + " where userId = ? and providerId = ? order by grade", connectionMapper,
userId, providerId);
if (connections.size() > 0) {
return connections.get(0);
} else {
return null;
}
}
private final ServiceProviderConnectionMapper connectionMapper = new ServiceProviderConnectionMapper();
private final class ServiceProviderConnectionMapper implements RowMapper<Connection<?>> {
public Connection<?> mapRow(ResultSet rs, int rowNum) throws SQLException {
ConnectionData connectionData = mapConnectionData(rs);
ConnectionFactory<?> connectionFactory = connectionFactoryLocator
.getConnectionFactory(connectionData.getProviderId());
return connectionFactory.createConnection(connectionData);
}
private ConnectionData mapConnectionData(ResultSet rs) throws SQLException {
return new ConnectionData(rs.getString("providerId"), rs.getString("providerUserId"),
rs.getString("displayName"), rs.getString("profileUrl"), rs.getString("imageUrl"),
decrypt(rs.getString("accessToken")), decrypt(rs.getString("secret")),
decrypt(rs.getString("refreshToken")), expireTime(rs.getLong("expireTime")));
}
private String decrypt(String encryptedText) {
return encryptedText != null ? textEncryptor.decrypt(encryptedText) : encryptedText;
}
private Long expireTime(long expireTime) {
return expireTime == 0 ? null : expireTime;
}
}
private <A> String getProviderId(Class<A> apiType) {
return connectionFactoryLocator.getConnectionFactory(apiType).getProviderId();
}
private String encrypt(String text) {
return text != null ? textEncryptor.encrypt(text) : text;
}
}
public class RavenJdbcUsersConnectionRepository implements UsersConnectionRepository {
private final JdbcTemplate jdbcTemplate;
private final ConnectionFactoryLocator connectionFactoryLocator;
private final TextEncryptor textEncryptor;
private ConnectionSignUp connectionSignUp;
private String tablePrefix = "";
public RavenJdbcUsersConnectionRepository(DataSource dataSource, ConnectionFactoryLocator connectionFactoryLocator,
TextEncryptor textEncryptor) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.connectionFactoryLocator = connectionFactoryLocator;
this.textEncryptor = textEncryptor;
}
/**
* The command to execute to create a new local user profile in the event no
* user id could be mapped to a connection. Allows for implicitly creating a
* user profile from connection data during a provider sign-in attempt. Defaults
* to null, indicating explicit sign-up will be required to complete the
* provider sign-in attempt.
*
* @param connectionSignUp a {@link ConnectionSignUp} object
* @see #findUserIdsWithConnection(Connection)
*/
public void setConnectionSignUp(ConnectionSignUp connectionSignUp) {
this.connectionSignUp = connectionSignUp;
}
/**
* Sets a table name prefix. This will be prefixed to all the table names before
* queries are executed. Defaults to "". This is can be used to qualify the
* table name with a schema or to distinguish Spring Social tables from other
* application tables.
*
* @param tablePrefix the tablePrefix to set
*/
public void setTablePrefix(String tablePrefix) {
this.tablePrefix = tablePrefix;
}
public List<String> findUserIdsWithConnection(Connection<?> connection) {
ConnectionKey key = connection.getKey();
List<String> localUserIds = jdbcTemplate.queryForList(
"select userId from " + tablePrefix + "UserConnection where providerId = ? and providerUserId = ?",
String.class, key.getProviderId(), key.getProviderUserId());
if (localUserIds.size() == 0 && connectionSignUp != null) {
String newUserId = connectionSignUp.execute(connection);
if (newUserId != null) {
createConnectionRepository(newUserId).addConnection(connection);
return Arrays.asList(newUserId);
}
}
return localUserIds;
}
public Set<String> findUserIdsConnectedTo(String providerId, Set<String> providerUserIds) {
MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("providerId", providerId);
parameters.addValue("providerUserIds", providerUserIds);
final Set<String> localUserIds = new HashSet<String>();
return new NamedParameterJdbcTemplate(jdbcTemplate).query(
"select userId from " + tablePrefix
+ "UserConnection where providerId = :providerId and providerUserId in (:providerUserIds)",
parameters, new ResultSetExtractor<Set<String>>() {
public Set<String> extractData(ResultSet rs) throws SQLException, DataAccessException {
while (rs.next()) {
localUserIds.add(rs.getString("userId"));
}
return localUserIds;
}
});
}
public ConnectionRepository createConnectionRepository(String userId) {
if (userId == null) {
throw new IllegalArgumentException("userId cannot be null");
}
return new RavenJdbcConnectionRepository(userId, jdbcTemplate, connectionFactoryLocator, textEncryptor,
tablePrefix);
}
}
public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> {
public QQConnectionFactory(String providerId,String appId,String appSecret) {
super(providerId, new QQServiceProvider(appId, appSecret), new QQAdapter());
}
}
@Configuration
@ConditionalOnProperty(prefix = "fuiou.security.social.qq",name = "app-id")
public class QQAutoConfig extends SocialAutoConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties;
@Override
protected ConnectionFactory<?> createConnectionFactory() {
return new QQConnectionFactory(securityProperties.getSocial().getQq().getProviderId(),
securityProperties.getSocial().getQq().getAppId(),
securityProperties.getSocial().getQq().getAppSecret());
}
}
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private SecurityProperties securityProperties;
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
// 插入到UsersConnection加解密。Encryptors.noOpText()這里不做加解密
return new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
}
@Bean
public SpringSocialConfigurer fuiouSocialSecurityConfig() {
return new FuiouSpringSocialConfigurer(securityProperties.getSocial().getFilterProcessesUrl());
}
}
@Setter
@Getter
public class SocialProperties {
private String filterProcessesUrl="/auth";
private QQProperties qq=new QQProperties();
}
@Setter
@Getter
public class QQProperties extends SocialProperties {
private String providerId="qq";
}
browser項(xiàng)目引入SocialConfig
@Autowired
private SpringSocialConfigurer fuiouSocialSecurityConfig;
http
.apply(fuiouSocialSecurityConfig)
fuiou.security.social.qq.app-id=101364240
fuiou.security.social.qq.app-secret=ef27b7a6ca651a3609dd47f21e385955
fuiou.security.social.filterProcessesUrl=/login
fuiou.security.social.qq.providerId=qq
server.port=80
app-id和app-secret轉(zhuǎn)載自: http://www.reibang.com/p/48bc3173663e
點(diǎn)擊登錄
打印的日志可以看到:引發(fā)跳轉(zhuǎn)的請(qǐng)求是===>http://127.0.0.1/signin
跳轉(zhuǎn)到了signin接口欺缘。
原因:
@SuppressWarnings("unchecked")
protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {
return extractAccessGrant(getRestTemplate().postForObject(accessTokenUrl, parameters, Map.class));
}
protected RestTemplate createRestTemplate() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactorySelector.getRequestFactory();
RestTemplate restTemplate = new RestTemplate(requestFactory);
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>(2);
converters.add(new FormHttpMessageConverter());
converters.add(new FormMapHttpMessageConverter());
converters.add(new MappingJackson2HttpMessageConverter());
restTemplate.setMessageConverters(converters);
restTemplate.setErrorHandler(new LoggingErrorHandler());
if (!useParametersForClientAuthentication) {
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (interceptors == null) { // defensively initialize list if it is null. (See SOCIAL-430)
interceptors = new ArrayList<ClientHttpRequestInterceptor>();
restTemplate.setInterceptors(interceptors);
}
interceptors.add(new PreemptiveBasicAuthClientHttpRequestInterceptor(clientId, clientSecret));
}
return restTemplate;
}
OAuth2AuthenticationService在去換取令牌時(shí)獲取到的返回值是text/html模式栋豫,但是在構(gòu)建與提供者進(jìn)行API通信的RestTemplate時(shí)未添加text/html類型的轉(zhuǎn)換器
創(chuàng)建自己的OAuth2Template,并重寫方法
@Slf4j
public class QQOAuth2Template extends OAuth2Template {
public QQOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
setUseParametersForClientAuthentication(true);
}
@Override
protected RestTemplate createRestTemplate() {
RestTemplate restTemplate= super.createRestTemplate();
restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
return restTemplate;
}
@Override
protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {
String responseStr=getRestTemplate().postForObject(accessTokenUrl, parameters, String.class);
log.info("responseStr==>"+responseStr);
String[] items=StringUtils.splitByWholeSeparatorPreserveAllTokens(responseStr, "&");
String accessToken=StringUtils.substringAfter(items[0], "=");
Long expiresIn=new Long(StringUtils.substringAfter(items[1], "="));
String refreshToken=StringUtils.substringAfter(items[2], "=");
return new AccessGrant(accessToken, null, refreshToken, expiresIn);
}
}
發(fā)現(xiàn)跳轉(zhuǎn)signup:引發(fā)跳轉(zhuǎn)的請(qǐng)求是===>http://127.0.0.1/signup
原因:這是因?yàn)樗鶕?jù)qq返回的信息去userconnection 中匹配谚殊,發(fā)現(xiàn)并沒有數(shù)據(jù)丧鸯,會(huì)跳轉(zhuǎn)到注冊(cè)頁面。
在Properties中配置注冊(cè)頁面url
@Data
public class BrowserProperties {
private String loginPage = "/fuiou-login.html";
private String siginUpUrl = "/fuiou-siginUp.html";
private LoginType loginType=LoginType.JSON;
private int rememberMeSeconds = 3600;
}
改變默認(rèn)的注冊(cè)地址
@Bean
public SpringSocialConfigurer fuiouSocialSecurityConfig() {
FuiouSpringSocialConfigurer configurer=new FuiouSpringSocialConfigurer(securityProperties.getSocial().getFilterProcessesUrl());
configurer.signupUrl(securityProperties.getBrowser().getSiginUpUrl());
return configurer;
}
注冊(cè)的jsp
<form action="/regist" method="post">
<table>
<tr>
<td>用戶名</td>
<td><input 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">注冊(cè)</button>
<button type="submit" name="type" value="binding">綁定</button>
</td>
</tr>
</table>
</form>
排除對(duì)注冊(cè)頁的攔截
.antMatchers("/authentication/requrie",
securityProperties.getBrowser().getLoginPage(),
"/code/*",securityProperties.getBrowser().getSiginUpUrl())
.permitAll()//該url不需要身份認(rèn)證
發(fā)現(xiàn)改完之后嫩絮,雖然userconnection增加了數(shù)據(jù)但是還是跳轉(zhuǎn)到了登錄頁
原因:debug發(fā)現(xiàn)usersConnectionRepository.findUserIdsWithConnection的實(shí)現(xiàn)類沒有用RavenJdbcUsersConnectionRepository丛肢,而是用的InMemoryUsersConnectionRepository,去內(nèi)存中找自然是找不到了剿干。
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;
}
修改:
@Primary
@Bean
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
// 插入到UsersConnection加解密蜂怎。Encryptors.noOpText()這里不做加解密
RavenJdbcUsersConnectionRepository usersConnectionRepository= new RavenJdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
return usersConnectionRepository;
}
這樣還是有一個(gè)問題:第一次用微信登錄的用戶還是要注冊(cè)一遍。
實(shí)現(xiàn)自動(dòng)注冊(cè)
@Component
public class DemoConnectionSignUp implements ConnectionSignUp {
@Override
public String execute(Connection<?> connection) {
// 根據(jù)社交用戶信息默認(rèn)創(chuàng)建用戶并返回唯一標(biāo)識(shí)
//這里直接用昵稱了置尔,實(shí)際情況要根據(jù)需求改
return connection.getDisplayName();
}
}
@Autowired(required = false)
private ConnectionSignUp connectionSignUp;
@Primary
@Bean
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
// 插入到UsersConnection加解密杠步。Encryptors.noOpText()這里不做加解密
RavenJdbcUsersConnectionRepository usersConnectionRepository= new RavenJdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
if(connectionSignUp!=null) {
usersConnectionRepository.setConnectionSignUp(connectionSignUp);
}
return usersConnectionRepository;
}