Spring Security(十四):认证(OAuth2)-App社交登录获取token

一:简介

OAuth2授权模式有四种,其中有标准模式、简化模式等,第三方提供的社交登录采用的授权模式会有不同

  • 简化模式(简化模式返回的是openId或者是access_token)
  • 标准模式(标准授权模式返回的是code)

简化模式获取token

App通过第三方提供的SDK可以获取到第三方的openId或者是第三方对应的access_token, 但是第三方提供的access_token只能访问第三方的接口,不能访问自己服务的接口。我们要做的就是根据第三方的openId来换取自己服务对应的令牌。实现逻辑和短信验证码的逻辑一样,就是自定义一套认证逻辑。

标准模式获取token

标准模式获取token只需要设置社交认证过滤器SocialAuthenticationFilter认证成功后对应的成功处理器即可

二:简化模式获取token

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springboot-security-example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-security-example</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.4.RELEASE</version>
            <scope>compile</scope>
        </dependency>


        <dependency>
            <groupId>org.springframework.social</groupId>
            <artifactId>spring-social-web</artifactId>
            <version>1.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.social</groupId>
            <artifactId>spring-social-core</artifactId>
            <version>1.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.social</groupId>
            <artifactId>spring-social-config</artifactId>
            <version>1.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.social</groupId>
            <artifactId>spring-social-security</artifactId>
            <version>1.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.yml

server:
  port: 8080

# Redis数据库索引(默认为0)
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root123
  redis:
    database: 0
    host: localhost
    port: 6379
    password:

logging:
  level:
    org.springframework: debug

OpenIdAuthenticationToken

public class OpenIdAuthenticationToken extends AbstractAuthenticationToken {

	private static final long serialVersionUID = 1L;

	/** 身份 */
	private final Object principal;
	/** 服务提供商 */
	private String providerId;


	public OpenIdAuthenticationToken(Object openId, String providerId) {
		super(null);
		this.principal = openId;
		this.providerId = providerId;
		setAuthenticated(false);
	}


	public OpenIdAuthenticationToken(Object openId, Collection<? extends GrantedAuthority> authorities) {
		super(authorities);
		this.principal = openId;
		super.setAuthenticated(true);
	}

	@Override
	public Object getPrincipal() {
		return this.principal;
	}
	
	public String getProviderId() {
		return this.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();
	}

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

OpenIdAuthenticationFilter

public class OpenIdAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
		private boolean postOnly = true;

		public OpenIdAuthenticationFilter(String loginProcessUrlOpenId) {
			super(new AntPathRequestMatcher(loginProcessUrlOpenId, "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());
			}

			// 封装OpenIdAuthenticationToken
			String openId = obtainOpenId(request);
			String providerId = obtainProviderId(request);
			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) {
			String openId = request.getParameter("openId");
			if (openId == null) {
				openId = "";
			}

			openId = openId.trim();
			return openId;
		}
		protected String obtainProviderId(HttpServletRequest request) {
			String providerId = request.getParameter("providerId");
			providerId = providerId.trim();
			return providerId;
		}

		protected void setDetails(HttpServletRequest request,
                                  OpenIdAuthenticationToken authRequest) {
			authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
		}
		
		public void setPostOnly(boolean postOnly) {
			this.postOnly = postOnly;
		}
}
public class OpenIdAuthenticationProvider implements AuthenticationProvider {

	private SocialUserDetailsService userDetailsService;
	
	private UsersConnectionRepository usersConnectionRepository;
	
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {

		// 从数据库UserConnection表查询数据
		OpenIdAuthenticationToken authenticationToken = (OpenIdAuthenticationToken) authentication;
		Set<String> providerUserIds = new HashSet<>();
		providerUserIds.add((String)authenticationToken.getPrincipal());
		Set<String> userIds = usersConnectionRepository.findUserIdsConnectedTo(authenticationToken.getProviderId(), providerUserIds);
		if (CollectionUtils.isEmpty(userIds) || userIds.size() != 1) {
			throw new InternalAuthenticationServiceException("无法获取用户信息");
		}

		// 根据用户id查询用户信息
		String userId = userIds.iterator().next();
		UserDetails user = userDetailsService.loadUserByUserId(userId);
		if (user == null) {
			throw new InternalAuthenticationServiceException("无法获取用户信息");
		}

		// 将用户信息封装的Token中,完成认证
		OpenIdAuthenticationToken authenticationResult = new OpenIdAuthenticationToken(user, user.getAuthorities());
		authenticationResult.setDetails(authenticationToken.getDetails());
		return authenticationResult;
	}

	@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;
	}
}

MyConnecitonSignUp

@Component
public class MyConnecitonSignUp implements ConnectionSignUp {
    @Override
    public String execute(Connection<?> connection) {
        // 根据社交用于信息默认创建用户并返回用户的唯一标识
        return connection.getDisplayName();
    }
}
public class MySpringSocialConfigurer extends SpringSocialConfigurer {

    private String filterProcessesUrl;

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

    @Override
    protected <T> T postProcess(T object) {
        SocialAuthenticationFilter filter = (SocialAuthenticationFilter) super.postProcess(object);
        filter.setFilterProcessesUrl(filterProcessesUrl);
        return (T) filter;
    }
}

SocialConfig

@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private MyConnecitonSignUp myConnecitonSignUp;

    @Override
    public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
        // Encryptors.noOpText() 对数据不加密
        JdbcUsersConnectionRepository jdbcUsersConnectionRepository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
        // 只能设置表前缀,但是不能修改表名
        jdbcUsersConnectionRepository.setTablePrefix("");
        if (myConnecitonSignUp != null) {
            jdbcUsersConnectionRepository.setConnectionSignUp(myConnecitonSignUp);
        }
        return jdbcUsersConnectionRepository;
    }

    @Bean
    public MySpringSocialConfigurer mySpringSocialConfigurer() {
        MySpringSocialConfigurer springSocialConfigurer = new MySpringSocialConfigurer("/qqLogin");
        springSocialConfigurer.userIdSource(getUserIdSource());
        springSocialConfigurer.signupUrl("/signup");
        return springSocialConfigurer;
    }

    @Override
    public UserIdSource getUserIdSource() {
        return new AuthenticationNameUserIdSource();
    }

    @Bean
    public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator) {
        return new ProviderSignInUtils(connectionFactoryLocator, getUsersConnectionRepository(connectionFactoryLocator));
    }

    @Bean
    public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator, ConnectionRepository connectionRepository) {
        return new ConnectController(connectionFactoryLocator, connectionRepository);
    }
}

OpenIdAuthenticationSecurityConfig

@Component
public class OpenIdAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
	
	@Autowired
	private AuthenticationSuccessHandler authenticationSuccessHandler;
	
	@Autowired
	private AuthenticationFailureHandler authenticationFailureHandler;
	
	@Autowired
	private SocialUserDetailsService userDetailsService;
	
	@Autowired
	private UsersConnectionRepository usersConnectionRepository;
	
	@Override
	public void configure(HttpSecurity http) throws Exception {
		OpenIdAuthenticationFilter openIdAuthenticationFilter = new OpenIdAuthenticationFilter("/authentication/openId");
		openIdAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
		openIdAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
		openIdAuthenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler);
		
		OpenIdAuthenticationProvider provider = new OpenIdAuthenticationProvider();
		provider.setUserDetailsService(userDetailsService);
		provider.setUsersConnectionRepository(usersConnectionRepository);
		
		http
			.authenticationProvider(provider)
			.addFilterAfter(openIdAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
	}
}

ResourceServerConfiguration

扫描二维码关注公众号,回复: 8614416 查看本文章
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;

    @Autowired
    private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;

    @Autowired
    private OpenIdAuthenticationSecurityConfig openIdAuthenticationSecurityConfig;

    @Override
    public void configure(HttpSecurity http) throws Exception {

        http
                .apply(smsCodeAuthenticationSecurityConfig)
                .and()
                .apply(openIdAuthenticationSecurityConfig)
                .and()
                .formLogin()
                    .loginPage("/authentication/form")
                    .successHandler(myAuthenticationSuccessHandler)
                    .failureHandler(myAuthenticationFailureHandler);

        http.authorizeRequests()
                .antMatchers("/login", "/authentication/form", "/authentication/mobile", "/authentication/openId", "/code/sms").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable();

    }
}
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory() // 使用in-memory存储
                .withClient("clientId")
                .secret(new BCryptPasswordEncoder().encode("clientSecret"))
                .authorizedGrantTypes("authorization_code")
                .scopes("all")
                .redirectUris("http://www.baidu.com");
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }
}
@Slf4j
@Component
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private AuthorizationServerTokenServices authorizationServerTokenServices;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        log.info("login sucesssful {}", objectMapper.writeValueAsString(authentication));

        String header = request.getHeader("Authorization");
        if (header == null || !header.toLowerCase().startsWith("basic ")) {
            throw new UnapprovedClientAuthenticationException("请求头中没有clientId");
        }

        String[] tokens = extractAndDecodeHeader(header, request);
        assert tokens.length == 2;

        String clientId = tokens[0];
        String clientSecret = tokens[1];
        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
        if (clientDetails == null) {
            throw new UnapprovedClientAuthenticationException("clientId配置信息不存在,clientId=" + clientId);
        } else if (!new BCryptPasswordEncoder().matches(clientSecret, clientDetails.getClientSecret())) {
            throw new UnapprovedClientAuthenticationException("clientSecret不匹配,clientId=" + clientId);
        }

        // grantType 为自定义的"custom"
        TokenRequest tokenRequest = new TokenRequest(new HashMap<>(), clientId, clientDetails.getScope(), "custom");
        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
        OAuth2AccessToken accessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);

        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(accessToken));
    }


    /**
     * Decodes the header into a username and password.
     *
     * @throws BadCredentialsException if the Basic header is not present or is not valid
     * Base64
     */
    private String[] extractAndDecodeHeader(String header, HttpServletRequest request)
            throws IOException {

        byte[] base64Token = header.substring(6).getBytes("UTF-8");
        byte[] decoded;
        try {
            decoded = Base64.getDecoder().decode(base64Token);
        }
        catch (IllegalArgumentException e) {
            throw new BadCredentialsException(
                    "Failed to decode basic authentication token");
        }

        String token = new String(decoded, "UTF-8");

        int delim = token.indexOf(":");

        if (delim == -1) {
            throw new BadCredentialsException("Invalid basic authentication token");
        }
        return new String[] { token.substring(0, delim), token.substring(delim + 1) };
    }
}
@Component
public class MyUserDetailsService implements UserDetailsService, SocialUserDetailsService {
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return new User(username, passwordEncoder.encode("123456"), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }

    @Override
    public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException {
        return new SocialUser(userId, passwordEncoder.encode("123456"), true, true, true, true, AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN"));
    }

    public static void main(String[] args) {
        System.out.println(new BCryptPasswordEncoder().encode("123456"));
    }
}

在这里插入图片描述
providerId和openId 对应于数据库中UserConnection表的providerId和providerUserId,UserConnection表中的数据是社交登录时保存下来的。
在这里插入图片描述

在这里插入图片描述

三:标准模式获取token

标准模式获取token只需要设置社交认证过滤器SocialAuthenticationFilter认证成功后对应的成功处理器即可。

在SocialConfig中创建SpringSocialConfigurer实例时将处理器赋值,然后SpringSocialConfigurer#postProcess() 方法中调用处理器的process()方法,处理器就是为SocialAuthenticationFilter设AuthenticationSuccessHandler。

public interface SocialAuthenticationFilterPostProcessor {
    void process(SocialAuthenticationFilter socialAuthenticationFilter);
}

App 社交登录成功后的处理逻辑,调用认证成功处理器,生成并返回token

@Component
public class AppSocialAuthenticationFilterPostProcessor implements SocialAuthenticationFilterPostProcessor {

	@Autowired
	private AuthenticationSuccessHandler authenticationSuccessHandler;
	
	@Override
	public void process(SocialAuthenticationFilter socialAuthenticationFilter) {
		// 设置认证过滤器认证成功后的处理逻辑
		socialAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
	}
}
public class MySpringSocialConfigurer extends SpringSocialConfigurer {

    private String filterProcessesUrl;

    private SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor;

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

    @Override
    protected <T> T postProcess(T object) {
        SocialAuthenticationFilter filter = (SocialAuthenticationFilter) super.postProcess(object);
        filter.setFilterProcessesUrl(filterProcessesUrl);
        // 设置app社交登录成功后的处理
        if (socialAuthenticationFilterPostProcessor != null) {
            socialAuthenticationFilterPostProcessor.process(filter);
        }
        return (T) filter;
    }

    public SocialAuthenticationFilterPostProcessor getSocialAuthenticationFilterPostProcessor() {
        return socialAuthenticationFilterPostProcessor;
    }

    public void setSocialAuthenticationFilterPostProcessor(SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor) {
        this.socialAuthenticationFilterPostProcessor = socialAuthenticationFilterPostProcessor;
    }
}
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private MyConnecitonSignUp myConnecitonSignUp;

    @Autowired
    private SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor;

    @Override
    public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
        // Encryptors.noOpText() 对数据不加密
        JdbcUsersConnectionRepository jdbcUsersConnectionRepository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
        // 只能设置表前缀,但是不能修改表名
        jdbcUsersConnectionRepository.setTablePrefix("");
        if (myConnecitonSignUp != null) {
            jdbcUsersConnectionRepository.setConnectionSignUp(myConnecitonSignUp);
        }
        return jdbcUsersConnectionRepository;
    }

    @Bean
    public MySpringSocialConfigurer mySpringSocialConfigurer() {
        MySpringSocialConfigurer springSocialConfigurer = new MySpringSocialConfigurer("/qqLogin");
        springSocialConfigurer.userIdSource(getUserIdSource());
        springSocialConfigurer.signupUrl("/signup");
        // 设置app社交登录成功后的处理逻辑
        springSocialConfigurer.setSocialAuthenticationFilterPostProcessor(socialAuthenticationFilterPostProcessor);
        return springSocialConfigurer;
    }

    @Override
    public UserIdSource getUserIdSource() {
        return new AuthenticationNameUserIdSource();
    }

    @Bean
    public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator) {
        return new ProviderSignInUtils(connectionFactoryLocator, getUsersConnectionRepository(connectionFactoryLocator));
    }

    @Bean
    public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator, ConnectionRepository connectionRepository) {
        return new ConnectController(connectionFactoryLocator, connectionRepository);
    }
}

对于标准模式获取token,App端只需要通过SDK获取到code值,然后调用服务器的社交登录地址"/<filterProcessesUrl>/<providerId>"即可拿到服务器对应的token了。
在这里插入图片描述

四:App社交账号注册

用户首次使用社交账号登录时,UserConnection表中是没有记录的,当用户社交账号登录时需要往UserConnection表中插入一条数据。

  • 当App通过第三方的SDK获取到授权码code时,可以通过调用自己服务器http://www.example.com:8080/qqLogin/weixin?code=xxxx 并携带Authorization和deviceId两个请求头去请求接口,此时服务器会跳转到http://www.example.com/social/signUp接口,当访问social/signUp接口时服务器会返回401响应码并且会返回第三方用户的一些基本信息

  • 当App收到social/signUp接口响应时再去引导用户去注册或者绑定,当注册或者绑定完成后再调用服务器注册接口"/user/regist", 并携带Content-Type和deviceId请求头,并携带username请求参数去调用注册接口,注册接口会将第三方的用户信息插入到UserConnection表中

public class AppSecurityException extends RuntimeException {

	private static final long serialVersionUID = 1L;

	public AppSecurityException(String msg) {
		super(msg);
	}
}
@Component
public class AppSignupUtils {
	
	@Autowired
	private RedisTemplate<String, Object> redisTemplate;
	
	@Autowired
	private UsersConnectionRepository usersConnectionRepository;
	
	@Autowired
	private ConnectionFactoryLocator connectionFactoryLocator;
	
	public void saveConnectionData(WebRequest request, ConnectionData connectionData) {
		redisTemplate.opsForValue().set(getKey(request), connectionData, 10, TimeUnit.MINUTES);
	}
	
	public void doPostSignUp(WebRequest request, String userId) {
		
		String key = getKey(request);
		if (!redisTemplate.hasKey(key)) {
			throw new AppSecurityException("无法找到缓存的第三方社交账号信息");
		}
		
		ConnectionData connectionData = (ConnectionData) redisTemplate.opsForValue().get(key);
		
		Connection<?> connection = connectionFactoryLocator.getConnectionFactory(connectionData.getProviderId()).createConnection(connectionData);
		
		usersConnectionRepository.createConnectionRepository(userId).addConnection(connection);
	
		redisTemplate.delete(key);
	}
	
	
	private String getKey(WebRequest request) {
		
		String deviceId = request.getHeader("deviceId");
		if (StringUtils.isEmpty(deviceId)) {
			throw new AppSecurityException("设备id不能为空");
		}
		return "connectiondata:" + deviceId;
	}
}
/**
 * 所有bean初始化前后都会经过这个类的方法
 * @author Administrator
 *
 */
@Component
public class SpringSocialBeanPostProcessor implements BeanPostProcessor {

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if ("mySpringSocialConfigurer".equals(beanName)) {
			MySpringSocialConfigurer configurer = (MySpringSocialConfigurer) bean;
			//三方用户openid不存在于user_connection表时,重定向的路径
			configurer.signupUrl("/social/signup");
			return configurer;
		}
		return bean;
	}

}
@Component
public class AppSocialAuthenticationFilterPostProcessor implements SocialAuthenticationFilterPostProcessor {

	@Autowired
	private AuthenticationSuccessHandler authenticationSuccessHandler;
	
	@Override
	public void process(SocialAuthenticationFilter socialAuthenticationFilter) {
		socialAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
	}
}

@RestController
public class AppSecurityController {
	
	@Autowired
	private ProviderSignInUtils providerSignInUtils;
	
	@Autowired
	private AppSignupUtils appSignupUtils;
	
	@GetMapping("/social/signUp")
	@ResponseStatus(HttpStatus.UNAUTHORIZED)
	public SocialUserInfo getSocialUserInfo(HttpServletRequest request){
		SocialUserInfo userInfo = new SocialUserInfo();
		Connection<?> connection = providerSignInUtils.getConnectionFromSession(new ServletWebRequest(request));
		
		if (connection != null)  {
			userInfo.setProviderId(connection.getKey().getProviderId());
			userInfo.setProviderUserId(connection.getKey().getProviderUserId());
			userInfo.setNickname(connection.getDisplayName());
			userInfo.setHeadimg(connection.getImageUrl());
			appSignupUtils.saveConnectionData(new ServletWebRequest(request), connection.createData());
		}
		
		return userInfo;
	}
}

@Data
@ToString
@RequiredArgsConstructor
public class SocialUserInfo {
	private String providerId;
	
	private String providerUserId;
	
	private String nickname;
	
	private String headimg;
}
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;

    @Autowired
    private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;

    @Autowired
    private OpenIdAuthenticationSecurityConfig openIdAuthenticationSecurityConfig;

    @Override
    public void configure(HttpSecurity http) throws Exception {

        http
                .apply(smsCodeAuthenticationSecurityConfig)
                .and()
                .apply(openIdAuthenticationSecurityConfig)
                .and()
                .formLogin()
                    .loginPage("/authentication/form")
                    .successHandler(myAuthenticationSuccessHandler)
                    .failureHandler(myAuthenticationFailureHandler);

        http.authorizeRequests()
                .antMatchers("/login", "/authentication/form", "/authentication/mobile", "/authentication/openId", "/code/sms", "/social/signUp").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable();

    }
}
public class User {
	public interface UserSimpleView{};
	public interface UserDetailView extends UserSimpleView{};
	
	private String id;
	private String username;
	private String password;
	private Date birthday;
	
	@JsonView(UserSimpleView.class)
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	@JsonView(UserDetailView.class)
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	@JsonView(UserSimpleView.class)
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	@JsonView(UserSimpleView.class)
	public Date getBirthday() {
		return birthday;
	}
	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}
	@Override
	public String toString() {
		return "User [id=" + id + ", username=" + username + ", password=" + password + "]";
	}
}
@Slf4j
@RestController
public class UserController {

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private ProviderSignInUtils providerSignInUtils;

    @Autowired
    private AppSignupUtils appSignUpUtils;


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

    @GetMapping(value = "/user/me", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public String me() throws JsonProcessingException {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        Object principal = auth.getPrincipal();
        return objectMapper.writeValueAsString(principal);
    }
}

在这里插入图片描述

发布了308 篇原创文章 · 获赞 936 · 访问量 133万+

猜你喜欢

转载自blog.csdn.net/vbirdbest/article/details/94448303
今日推荐