spring security 使用篇 使用spring soc

2018-10-20  本文已影响345人  怪诞140819

1.OAuth2协议之授权码

OAuth2协议.png

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

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

Spring social的接口和类

处理流程

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 否则会有匹配不上的问题

上一篇 下一篇

猜你喜欢

热点阅读