美文网首页
spring security 使用篇 使用spring soc

spring security 使用篇 使用spring soc

作者: 怪诞140819 | 来源:发表于2018-10-20 10:39 被阅读345次

    1.OAuth2协议之授权码

    OAuth2协议.png

    对接第三方来进行认证授权主要采用的是OAuth2的授权码模式,所以我们需要对此来进行对接。OAuth2的主要流程分为以下6个步骤

    • (1).将用户导向认证服务器
    • (2).用户同意授权
    • (3).返回client并且携带授权码
    • (4).申请令牌
    • (5).发放令牌
    • (6).获取用户信息
      具体对于spring social对接就如下图


      spring social对接流程

    spring social封装了整个上篇OAuth2对接的流程,上图就是逻辑上的流程图

    Spring social的接口和类

    • (1). 服务提供商的抽象 ServiceProvider,Spring 有抽象类完成共有特性AbstractOAuth2ServiceProvider
    • (2).OAuth2Operations接口,默认实现OAuth2Template,主要封装了流程的每个步骤的操作
    • (3).Api,spring social定义的抽象类为AbstractOAuth2ApiBinding
    处理流程

    2.QQ登录实现

    QQ登录需要实现以下效果


    点击QQ登录

    点击QQ登录跳转到如下


    QQ登录授权页面
    最后获取到用户的信息
    获取QQ用户信息

    2.1 准备

    需要到QQ互联https://connect.qq.com/index.html申请到APP IDAPP Key

    2.2 编写Api

    接口

    public interface QQ {
    
        /**
         * 获取QQ用户信息
         * @return
         */
        QQUserInfo getUserInfo();
    }
    

    实现

    /**
     * @Auther: guaidan
     * @Date: 2018/10/16 11:20
     * @Description:
     */
    public class QQImpl extends AbstractOAuth2ApiBinding implements QQ {
    
        private Logger logger = LoggerFactory.getLogger(getClass());
    
        private static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";
    
       //后面默认会加上access_token
        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();
    
        private String appId;
    
        private String openId;
    
        public QQImpl(String accessToken,String appId) {
            super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);
            this.appId = appId;
            //这里需要获取到openId
            String url = String.format(URL_GET_OPENID,accessToken);
            String result = getRestTemplate().getForObject(url,String.class);
            this.openId = StringUtils.substringBetween(result, "\"openid\":\"", "\"}");
            logger.info("openId is:"+this.openId);
        }
    
        @Override
        public QQUserInfo getUserInfo() {
    
            String url = String.format(URL_GET_USERINFO,this.appId,this.openId);
            String result = getRestTemplate().getForObject(url,String.class);
            logger.info("getQQUserInfo result is "+result);
            QQUserInfo userInfo = null;
            try {
                userInfo = objectMapper.readValue(result, QQUserInfo.class);
                return userInfo;
            } catch (Exception e) {
                throw new RuntimeException("获取用户信息失败", e);
            }
        }
    }
    

    QQ 用户信息的Bean,这里只写出字段,get和set方法生成一下

    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;
        /**
         *  标识用户是否为黄钻用户(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 constellation;
    }
    

    2.3 自定义OAuth2操作的Template

    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);
    
            //设置系统自动填充clientId和clientSecret等参数
            setUseParametersForClientAuthentication(true);
        }
    
    
        @Override
        protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {
            String responseText = getRestTemplate().postForObject(accessTokenUrl,parameters,String.class);
            logger.info("response access token text:"+responseText);
            String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(responseText,"&");
            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;
        }
    }
    

    2.4 自定义Provider

    public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> {
    
        private String appId;
    
        private String appSecret;
    
        private static final String AUTHORIZE_URL = "https://graph.qq.com/oauth2.0/authorize";
        private static final String ACCESS_TOKEN_URL = "https://graph.qq.com/oauth2.0/token";
    
    
        public QQServiceProvider(String appId,String appSecret) {
            super(new QQOAuth2Template(appId, appSecret, AUTHORIZE_URL, ACCESS_TOKEN_URL));
            this.appId = appId;
            this.appSecret = appSecret;
        }
    
        @Override
        public QQ getApi(String accessToken) {
            return new QQImpl(accessToken,appId);
        }
    
    

    2.5 自定义ApiAdaptor

    public class QQAdaptor implements ApiAdapter<QQ> {
        @Override
        public boolean test(QQ qq) {
    
            //这里表示测试QQ服务是否正产,返回true,表示相信QQ的服务是永远正常的
            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 qq) {
            // do nothing
            return null;
        }
    
        @Override
        public void updateStatus(QQ qq, String s) {
    
            // do nothing
        }
    }
    
    

    2.6 自定义ConnectionFactory

    public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> {
    
    
        public QQConnectionFactory(String providerId,String appId,String appSecret) {
            super(providerId, new QQServiceProvider(appId,appSecret), new QQAdaptor());
        }
    }
    

    2.7 自定义SpringSocialConfigurer

    public class CustomSpringSocialConfigurer extends SpringSocialConfigurer {
    
        private String filterProcessesUrl;
    
        public CustomSpringSocialConfigurer(String filterProcessesUrl){
            this.filterProcessesUrl = filterProcessesUrl;
        }
    
    
        @Override
        protected <T> T postProcess(T object) {
            SocialAuthenticationFilter filter = (SocialAuthenticationFilter) super.postProcess(object);
            filter.setFilterProcessesUrl(filterProcessesUrl);
            return (T) filter;
        }
    
    }
    

    2.8自定义SocialConfigurer

    @Configuration
    @EnableSocial
    public class SocialConfig extends SocialConfigurerAdapter {
    
        @Autowired
        private DataSource dataSource;
    
        @Autowired
        private SecurityProperties securityProperties;
    
        @Override
        public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
    
            //这里需要数据库创建表,建表语句,请查看 JdbcUsersConnectionRepository.sql
            //第一个参数数据源,第二个参数加载不同的ConnectionFactory,第三个参数加密
            JdbcUsersConnectionRepository jdbcUsersConnectionRepository = new JdbcUsersConnectionRepository(dataSource,connectionFactoryLocator, Encryptors.noOpText());
            return jdbcUsersConnectionRepository;
        }
    
        @Bean(name = "customSocialConfigure")
        public SpringSocialConfigurer customSocialConfigure(){
            CustomSpringSocialConfigurer customSpringSocialConfigurer = new CustomSpringSocialConfigurer(securityProperties.getSocial().getFilterProcessesUrl());
            return customSpringSocialConfigurer;
        }
    }
    
    

    2.9 相关自定义配置

    @ConfigurationProperties(prefix = "guaidan.security")
    public class SecurityProperties {
        private SocialProperties social = new SocialProperties();
        public SocialProperties getSocial() {
            return social;
        }
    
        public void setSocial(SocialProperties social) {
            this.social = social;
        }
    }
    
    
    
    
    public class SocialProperties {
    
        private String filterProcessesUrl = "/auth";
    
        private QQProperties qq = new QQProperties();
    
    
        public String getFilterProcessesUrl() {
            return filterProcessesUrl;
        }
    
        public void setFilterProcessesUrl(String filterProcessesUrl) {
            this.filterProcessesUrl = filterProcessesUrl;
        }
    
        public QQProperties getQq() {
            return qq;
        }
    
        public void setQq(QQProperties qq) {
            this.qq = qq;
        }
    }
    
    
    
    import org.springframework.boot.autoconfigure.social.SocialProperties;
    
    /**
     * 这里的SocialProperties是系统的不是自己定义的
     * @Auther: guaidan
     * @Date: 2018/10/16 14:04
     * @Description:
     */
    public class QQProperties extends SocialProperties {
    
        private String providerId = "qq";
    
        public String getProviderId() {
            return providerId;
        }
    
        public void setProviderId(String providerId) {
            this.providerId = providerId;
        }
    }
    

    在application.properties配置

    guaidan.security.social.qq.app-id= 你自己的 APP ID
    guaidan.security.social.qq.app-secret= 你自己的 APP Key
    guaidan.security.social.qq.providerId = qq
    guaidan.security.social.filterProcessesUrl = /social
    

    注意点 这里根据配置需要将QQ互联中的配置为http[s]://域名/[filterProcessesUrl ]/[providerId ] 例如 http://test.demo.com/social/qq 否则会有匹配不上的问题

    相关文章

      网友评论

          本文标题:spring security 使用篇 使用spring soc

          本文链接:https://www.haomeiwen.com/subject/idffzftx.html