spring-security-oauth2(二十一) 重构社交登录

重构社交登录

app里面的第三方登录不像浏览器中一样,一般是通过调用sdk(服务提供商),一般有2中模式。

浏览器社交登录流程

 简化模式

从流程图分析,我们要提供一个用openId登录后台,这个流程和短信登录很相似,只是openId是从我们的社交用户表(UserConnection)中获取。直接上代码

OpenIdAuthenticationToken

package com.rui.tiger.auth.app.social.openid;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;

import java.util.Collection;

/**
 * openId登录
 * @author CaiRui
 * @date 2019-04-23 08:20
 */
public class OpenIdAuthenticationToken extends AbstractAuthenticationToken {
	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

	// ~ Instance fields
	// ================================================================================================

	private final Object principal;//openId
	private String providerId;//供应商ID

	// ~ Constructors
	// ===================================================================================================
	public OpenIdAuthenticationToken(Object principal, String providerId) {
		super(null);
		this.principal = principal;
		this.providerId = providerId;
		super.setAuthenticated(true); // must use super, as we override
	}

	public OpenIdAuthenticationToken(Object principal,
									 Collection<? extends GrantedAuthority> authorities) {
		super(authorities);
		this.principal = principal;
		super.setAuthenticated(true); // must use super, as we override
	}

	// ~ Methods
	// ========================================================================================================


	@Override
	public Object getPrincipal() {
		return this.principal;
	}

	@Override
	public Object getCredentials() {
		return null;
	}

	public String getProviderId() {
		return providerId;
	}

	@Override
	public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
		if (isAuthenticated) {
			throw new IllegalArgumentException(
					"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
		}

		super.setAuthenticated(false);
	}

	@Override
	public void eraseCredentials() {
		super.eraseCredentials();
	}

}

过滤器  OpenIdAuthenticationFilter

package com.rui.tiger.auth.app.social.openid;

import com.rui.tiger.auth.core.properties.QQProperties;
import com.rui.tiger.auth.core.properties.SecurityConstants;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 逻辑通短信登录验证
 * @author CaiRui
 * @date 2019-04-23 08:23
 */
public class OpenIdAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

	private String openIdParameter = SecurityConstants.DEFAULT_PARAMETER_NAME_OPENID;
	// 服务提供商id,qq还是微信
	/**
	 * @see QQProperties#providerId
	 */
	private String providerIdParameter = SecurityConstants.DEFAULT_PARAMETER_NAME_PROVIDERID;
	private boolean postOnly = true;



	public OpenIdAuthenticationFilter() {
		super(new AntPathRequestMatcher(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_OPENID, "POST"));
	}

	@Override
	public Authentication attemptAuthentication(HttpServletRequest request,
												HttpServletResponse response) throws AuthenticationException {
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException(
					"Authentication method not supported: " + request.getMethod());
		}

		String openId = obtainOpenId(request);
		String providerId = obtainProviderId(request);

		if (openId == null) {
			openId = "";
		}
		if (providerId == null) {
			providerId = "";
		}
		openId = openId.trim();

		OpenIdAuthenticationToken authRequest = new OpenIdAuthenticationToken(openId, providerId);

		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);

		return this.getAuthenticationManager().authenticate(authRequest);
	}


	protected String obtainOpenId(HttpServletRequest request) {
		return request.getParameter(openIdParameter);
	}

	private String obtainProviderId(HttpServletRequest request) {
		return request.getParameter(providerIdParameter);
	}

	protected void setDetails(HttpServletRequest request,
							  OpenIdAuthenticationToken authRequest) {
		authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
	}

	public void setOpenIdParameter(String openIdParameter) {
		Assert.hasText(openIdParameter, "Username parameter must not be empty or null");
		this.openIdParameter = openIdParameter;
	}

	public void setPostOnly(boolean postOnly) {
		this.postOnly = postOnly;
	}

	public final String getOpenIdParameter() {
		return openIdParameter;
	}
}

常量字典新增  SecurityConstants

供应商 OpenIdAuthenticationProvider

package com.rui.tiger.auth.app.social.openid;

import org.apache.commons.collections.CollectionUtils;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.security.SocialUserDetailsService;

import java.util.HashSet;
import java.util.Set;

/**
 * 验证OpenIdAuthenticationToken
 * 查询社交数据库表UserConnection
 * @author CaiRui
 * @date 2019-04-23 08:30
 */
public class OpenIdAuthenticationProvider implements AuthenticationProvider {


	private SocialUserDetailsService userDetailsService;

	private UsersConnectionRepository usersConnectionRepository;

	/*
	 * (non-Javadoc)
	 *
	 * @see org.springframework.security.authentication.AuthenticationProvider#
	 * authenticate(org.springframework.security.core.Authentication)
	 */
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {

		OpenIdAuthenticationToken authenticationToken = (OpenIdAuthenticationToken) authentication;

		Set<String> providerUserIds = new HashSet<>();
		providerUserIds.add((String) authenticationToken.getPrincipal());//openId
		//UserConnection 这个表里查询信息
		Set<String> userIds = usersConnectionRepository.findUserIdsConnectedTo(authenticationToken.getProviderId(), providerUserIds);

		if(CollectionUtils.isEmpty(userIds) || userIds.size() != 1) {
			throw new InternalAuthenticationServiceException("无法获取用户信息");
		}

		String userId = userIds.iterator().next();

		UserDetails user = userDetailsService.loadUserByUserId(userId);

		if (user == null) {
			throw new InternalAuthenticationServiceException("无法获取用户信息");
		}

		OpenIdAuthenticationToken authenticationResult = new OpenIdAuthenticationToken(user, user.getAuthorities());

		authenticationResult.setDetails(authenticationToken.getDetails());

		return authenticationResult;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.springframework.security.authentication.AuthenticationProvider#
	 * supports(java.lang.Class)
	 */
	@Override
	public boolean supports(Class<?> authentication) {
		return OpenIdAuthenticationToken.class.isAssignableFrom(authentication);
	}

	public SocialUserDetailsService getUserDetailsService() {
		return userDetailsService;
	}

	public void setUserDetailsService(SocialUserDetailsService userDetailsService) {
		this.userDetailsService = userDetailsService;
	}

	public UsersConnectionRepository getUsersConnectionRepository() {
		return usersConnectionRepository;
	}

	public void setUsersConnectionRepository(UsersConnectionRepository usersConnectionRepository) {
		this.usersConnectionRepository = usersConnectionRepository;
	}

}

配置类

package com.rui.tiger.auth.app.social.openid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.security.SocialUserDetailsService;
import org.springframework.stereotype.Component;

/**
 * openId 权限配置类
 * @author CaiRui
 * @date 2019-04-23 08:39
 */
@Component
public class OpenIdAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

	@Autowired
	private SocialUserDetailsService userDetailsService;

	@Autowired
	private UsersConnectionRepository usersConnectionRepository;

	@Autowired
	private AuthenticationFailureHandler authenticationFailureHandler;
	@Autowired
	private AuthenticationSuccessHandler authenticationSuccessHandler;

	@Override
	public void configure(HttpSecurity http) throws Exception {
		OpenIdAuthenticationProvider provider = new OpenIdAuthenticationProvider();
		provider.setUserDetailsService(userDetailsService);
		provider.setUsersConnectionRepository(usersConnectionRepository);

		OpenIdAuthenticationFilter filter = new OpenIdAuthenticationFilter();

		filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
		filter.setAuthenticationFailureHandler(authenticationFailureHandler);
		filter.setAuthenticationSuccessHandler(authenticationSuccessHandler);

		//密码登录后置过滤
		http.
				authenticationProvider(provider)
				.addFilterAfter(filter, UsernamePasswordAuthenticationFilter.class);
	}

}

资源服务器配置openid登录 com.rui.tiger.auth.app.TigerResourceServerConfig#configure

ok 下面我们来测试下

社交登录库中查看社交用户细信息

postman发送测试,同样请求头中添加app相关信息

 

授权码模式 

此模式下,只需要app端把拿到的授权码转发给服务器,再由服务器拿授权码去换令牌,获取成功后不是跳转到首页而是我们自定义的成功处理器,然后走oath2的 token生成逻辑返回我们系统自定义的token;

成功处理器接口及实现

package com.rui.tiger.auth.core.social;

import org.springframework.social.security.SocialAuthenticationFilter;

/**
 * @author CaiRui
 * @date 2019-04-24 16:01
 */
public interface SocialAuthenticationFilterPostProcessor {

	void process(SocialAuthenticationFilter socialAuthenticationFilter);
}

 实现

package com.rui.tiger.auth.app.social.impl;

import com.rui.tiger.auth.core.social.SocialAuthenticationFilterPostProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.social.security.SocialAuthenticationFilter;
import org.springframework.stereotype.Component;

/**
 * @author CaiRui
 * @date 2019-04-24 16:11
 */
@Component
public class AppSocialAuthenticationFilterPostProcessor implements SocialAuthenticationFilterPostProcessor {

	@Autowired
	private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;

	@Override
	public void process(SocialAuthenticationFilter socialAuthenticationFilter) {
		// 这里设置的其实就是之前  重构用户名密码登录里面实现的 tigerAuthenticationSuccessHandler
		//这样就可以返回token
		socialAuthenticationFilter.setAuthenticationSuccessHandler(imoocAuthenticationSuccessHandler);

	}
}

社交配置类进行修改

package com.rui.tiger.auth.core.social;

import org.springframework.social.security.SocialAuthenticationFilter;
import org.springframework.social.security.SpringSocialConfigurer;

/**
 * 自定义SpringSocialConfigurer 用于覆盖默认的社交登陆拦截请求
 *
 * @author CaiRui
 * @Date 2019/1/5 16:15
 */
public class TigerSpringSocialConfigurer extends SpringSocialConfigurer {

	private String filterProcessesUrl;//覆盖默认的/auth 拦截路径

	private SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor;

	public TigerSpringSocialConfigurer(String filterProcessesUrl) {
		this.filterProcessesUrl = filterProcessesUrl;
	}


	@Override
	protected <T> T postProcess(T object) {
		SocialAuthenticationFilter socialAuthenticationFilter = (SocialAuthenticationFilter) super.postProcess(object);
		socialAuthenticationFilter.setFilterProcessesUrl(filterProcessesUrl);
		if (socialAuthenticationFilterPostProcessor!=null){
			socialAuthenticationFilterPostProcessor.process(socialAuthenticationFilter);
		}
		return (T) socialAuthenticationFilter;
	}

	public SocialAuthenticationFilterPostProcessor getSocialAuthenticationFilterPostProcessor() {
		return socialAuthenticationFilterPostProcessor;
	}

	public void setSocialAuthenticationFilterPostProcessor(SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor) {
		this.socialAuthenticationFilterPostProcessor = socialAuthenticationFilterPostProcessor;
	}
}

注入

package com.rui.tiger.auth.core.social;

import com.rui.tiger.auth.core.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.social.UserIdSource;
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.ConnectionRepository;
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.ConnectController;
import org.springframework.social.connect.web.ProviderSignInUtils;
import org.springframework.social.security.AuthenticationNameUserIdSource;
import org.springframework.social.security.SpringSocialConfigurer;

import javax.sql.DataSource;

/**
 * 社交配置类
 *
 * @author CaiRui
 * @Date 2019/1/5 11:46
 */
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {

    @Autowired
    private DataSource dataSource;//数据源
    @Autowired
    private SecurityProperties securityProperties;
    //第三方登录直接注册用户 可以不实现 跳到注册界面
    @Autowired(required = false)
    private ConnectionSignUp connectionSignUp;
    //浏览器项目不用实现这个接口
    @Autowired(required = false)
    private SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor;


    /**
     * 默认配置类  包括了过滤器SocialAuthenticationFilter 添加到security过滤链中
     *
     * @return
     */
    @Bean
    public SpringSocialConfigurer tigerSpringSocialConfigurer() {
        TigerSpringSocialConfigurer tigerSpringSocialConfigurer = new TigerSpringSocialConfigurer(
                securityProperties.getSocial().getFilterProcessesUrl());
        //配置自己的注册界面
        tigerSpringSocialConfigurer.signupUrl(securityProperties.getBrowser().getSignupUrl());
        tigerSpringSocialConfigurer.setSocialAuthenticationFilterPostProcessor(socialAuthenticationFilterPostProcessor);
        return tigerSpringSocialConfigurer;
    }

    /**
     * 业务系统用户和服务提供商用户对应关系,保存在表UserConnection
     * JdbcUsersConnectionRepository.sql 中有建表语句
     * userId 业务系统Id
     * providerId 服务提供商的Id
     * providerUserId  同openId
     * Encryptors  加密策略 这里不加密
     *
     * @param connectionFactoryLocator
     * @return
     */
    @Override
    public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
        JdbcUsersConnectionRepository jdbcUsersConnectionRepository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
        //设定表UserConnection的前缀 表名不可以改变
        //jdbcUsersConnectionRepository.setTablePrefix("tiger_");
        if(connectionSignUp!=null){
            jdbcUsersConnectionRepository.setConnectionSignUp(connectionSignUp);
        }
        return jdbcUsersConnectionRepository;
    }

    /**
     * 从认证中获取用户信息
     *
     * @return
     */
    @Override
    public UserIdSource getUserIdSource() {
        return new AuthenticationNameUserIdSource();
    }

    /**
     * social和注册互动工具类
     * @return
     */
    @Bean
    public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator){
        return new ProviderSignInUtils(connectionFactoryLocator,getUsersConnectionRepository(connectionFactoryLocator));
    }



    //https://docs.spring.io/spring-social/docs/1.1.x-SNAPSHOT/reference/htmlsingle/#creating-connections-with-connectcontroller
    //社交账号绑定和解绑处理帮助类
    @Bean
    public ConnectController connectController(
            ConnectionFactoryLocator connectionFactoryLocator,
            ConnectionRepository connectionRepository) {
        return new ConnectController(connectionFactoryLocator, connectionRepository);
    }
}

ok 开启我们的测试验证

测试步骤:

1. demo项目引用浏览器项目,做qq授权登录,拿到授权码(断点调试关闭服务器还是执行,这里我们先注释这段请求token的代码)

org.springframework.social.security.provider.OAuth2AuthenticationService#getAuthToken

com.rui.tiger.auth.core.social.qq.connect.QQOAuth2Template#postForAccessGrant

授权码:D9463227AB66B2D7517BA2616537324E

state:d2436704-33d3-4933-a2b8-67da5253f3a1

2.关闭服务器,再把demo切换到app项目,注意要把上面注释的请求token的代码放开

3.postman带上授权码和client信息发送请求token信息

ok 成功返回我们自己的token信息

下章我们重构社交注册逻辑 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值