Spring Social第三方登錄(qq登錄)

oauth協(xié)議簡(jiǎn)介

image.png

授權(quán)碼模式流程:


image.png

授權(quán)碼模式的特殊之處在于:用戶同意授權(quán)的動(dòng)作是在認(rèn)證服務(wù)器上完成的,其他的模式是在第三方上完成的驳规,這樣避免了偽造用戶授權(quán)。

授權(quán)碼模式也是功能最完整,最嚴(yán)密的模式旭蠕,也是使用最廣泛的協(xié)議。

總結(jié):就是為了不用將用戶的用戶名和密碼交給第三方應(yīng)用旷坦,就可以有權(quán)限用戶存在服務(wù)器上的一些資源掏熬。

第三方登錄邏輯流程.png

上圖就是整個(gè)第三方登錄的整個(gè)流程,Spring Social將這個(gè)流程封裝到了SocialAuthenticationFilter中秒梅。

第三方登錄.png

上圖是整個(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

image.png

點(diǎn)擊登錄


image.png

打印的日志可以看到:引發(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;
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市榜轿,隨后出現(xiàn)的幾起案子幽歼,更是在濱河造成了極大的恐慌,老刑警劉巖谬盐,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甸私,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡飞傀,警方通過查閱死者的電腦和手機(jī)皇型,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門泣刹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人犀被,你說我怎么就攤上這事椅您。” “怎么了寡键?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵掀泳,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我西轩,道長(zhǎng)员舵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任藕畔,我火速辦了婚禮马僻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘注服。我一直安慰自己韭邓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布溶弟。 她就那樣靜靜地躺著女淑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪辜御。 梳的紋絲不亂的頭發(fā)上鸭你,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音擒权,去河邊找鬼袱巨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛碳抄,可吹牛的內(nèi)容都是我干的愉老。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼纳鼎,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼俺夕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起贱鄙,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤劝贸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后逗宁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體映九,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年瞎颗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了件甥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捌议。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖引有,靈堂內(nèi)的尸體忽然破棺而出瓣颅,到底是詐尸還是另有隱情,我是刑警寧澤譬正,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布宫补,位于F島的核電站,受9級(jí)特大地震影響曾我,放射性物質(zhì)發(fā)生泄漏粉怕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一抒巢、第九天 我趴在偏房一處隱蔽的房頂上張望贫贝。 院中可真熱鬧,春花似錦蛉谜、人聲如沸稚晚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜈彼。三九已至筑辨,卻和暖如春俺驶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棍辕。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工暮现, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人楚昭。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓栖袋,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親抚太。 傳聞我的和親對(duì)象是個(gè)殘疾皇子塘幅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359