zoukankan      html  css  js  c++  java
  • 使用SpringSocial 开发第三方QQ登录

    	OAuth 协议
    		OAuth协议要解决的问题
    			解决传统模式的授权(授权协议),认证资源访问问题
    		OAuth协议中的各种角色
    			Privider 服务提供商(提供令牌) (如微信)
    				Authorization Server 认证服务器
    				ResourceServer 资源服务器
    			Resource Owner 资源所有者 (用户)
    			Client 第三方应用
    		OAuth协议运行流程
    			0访问Client
    			1将用户导向认证服务器
    			2用户同意授权
    			3返回Client并携带授权码
    			4申请令牌
    			5发放令牌 (1-5是OAuth标准流程)
    			6获取用户信息 (每个服务商提供返回的字段不一样)
    			7根据用户信息构建Authentictioin并放入SecurityContext
    		OAuth协议授权模式
    			授权码模式 (authorization code)
    				认证授权在服务器提供商认证服务器完成,更安全
    			密码模式(Resource owner password credentials)
    			客户端模式(client credentials)
    			简化模式(implicit)
    	SpringSocial 基本原理 (就是封装了OAuth2的基本流程 SocialAuthenticationFilter加入到过滤器链中)
    		ServiceProvider (AbstractOAuth2ServiceProvider服务提供商的抽象实现)
                OAuth2Operations (OAuth2Template 封装1-5 会帮你去完成OAuth的认证流程)
                Api(AbstractOAuth2ApiBinding获取用户信息抽象实现 6)
    		第7步是在第三方应用上操作
    		Connection(OAuth2Connection服务提供商的信息)  是由 ConnectionFactory(OAuth2ConnectionFactory) 创建的
    		ConnectionFactory 中是包含(ServiceProvider 的)
    		ApiAdapter  (在Aip 和 Connection之间做一个适配,因为Connection字段是固定的,服务商是个性化的)
    		用户和第三方用户有个关联表,UserConnection
    		UserConnection 表是由 JdbcUsersConnectionRepository(UsersConnectionRepository) 这个类进行操作CRUD
    

     第一步: 根据图2 先 构建ServiceProvider

     (6获取用户信息): 根据OAuth2Operations (OAuth2Template),Api(AbstractOAuth2ApiBinding) 构建 ServiceProvider

    创建API 获取用户信息接口

    package com.imooc.security.core.social.qq.api;
    
    /**
     * @Title: QQ
     * @ProjectName spring-security-main
     * @date 2020/12/710:21
     */
    public interface QQ {
        QQUserInfo getUserInfo();
    }
    

     实现

    package com.imooc.security.core.social.qq.api;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.social.oauth2.AbstractOAuth2ApiBinding;
    import org.springframework.social.oauth2.TokenStrategy;
    
    /**
     * @Title: QQImpl
     * @ProjectName spring-security-main
     * @date 2020/12/710:22
     */
    @Slf4j
    public class QQImpl extends AbstractOAuth2ApiBinding implements QQ {
        // 获取openId
        private static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";
        // 获取用户信息 accessToken 父类会处理,这里不用拼接参数了
        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;
        private ObjectMapper objectMapper = new ObjectMapper();
    
        public QQImpl(String accessToken, String appId) {
            super(accessToken,TokenStrategy.ACCESS_TOKEN_PARAMETER); // 默认父类是放在header中,这里要放在参数里
            this.appId = appId;
            // 获取openId
            String url = String.format(URL_GET_OPENID, accessToken);
            String result = getRestTemplate().getForObject(url, String.class);
            log.info(result);
            // 截取一下openId
            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);
            log.info(result);
            QQUserInfo userInfo = null;
            try {
                userInfo = objectMapper.readValue(result, QQUserInfo.class);
                userInfo.setOpenId(openId);
                return userInfo;
            } catch (Exception e) {
                throw new RuntimeException("获取用户信息失败", e);
            }
        }
    }
    

     QQUserInfo  实体类

    package com.imooc.security.core.social.qq.api;
    
    import lombok.Data;
    
    @Data
    public class QQUserInfo {
    	
    	/**
    	 * 	返回码
    	 */
    	private String ret;
    	/**
    	 * 如果ret<0,会有相应的错误信息提示,返回数据全部用UTF-8编码。
    	 */
    	private String msg;
    	/**
    	 * 
    	 */
    	private String openId;
    	/**
    	 * 不知道什么东西,文档上没写,但是实际api返回里有。
    	 */
    	private String is_lost;
    	/**
    	 * 省(直辖市)
    	 */
    	private String province;
    	/**
    	 * 市(直辖市区)
    	 */
    	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像素则是一定会有。
    	 */
    	private String figureurl_qq_2;
    	/**
    	 * 	性别。 如果获取不到则默认返回”男”
    	 */
    	private String gender;
    	private String gender_type;
    
    
    	private String constellation;
    
    
    	/**
    	 * 	标识用户是否为黄钻用户(0:不是;1:是)。
    	 */
    	private String is_yellow_vip;
    	/**
    	 * 	标识用户是否为黄钻用户(0:不是;1:是)
    	 */
    	private String vip;
    	/**
    	 * 	黄钻等级
    	 */
    	private String yellow_vip_level;
    	/**
    	 * 	黄钻等级
    	 */
    	private String level;
    	/**
    	 * 标识是否为年费黄钻用户(0:不是; 1:是)
    	 */
    	private String is_yellow_year_vip;
    
    	private String figureurl_qq;
    
    	private String figureurl_type;
    
    }
    

    创建QQOAuth2Template  

    package com.imooc.security.core.social.qq.connet;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang.StringUtils;
    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;
    
    import java.nio.charset.Charset;
    @Slf4j
    public class QQOAuth2Template extends OAuth2Template {
    	
    
    	public QQOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
    		super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
    		setUseParametersForClientAuthentication(true); // 设置true client_secret client_id才会带上这两个参数
    	}
    	
    	@Override
    	protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {
    		String responseStr = getRestTemplate().postForObject(accessTokenUrl, parameters, String.class);
    		// 因为qq返回的是 用&拼接的字符串 需要自己处理一下
    		log.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();
    		// qq返回的是text/html 默认的template没有添加处理这样的类型,自己添加一个
    		restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
    		return restTemplate;
    	}
    
    }
    

      

    根据  QQOAuth2Template ,QQImpl  创建 QQServiceProvider 

    package com.imooc.security.core.social.qq.connet;
    
    import com.imooc.security.core.social.qq.api.QQ;
    import com.imooc.security.core.social.qq.api.QQImpl;
    import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider;
    import org.springframework.social.oauth2.OAuth2Template;
    
    /**
     * @Title: QQServiceProvider
     * @ProjectName spring-security-main
     * @date 2020/12/711:01
     */
    public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> {
    
        private String appId;
        // 1.导向认证服务器rul
        private static final String URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";
        // 4.用户同意授权,返回授权码去申请令牌的url
        private static final String URL_ACCESS_TOKEN = "https://graph.qq.com/oauth2.0/token";
        public QQServiceProvider(String appId, String appSecret) {
    //        super(oauth2Operations);
            // OAuth2Template 用系统默认的
    //        super(new OAuth2Template(appId, appSecret, URL_AUTHORIZE, URL_ACCESS_TOKEN));
            // 默认Template不能满足使用
            super(new QQOAuth2Template(appId, appSecret, URL_AUTHORIZE, URL_ACCESS_TOKEN));
            this.appId = appId;
        }
    
        @Override
        public QQ getApi(String accessToken) {
            return new QQImpl(accessToken, appId);
        }
    
    }
    

      图2右边的代码已经完成

     图2 左边开发

    创建QQAdapter

    package com.imooc.security.core.social.qq.connet;
    import com.imooc.security.core.social.qq.api.QQ;
    import com.imooc.security.core.social.qq.api.QQUserInfo;
    import org.springframework.social.connect.ApiAdapter;
    import org.springframework.social.connect.ConnectionValues;
    import org.springframework.social.connect.UserProfile;
    /**
     * 服务商和第三方应用之间做适配的,适配的类型就是QQ
     */
    public class QQAdapter implements ApiAdapter<QQ> {
        /**
         * 测试方法
         * @param api
         * @return
         */
        @Override
        public boolean test(QQ api) {
            return true;
        }
        /**
         * 主要方法, qq api的信息设置到connectionValues
         * @param api
         * @param values
         */
        @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) {
            return null;
        }
        @Override
        public void updateStatus(QQ api, String message) {
    
        }
    }
    

     根据 QQAdapter 和 QQServiceProvider 可以构建QQConnectionFactory

    package com.imooc.security.core.social.qq.connet;
    import com.imooc.security.core.social.qq.api.QQ;
    import org.springframework.social.connect.support.OAuth2ConnectionFactory;
    public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> {
        /**
         * 根据 QQAdapter 和 QQServiceProvider 可以构建QQConnectionFactory
         * @param providerId 服务商Id
         * @param appId 
         * @param appSecret
         */
        public QQConnectionFactory(String providerId, String appId, String appSecret) {
            super(providerId, new QQServiceProvider(appId, appSecret), new QQAdapter());
        }
    }
    

      建表 查找 JdbcUsersConnectionRepository.sql 

    创建  SocialConfig 配置类,启用Social

    package com.imooc.security.core.social;
    
    import com.imooc.security.core.properties.SecurityProperties;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    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 javax.sql.DataSource;
    
    /**
     * @Title: SocialConfig
     * @ProjectName spring-security-main
     * @date 2020/12/713:57
     */
    @Configuration
    @EnableSocial
    @Slf4j
    public class SocialConfig extends SocialConfigurerAdapter {
        @Autowired
        private SecurityProperties securityProperties;
        // 数据源
        @Autowired
        private DataSource dataSource;
    
        @Autowired(required = false)
        private ConnectionSignUp connectionSignUp;
    
        /**
         * 默认使用InMemoryUsersConnectionRepository类 , Primary 候选bean中优先使用,Bean 添加这两个注解
         * @param connectionFactoryLocator 作用就是查找ConnectionFactory
         * @return
         */
        @Primary
        @Bean
        @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;
        }
    
        /**
         * 配置自定义路径  默认是auth
         *需要 把SpringSocialConfigure 加入到过滤器链上
         * @return
         */
        @Bean
        public SpringSocialConfigurer imoocSocialSecurityConfig() {
            String filterProcessesUrl = securityProperties.getSocial().getFilterProcessesUrl();
            log.info(filterProcessesUrl);
            ImoocSpringSocialConfigurer imoocSpringSocialConfigurer = new ImoocSpringSocialConfigurer(filterProcessesUrl);
            // 登录成功 回调的url
            imoocSpringSocialConfigurer.signupUrl(securityProperties.getBrowser().getSignUpUrl());
            return imoocSpringSocialConfigurer;
    //        return new SpringSocialConfigurer();
        }
    
        @Bean
        public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator) {
            return new ProviderSignInUtils(connectionFactoryLocator,
                    getUsersConnectionRepository(connectionFactoryLocator)) {
            };
        }
    }
    

      创建 ImoocSpringSocialConfigurer

    package com.imooc.security.core.social;
    
    import org.springframework.social.security.SocialAuthenticationFilter;
    import org.springframework.social.security.SpringSocialConfigurer;
    public class ImoocSpringSocialConfigurer extends SpringSocialConfigurer {
    
    	/**
    	 * 修改默认的路由auth 可配置,默认是auth,自定义回调地址, 要和备案的一致
    	 */
    	private String filterProcessesUrl;
    	
    	public ImoocSpringSocialConfigurer(String filterProcessesUrl) {
    		this.filterProcessesUrl = filterProcessesUrl;
    	}
    	
    	@Override
    	protected <T> T postProcess(T object) {
    		SocialAuthenticationFilter filter = (SocialAuthenticationFilter) super.postProcess(object);
    		filter.setFilterProcessesUrl(filterProcessesUrl);
    		return (T) filter;
    	}
    
    }
    
    创建 QQAutoConfig
    package com.imooc.security.core.social.qq.config;
    
    import com.imooc.security.core.properties.QQProperties;
    import com.imooc.security.core.properties.SecurityProperties;
    import com.imooc.security.core.social.qq.connet.QQConnectionFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.boot.autoconfigure.social.SocialAutoConfigurerAdapter;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.social.connect.ConnectionFactory;
    
    @Configuration
    @ConditionalOnProperty(prefix = "imooc.security.social.qq", name = "app-id") // 当配置文件中,有配置属性才会生效
    public class QQAutoConfig extends SocialAutoConfigurerAdapter {
        @Autowired
        private SecurityProperties securityProperties;
        @Override
        protected ConnectionFactory<?> createConnectionFactory() {
            QQProperties qqConfig = securityProperties.getSocial().getQq();
            return new QQConnectionFactory(qqConfig.getProviderId(), qqConfig.getAppId(), qqConfig.getAppSecret());
        }
    }
    

      

    把SpringSocialConfigurer 加入到过滤器链上
    @Autowired
    private SpringSocialConfigurer imoocSocialSecurityConfig;
    
    .apply(imoocSocialSecurityConfig) 
    

      

    imooc-signIn.html

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>登录</title>
    </head>
    <body>
    	<h2>标准登录页面</h2>
    	<h3>表单登录</h3>
    	<form action="/authentication/form" 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>图形验证码:</td>
    				<td>
    					<input type="text" name="imageCode">
    					<img src="/code/image?width=200">
    				</td>
    			</tr>
    			<tr>
    				<td colspan='2'><input name="remember-me" type="checkbox" value="true" />记住我</td>
    			</tr>
    			<tr>
    				<td colspan="2"><button type="submit">登录</button></td>
    			</tr>
    		</table>
    	</form>
    	
    	<h3>短信登录</h3>
    	<form action="/authentication/mobile" method="post">
    		<table>
    			<tr>
    				<td>手机号:</td>
    				<td><input type="text" name="mobile" value="13012345678"></td>
    			</tr>
    			<tr>
    				<td>短信验证码:</td>
    				<td>
    					<input type="text" name="smsCode">
    					<a href="/code/sms?mobile=13012345678">发送验证码</a>
    				</td>
    			</tr>
    			<tr>
    				<td colspan="2"><button type="submit">登录</button></td>
    			</tr>
    		</table>
    	</form>
    	<br>
    	<h3>社交登录</h3>
    	<a href="/qqLogin/callback.do">QQ登录2</a>
    	<a href="/auth/qq">QQ登录1</a>
    	    
    	<a href="/qqLogin/weixin">微信登录</a>
    </body>
    </html>
    

      

    注册问题 SocialConfig 

    首次登录自动注册 

    DemoConnectionSignUp
    package com.imooc.security;
    
    import org.springframework.social.connect.Connection;
    import org.springframework.social.connect.ConnectionSignUp;
    import org.springframework.stereotype.Component;
    
    @Component
    public class DemoConnectionSignUp implements ConnectionSignUp {
    	@Override
    	public String execute(Connection<?> connection) {
    		//根据社交用户信息默认创建用户并返回用户唯一标识
    		return connection.getDisplayName();
    	}
    
    }
    

      

    点击按钮进行手动注册

        @Autowired
        private ProviderSignInUtils providerSignInUtils;
    
        @PostMapping("/regist")
        public void regist(User user, HttpServletRequest request) {
    
            //不管是注册用户还是绑定用户,都会拿到一个用户唯一标识。
            String userId = user.getUsername();
            providerSignInUtils.doPostSignUp(userId, new ServletWebRequest(request));
        }
    

      获取当前用户信息

    	/**
    	 * 前端可以显示 当前登录的用户信息
    	 * @param request
    	 * @return
    	 */
    	@GetMapping("/social/user")
    	public SocialUserInfo getSocialUserInfo(HttpServletRequest request) {
    		SocialUserInfo userInfo = new SocialUserInfo();
    		// 在session中取出用户信息
    		Connection<?> connection = providerSignInUtils.getConnectionFromSession(new ServletWebRequest(request));
    		userInfo.setProviderId(connection.getKey().getProviderId());
    		userInfo.setProviderUserId(connection.getKey().getProviderUserId());
    		userInfo.setNickname(connection.getDisplayName());
    		userInfo.setHeadimg(connection.getImageUrl());
    		return userInfo;
    	}
    

      

  • 相关阅读:
    计蒜客模拟赛D2T2 蒜头君的排序:区间逆序对(移动端点) + 树状数组
    计蒜客模拟赛D2T1 蒜头君的兔子:矩阵快速幂
    计蒜客模拟赛D1T2 蒜头君的树:树上节点之间最短距离和
    计蒜客模拟赛D1T1 蒜头君打地鼠:矩阵旋转+二维前缀和
    Cubieboard安装系统
    awk速查手册
    sed速查手册
    常用正则表达
    MySQL索引小记
    jQuery中attr和prop的区别
  • 原文地址:https://www.cnblogs.com/412013cl/p/14133233.html
Copyright © 2011-2022 走看看