SpringSecurity学习总结-4 第五章 使用Spring Social开发第三方登录

17 篇文章 0 订阅

一、 OAuth

1、OAuth协议简介

1.1 OAuth协议要解决的问题

(1)应用可以访问用户在微信上的所有数据

(2)用户只有修改密码,才能收回授权

(3)密码泄露的可能性大大提高

1.2 OAuth协议中的各种角色

1.2.1 服务提供商(Provider):

         认证服务器(Authorization Server):认证用户的身份,产生token令牌。

         资源服务器(Resource Server):保存用户数据,验证token令牌。

1.2.2资源所有者(Resource Owner):拥有自己的数据。

1.2.3第三方应用(Client):提供第三方服务。

1.3 OAuth协议运行流程

1.3.1用户访问第三方应用(Client)。

1.3.2第三方应用(Client)请求用户的授权。

1.3.3用户同意授权给第三方应用(Client)。

1.3.4第三方应用(Client)带着用户信息去访问服务提供商的认证服务器(Authorization Server)申请令牌。

1.3.5认证服务器(Authorization Server)验证用户是否同意授权给第三方应用(Client),若同意则生成并发给第三方应用(Client)token令牌。

1.3.6第三方应用(Client)拿着用户的授权令牌去资源服务器(Resource Server)申请获取用户的数据。

1.3.7 资源服务器(Resource Server)验证令牌是否有效。

1.3.8资源服务器(Resource Server)开放用户的资源给第三方应用(Client)。

1.4 OAuth协议中的授权模式

1.4.1授权码模式(Authorization Code)(本系统使用)

1.4.2简化模式(Implicit)

1.4.3密码模式(Resource Owner Password Credentials)

1.4.4客户端模式(Client Credentials)

二、SpringSocial

2.1SpringSocial简介

简介:目的是实现第三方登录,比如QQ、微信、微博等。基于Oauth2协议,对主流的社交网站兼容性较好。下面是对SpringSocial及其原理具体的介绍。

2.2 SpringSocial是基于SpringSecurity框架的,它其实就是在SpringSecurity的过滤器链中添加了SocialAuthenticationFilter过滤器,并使其生效,所以,SpringSecurity的基本原理在SpringSocial中也适用,只不过SpringSocial中有一些特定的实现。

2.3SpringSocial中的角色介绍

下面是SpringSocial中必须要了解几个重要的角色:

2.3.1 Connection :

用来将各式各样的第三方用户信息统一为标准的数据结构,他是由ConnectionFacotory的工厂方法创建的,Connection的实现类是OAuth2Connection.

2.3.2 ConnectionFacotory:

提供工厂方法用来创建Connection对象,需提供两个核心组件(ConnectionFacotory的实现类是OAuth2ConnectionFactory):

2.3.2.1 ServiceProvider:用来封装获取第三方用户信息 ,每种登录方式都要提供该登录方式特有的ServiceProvider的实现,需要提供两个组件(ServiceProvider的抽象类是AbstractOAuth2ServiceProvider)

      (1)、OAuth2Operations :用来走整个Oauth流程获取accessToken,SpringSocial封装accessToken的对象为AccessGrant ,它封装了Oauth2协议请求令牌时的标准返回,如令牌,刷新令牌,超时时间等。OAuth2Operations 的实现类是Oauth2Template。

         (2)、  Api :拿OAuth2Perations 获得的accessToken,和一些必填的参数,如openId(用户在服务提供商处的id,在QQ登录中需要通过accessToken去获取,而微信在返回accessToken时就返回用户在微信的openId了)等去第三方应用获取用户的基本信息。需要根据不同的登录方式提供自己的Api并实现获取用户信息的接口。Api 的实现是AbstractOAuth2Binding。

2.3.2.2 ApiAdater :用来将Api接口获得的用户基本信息适配成标准的第三方信息Connection对象

2.3.3 UsersConnectionRepository:用来对Connection对象做一些增删改查的操作。SpringSocial提供的表结构如下,UserConnection表是用来将服务提供商里的用户Id和自己系统里的用户Id关联起来,这样在第三方登录的时候就能知道是自己系统里的哪位用户登陆了。


create table UserConnection (
 userId varchar(255) not null,
 providerId varchar(255) not null,
 providerUserId varchar(255),
 rank int not null,
 displayName varchar(255),
 profileUrl varchar(512),
 imageUrl varchar(512),
 accessToken varchar(512) not null,
 secret varchar(512),
 refreshToken varchar(512),
 expireTime bigint,
 primary key (userId, providerId, providerUserId));
create unique index UserConnectionRank on UserConnection(userId, providerId, rank);

它是通过userId(用户在业务系统的id),providerId(服务提供商的id),providerUserId(即OpenId)做主键标识某个业务系统的用户已经与某个服务提供商的某个用户做了绑定了。

三、开发QQ登录

3.1 ServiceProvider里的API

3.1.1 新建接口类QQ,用户获取QQ用户信息

package security.core.social.qq.api;

/**
 * 从QQ获取用户信息的接口
 * 角色:ServiceProvider里的API
 */
public interface QQ {

    /**
     * 从QQ获取用户信息的方法
     * @return
     */
    QQUserInfo getUserInfo();

}

3.1.2 QQ类的实现类,同时继承了抽象类 AbstractOAuth2ApiBinding 

(1)继承AbstractOAuth2ApiBinding的目的是为了走 2.1SpringSocial简介中的 1~5步;

    /**
     * 1、获取openId
     * @param accessToken
     * @param appId
     */
    //openId是通过使用accessToken请求时获取到的,所以构造函数的形参中要加入accessToken,appId是QQ服务提供商提供给你的,你这个系统的Id,也需要传入
    public QQImpl(String accessToken, String appId) {
        //父类会自动将accessToken放到请求url后面作为请求参数
        super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);
        this.appId = appId;
        //String.format用来将请求中的%s用accessToken替换掉
        String url = String.format(URL_GET_OPENID,accessToken);
        //发送HTTP请求获取openId,返回的是JSON数据: callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} );
        String result = getRestTemplate().getForObject(url,String.class);
        System.out.println(result);
        this.openId = StringUtils.substringBetween(result,"\"openid\":","}");
    }

(2)实现QQ类是为了获取用户信息。

    /**
     * 2、使用 access_token,appid,openid来获取用户信息
     * @return
     * @throws Exception
     */
    @Override
    public QQUserInfo getUserInfo(){
        //因为在构造函数中super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);已经将accessToken加到请求中,所以这里就没有再加accessToken参数
        String url = String.format(URL_GET_USERINFO,appId,openId);
        //获取用户信息
        String result = getRestTemplate().getForObject(url,String.class);
        System.out.println(result);
        //objectMapper.readValue()是将字符串转为对象
        QQUserInfo userInfo = null;
        try {
            userInfo = objectMapper.readValue(result, QQUserInfo.class);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("获取用户信息失败",e);
        }
        return userInfo;
    }

顺序:先走(1),后走(2)。

完整代码:

package security.core.social.qq.api;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang.StringUtils;
import org.springframework.social.oauth2.AbstractOAuth2ApiBinding;
import org.springframework.social.oauth2.TokenStrategy;

/**
 * 获取用户信息的实现类
 * 继承了 AbstractOAuth2ApiBinding,AbstractOAuth2ApiBinding里两个重要的属性:
 * accessToken:前5步获取的令牌信息(token)
 * restTemplate:用于发送HTTP请求,来获取服务提供商那里的相应信息
 */
public class QQImpl extends AbstractOAuth2ApiBinding implements QQ{

    /**
     * 获取用户OpenId时请求的URL
     */
    private static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";

    ///获取用户信息时请求的URL
    private static final String URL_GET_USERINFO = "https://graph.qq.com/user/get_user_info?oauth_consumer_key=%s&openid=%s";

    //父类中已有accessToken,所以这里不需要新建accessToken

    private String appId;

    private String openId;

    private ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 1、获取openId
     * @param accessToken
     * @param appId
     */
    //openId是通过使用accessToken请求时获取到的,所以构造函数的形参中要加入accessToken,appId是QQ服务提供商提供给你的,你这个系统的Id,也需要传入
    public QQImpl(String accessToken, String appId) {
        //父类会自动将accessToken放到请求url后面作为请求参数
        super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);
        this.appId = appId;
        //String.format用来将请求中的%s用accessToken替换掉
        String url = String.format(URL_GET_OPENID,accessToken);
        //发送HTTP请求获取openId,返回的是JSON数据: callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} );
        String result = getRestTemplate().getForObject(url,String.class);
        System.out.println(result);
        this.openId = StringUtils.substringBetween(result,"\"openid\":","}");
    }

    /**
     * 2、使用 access_token,appid,openid来获取用户信息
     * @return
     * @throws Exception
     */
    @Override
    public QQUserInfo getUserInfo() throws Exception{
        //因为在构造函数中super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);已经将accessToken加到请求中,所以这里就没有再加accessToken参数
        String url = String.format(URL_GET_USERINFO,appId,openId);
        //获取用户信息
        String result = getRestTemplate().getForObject(url,String.class);
        System.out.println(result);
        //objectMapper.readValue()是将字符串转为对象
        QQUserInfo userInfo = objectMapper.readValue(result,QQUserInfo.class);
        return userInfo;
    }
}

QQ的登录具体流程可到 : https://wiki.connect.qq.com/准备工作_oauth2-0 查看。

Step1:获取Authorization Code

请求地址:https://graph.qq.com/oauth2.0/authorize

Step2:通过Authorization Code获取Access Token

请求地址:https://graph.qq.com/oauth2.0/token

Step3:获取用户OpenID

 请求地址:https://graph.qq.com/oauth2.0/me

Step4:获取用户信息

 请求地址:https://graph.qq.com/user/get_user_info

3.2 ServiceProvider封装获取授权码、令牌和用户信息

package security.core.social.qq.connect;

import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider;
import org.springframework.social.oauth2.OAuth2Operations;
import org.springframework.social.oauth2.OAuth2Template;
import security.core.social.qq.api.QQ;
import security.core.social.qq.api.QQImpl;

/**
 * QQ登录时的ServiceProvider
 */
public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> {

    private String appId;

    //1、第三方应用将用户带到服务提供商的URL获取授权码
    private static final String URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";

    //2、第三方应用拿着授权码获取令牌的URL
    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));
    }

    /**
     * 获取QQ的实现类
     * @param accessToken
     * @return
     */
    @Override
    public QQ getApi(String accessToken) {
        return new QQImpl(accessToken,appId);
    }
}

3.3新建 ApiAdapter的实现类QQAdapter

package security.core.social.qq.connect;

import org.springframework.social.connect.ApiAdapter;
import org.springframework.social.connect.ConnectionValues;
import org.springframework.social.connect.UserProfile;
import security.core.social.qq.api.QQ;
import security.core.social.qq.api.QQUserInfo;

/**
 * ApiAdapter里的泛型是要适配接口,这里是QQ
 */
public class QQAdapter implements ApiAdapter<QQ> {

    /**
     * 测试QQ接口是否可用
     * @param qq
     * @return
     */
    @Override
    public boolean test(QQ qq) {
        return true;
    }

    /**
     * 数据适配:将QQ接口获取到的数据转换成Connection里标准数据
     * @param qq
     * @param connectionValues
     */
    @Override
    public void setConnectionValues(QQ qq, ConnectionValues connectionValues) {
        QQUserInfo userInfo = qq.getUserInfo();

        connectionValues.setDisplayName(userInfo.getNickname());
        connectionValues.setImageUrl(userInfo.getFigureurl_qq_1());
        //个人主页:QQ是没有个人主页的,设为null
        connectionValues.setProfileUrl(null);
        connectionValues.setProviderUserId(userInfo.getOpenId());
    }

    /**
     * 获取用户主页信息,暂时用不到
     * @param qq
     * @return
     */
    @Override
    public UserProfile fetchUserProfile(QQ qq) {
        return null;
    }

    /**
     * 更新QQ信息(用不到,QQ无此功能)
     * @param qq
     * @param s
     */
    @Override
    public void updateStatus(QQ qq, String s) {

    }
}

3.3 新建ConnectionFacotory并继承OAuth2Connection

package security.core.social.qq.connect;

import org.springframework.social.connect.support.OAuth2ConnectionFactory;
import security.core.social.qq.api.QQ;

/**
 * QQConnectionFactory继承OAuth2ConnectionFactory的泛型是要适配的接口,作用是用来生成Connection
 */
public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> {

    /**
     * QQConnectionFactory的构造函数
     * @param providerId
     * @param appId
     * @param appScret
     */
    public QQConnectionFactory(String providerId,String appId,String appScret) {
        /**
         * providerId:服务提供商的唯一标识
         * serviceProvider:QQServiceProvider
         * apiAdapter:QQAdapter
         */
        super(providerId, new QQServiceProvider(appId,appScret), new QQAdapter());
    }
}

3.4 新建一个配置类SocialConfig,使用springsocial的配置将从第三方获取到的用户数据存储到数据库中

package security.core.social;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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.UsersConnectionRepository;
import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository;

import javax.sql.DataSource;

/**
 * SocialConfig:用于将从第三方获取到的用户数据存储到数据库中
 * EnableSocial:将项目的social特性开启起来
 */
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    /**
     * JdbcUsersConnectionRepository构造函数的三个参数的作用:
     * dataSource:用于连接数据库
     * connectionFactoryLocator:用于查找具体使用的ConnectionFactoryLocator
     * textEncryptor:用于加解密数据
     * @param connectionFactoryLocator
     * @return
     */
    @Override
    public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
        //Encryptors.noOpText():是不做任何加解密
        return new JdbcUsersConnectionRepository(dataSource,connectionFactoryLocator, Encryptors.noOpText());
    }
}

3.5 将 JdbcUsersConnectionRepository使用的数据表的建表语句拿到数据库里执行建表,建表语句在JdbcUsersConnectionRepository类的旁边

3.6给新建的数据表加入前缀

    /**
     * JdbcUsersConnectionRepository构造函数的三个参数的作用:
     * dataSource:用于连接数据库
     * connectionFactoryLocator:用于查找具体使用的ConnectionFactoryLocator
     * textEncryptor:用于加解密数据
     * @param connectionFactoryLocator
     * @return
     */
    @Override
    public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
        //Encryptors.noOpText():是不做任何加解密
        JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource,connectionFactoryLocator, Encryptors.noOpText());
        //setTablePrefix:设置表的前缀
        repository.setTablePrefix("u_");
        return repository;
    }

完整代码:

package security.core.social;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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.UsersConnectionRepository;
import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository;

import javax.sql.DataSource;

/**
 * SocialConfig:用于将从第三方获取到的用户数据存储到数据库中
 * EnableSocial:将项目的social特性开启起来
 */
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    /**
     * JdbcUsersConnectionRepository构造函数的三个参数的作用:
     * dataSource:用于连接数据库
     * connectionFactoryLocator:用于查找具体使用的ConnectionFactoryLocator
     * textEncryptor:用于加解密数据
     * @param connectionFactoryLocator
     * @return
     */
    @Override
    public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
        //Encryptors.noOpText():是不做任何加解密
        JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource,connectionFactoryLocator, Encryptors.noOpText());
        //setTablePrefix:设置表的前缀
        repository.setTablePrefix("u_");
        return repository;
    }
}

3.7 使用 userId获取用户信息

修改MyUserDetailsService,加入实现SocialUserDetailsService,用于通过userId来获取用户信息

package security.browser.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.social.security.SocialUser;
import org.springframework.social.security.SocialUserDetails;
import org.springframework.social.security.SocialUserDetailsService;
import org.springframework.stereotype.Component;

/**
 * SocialUserDetailsService:是social的获取用户信息的接口类
 */
@Component
@Slf4j
public class MyUserDetailsService implements UserDetailsService, SocialUserDetailsService {

    @Autowired
    private PasswordEncoder myPasswordEncoder;
    /**
     * 可以@AutowiredDao层接口从而实现根据用户名去查找用户信息
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("用户名: "+username);

        String password = myPasswordEncoder.encode("123456");
        log.info("密码:"+password);

        //这个User类实现了UserDetails
        //密码应该是数据库查询出的密码
        //authorities:用户权限的集合,即用来给用户授权
        User user = new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));

        return user;
    }

    /**
     * 通过userId获取用户信息
     * @param userId
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException {
        log.info("用户Id: "+userId);

        String password = myPasswordEncoder.encode("123456");
        log.info("密码:"+password);

        //这个User类实现了UserDetails
        //密码应该是数据库查询出的密码
        //authorities:用户权限的集合,即用来给用户授权
        return new SocialUser(userId,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

3.8新建QQProperties的相关配置类

package security.core.properties;

/**
 * QQ登录的相关配置
 */
public class QQProperties{

    /**
     * 从服务提供商处申请的本项目的appId
     */
    private String appId;

    /**
     * 从服务提供商处申请的本项目的appSecret
     */
    private String appSecret;

    /**
     * 服务提供商的标识
     */
    private String provideId = "qq";

    public String getAppId() {
        return appId;
    }

    public void setAppId(String appId) {
        this.appId = appId;
    }

    public String getAppSecret() {
        return appSecret;
    }

    public void setAppSecret(String appSecret) {
        this.appSecret = appSecret;
    }

    public String getProvideId() {
        return provideId;
    }

    public void setProvideId(String provideId) {
        this.provideId = provideId;
    }
}

3.9 新建SocialProperties相关的配置类

package security.core.properties;

/**
 * 社交相关的配置类
 */
public class SocialProperties {

    private QQProperties qq = new QQProperties();

    public QQProperties getQqProperties() {
        return qq;
    }

    public void setQqProperties(QQProperties qq) {
        this.qq = qq;
    }
}

3.10 在SecurityProperties中加入SocialProperties配置


    /**
     * 社交相关的配置
     */
    private SocialProperties social = new SocialProperties();


    public SocialProperties getSocial() {
        return social;
    }

    public void setSocial(SocialProperties social) {
        this.social = social;
    }

完整代码:

package security.core.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@Component
@PropertySource("classpath:application-browser.properties")
//@PropertySource(value= {"classpath:application-demo.properties","classpath:application-browser.properties"})
@ConfigurationProperties("imooc.security")
public class SecurityProperties {

    /**
     * 浏览器相关的配置
     */
    private BrowserProperties browser = new BrowserProperties();

    /**
     * 验证码相关的配置
     */
    private ValidateCodeProperties code = new ValidateCodeProperties();

    /**
     * 社交相关的配置
     */
    private SocialProperties social = new SocialProperties();

    public BrowserProperties getBrowser() {
        return browser;
    }

    public void setBrowser(BrowserProperties browser) {
        this.browser = browser;
    }

    public ValidateCodeProperties getCode() {
        return code;
    }

    public void setCode(ValidateCodeProperties code) {
        this.code = code;
    }

    public SocialProperties getSocial() {
        return social;
    }

    public void setSocial(SocialProperties social) {
        this.social = social;
    }
}

3.11 新建SocialAutoConfigurerAdapter,因为使用的SpringSocial中没有SocialAutoConfigurerAdapter,所以新建一个

package security.core.social.qq.config;

import org.springframework.core.env.Environment;
import org.springframework.social.config.annotation.ConnectionFactoryConfigurer;
import org.springframework.social.config.annotation.SocialConfigurerAdapter;
import org.springframework.social.connect.ConnectionFactory;

/**
 * 因为使用的SpringSocial中没有SocialAutoConfigurerAdapter,所以新建一个
 */
public abstract class SocialAutoConfigurerAdapter extends SocialConfigurerAdapter {
    public SocialAutoConfigurerAdapter() {
    }
    public void addConnectionFactories(ConnectionFactoryConfigurer configurer, Environment environment) {
        configurer.addConnectionFactory(this.createConnectionFactory());
    }
    protected abstract ConnectionFactory<?> createConnectionFactory();
}

3.12 新建 QQAutoConfig,用于将QQ的配置注入到ConnectionFactory的构造参数中

package security.core.social.qq.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.social.connect.ConnectionFactory;
import security.core.properties.QQProperties;
import security.core.properties.SecurityProperties;
import security.core.social.qq.connect.QQConnectionFactory;

/**
 * 用于将QQ的配置注入到ConnectionFactory的构造参数中
 * ConditionalOnProperty:作用是只有当系统中配置了appId时QQAutoConfig配置类才会生效
 */
@Configuration
@ConditionalOnProperty(value = "imooc.security.social.qq",name = "appId")
public class QQAutoConfig extends SocialAutoConfigurerAdapter{

@Autowired
private SecurityProperties securityProperties;

    /**
     * 在创建ConnectionFactory时将参数注入进去
     * @return
     */
    @Override
    protected ConnectionFactory<?> createConnectionFactory() {
        QQProperties qqconfig = securityProperties.getSocial().getQqProperties();
        return new QQConnectionFactory(qqconfig.getProvideId(),qqconfig.getAppId(),qqconfig.getAppSecret());
    }
}

3.13 在application-browser.properties属性文件中加入

imooc.security.social.qq.appId = 
imooc.security.social.qq.appSecret = 

3.14 在过滤器链中加入SocialAuthenticationFilter过滤器

3.14.1、将SpringSocial的Bean写在SocialConfig

    /**
     * SpringSocial的配置,这个Bean也可以挪到 BrowserSecurityConfig里
     * @return
     */
    @Bean
    public SpringSocialConfigurer imoocSecuritySocialConfig(){
        return new SpringSocialConfigurer();
    }

3.14.2、在SpringSecurity的过滤器链中加入SpringSocial的配置

    //加入SpringSocialConfigurer的配置
    @Autowired
    private SpringSocialConfigurer socialConfigurer;
/************************************************/
.apply(socialConfigurer).and()//将SpringSocial的过滤器配置加入其中
BrowserSecurityConfig的完整代码:

package security.browser.config;

import imooc.security.msn.service.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.social.security.SpringSocialConfigurer;
import security.core.authentication.handle.MsnCodeAuthenticationFailureHandler;
import security.core.authentication.handle.MsnCodeAuthenticationSuccessHandler;
import security.core.authentication.mobile.MsnCodeAuthenticationSecurityConfig;
import security.core.properties.SecurityProperties;
import security.core.validateCode.ImageCodeGenerator;
import security.core.validateCode.ValidateCodeFilter;
import security.core.validateCode.ValidateCodeGenerator;

import javax.sql.DataSource;

/**
 * WebSecurityConfigurerAdapter是SpringSecurity提供的安全适配器类
 */
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 读取配置信息
     */
    @Autowired
    private SecurityProperties securityProperties;

    /**
     * 自定义的登陆成功的处理器
     */
    @Autowired
    private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;

    /**
     * 自定义的登陆失败的处理器
     */
    @Autowired
    private AuthenticationFailureHandler imoocAuthenticationFailureHandler;

    @Autowired
    private MsnCodeAuthenticationSecurityConfig msnCodeAuthenticationSecurityConfig;

    //加入SpringSocialConfigurer的配置
    @Autowired
    private SpringSocialConfigurer socialConfigurer;

    @Override
    public void configure(WebSecurity web) throws Exception {
      //解决静态资源被SpringSecurity拦截的问题
        //   "/static/**"的意思是任何以 static地址开头的资源都能被访问
      web.ignoring().antMatchers("/static/**","/jquery/**","/layui/**");
     }
    /**
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //加入图片验证码的前置校验过滤器
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        validateCodeFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);
        //设置可配置的拦截url
        validateCodeFilter.setSecurityProperties(securityProperties);
        validateCodeFilter.afterPropertiesSet();

        http.apply(msnCodeAuthenticationSecurityConfig).and()
                .apply(socialConfigurer).and()//将SpringSocial的过滤器配置加入其中
                .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                .formLogin()//开启表单登录(即对表单登录进行身份认证)
//    http.formLogin()//开启表单登录(即对表单登录进行身份认证)
                .loginPage("/authentication/require")//指定登录页面
                .loginProcessingUrl("/authentication/form")//让UsernamePasswordAuthenticationFilter能够处理提交的登录请求
                .successHandler(imoocAuthenticationSuccessHandler)
                .failureHandler(imoocAuthenticationFailureHandler)
//                .and()
//                .rememberMe()
//                .tokenRepository(persistentTokenRepository())
//                .tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSecond())
//                .userDetailsService(userDetailsService)
//                http.httpBasic()//开启SpringSecurity原生的表单登录
                .and()
                .authorizeRequests()//对请求进行授权(即登录后需要授权)
                .antMatchers("/authentication/require","/authentication/mobile",
                        securityProperties.getBrowser().getLoginPage(),
                        "/code/*").permitAll()//允许signIn.html请求进来,不进行拦截
                .anyRequest()//对任何请求
                .authenticated()//开启认证
                .and()
                .csrf() //跨域请求伪造
                .disable();//关闭

//                .anyRequest()
//                .authenticated();
//            上面两个方法的意思:对任何请求都需要认证

    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public ValidateCodeGenerator imageCodeGenerator(){
        return new ImageCodeGenerator();
    }


//    @Autowired
//    private DataSource dataSource;
//
//    @Autowired
//    private UserDetailsService userDetailsService;
//
//    @Bean
//    public PersistentTokenRepository persistentTokenRepository(){
//        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
//        tokenRepository.setDataSource(dataSource);
        tokenRepository.setCreateTableOnStartup(true);
//        return tokenRepository;
//    }

    /**
     * 消息服务的Bean
     * @return
     */
    @Bean
    public MessageService messageService(){
        return new MessageService();
    }

    @Bean
    public MsnCodeAuthenticationSecurityConfig getMsnCodeAuthenticationSecurityConfig(){
        return new MsnCodeAuthenticationSecurityConfig();
    }

    @Bean
    public MsnCodeAuthenticationSuccessHandler getMsnCodeAuthenticationSuccessHandler(){
        return new MsnCodeAuthenticationSuccessHandler();
    }

    @Bean
    public MsnCodeAuthenticationFailureHandler getMsnCodeAuthenticationFailureHandler(){
        return new MsnCodeAuthenticationFailureHandler();
    }

    /**
     * SpringSocial的配置,这个Bean也可以挪到 BrowserSecurityConfig里
     * @return
     */
    @Bean
    public SpringSocialConfigurer imoocSecuritySocialConfig(){
        return new SpringSocialConfigurer();
    }

}

3.15在登陆页面上加入社交登录的入口

<div style="margin:0 auto; width:500px; height:200px; border:1px solid #F00">
    <!-- 社交登录-->
    <h3>社交登录</h3>
<!--    因为SocialAuthenticationFilter里会拦截以auth开头的地址,qq地址是配置的provideId-->
    <a href="/auth/qq">QQ登录</a>


</div>

3.16启动失败

Method springSecurityFilterChain in org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration required a bean of type 'org.springframework.social.connect.UsersConnectionRepository' that could not be found.


Action:

Consider defining a bean of type 'org.springframework.social.connect.UsersConnectionRepository' in your configuration.

Disconnected from the target VM, address: '127.0.0.1:56135', transport: 'socket'

Process finished with exit code 1

因无法解决3.16的问题,所以代码暂时更新到此处!

四、Session管理

​​​​​​​

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值