使用现有API的FB / Google客户端登录(Ionic)

时间:2017-08-22 20:07:15

标签: java spring facebook cordova ionic-framework

我的筹码是:

  • Ionic 2
  • Java Spring
  • JWT身份验证

我想在我的应用程序中使用相应的cordova插件实现社交登录按钮(Facebook,Google等),登录用户并在我现有的自定义服务器端API上验证并存储他/她的数据。我无法找到关于如何做到这一点的任何好教程。

我希望我的用户使用随机密码保留在数据库中,并且能够从我的应用程序登录。

我想象的是:

(客户端)

FB.login(function(userDetailsAndToken) {
    myBackendAPI.socialLogin(userDetailsAndToken).then(function(user) {
        //Successfully logged in with Facebook!
        this.user = user;
    }
}

并在后端(Java Spring):

@PostMapping("/social/account")
public ResponseEntity socialAccount(@Valid @RequestBody FacebookDTO facebookDTO) {
    validateSocialUser(facebookDTO);

    //if user exists return invalid etc.
    Optional<User> existingUser = userRepository.findOneByEmail(facebookDTO.getEmail());
    if (existingUser.isPresent()) {
        return ResponseEntity.badRequest();
    }

    //User doesn't exist. Proceed with login 
    //map from social to my User entity
    User user = socialMapper.toEntity(facebookDTO);

    userService
        .createUser(user.getLogin(), user.getPassword(),
            user.getFirstName(), user.getLastName(),
            user.getEmail().toLowerCase(), user.getImageUrl(),
            user.getLangKey());
    return new ResponseEntity<>(HttpStatus.CREATED);
}

这可行且安全吗?关于如何实现这一目标的任何好的资源/图书馆或指南?

3 个答案:

答案 0 :(得分:4)

以下是有关使用FB和Google Auth的示例演示,但我不是来自Java背景,因此您将只找到客户端解决方案。

<强>服务

让我们实现登录功能的逻辑。在oauth.service.ts文件夹中创建oauth文件,然后将以下代码粘贴到其中:

import { Injectable, Injector } from '@angular/core';
import { FacebookOauthProvider } from './facebook/facebook-oauth.provider';
import { IOathProvider } from './oauth.provider.interface';
import { GoogleOauthProvider } from './google/google-oauth.provider';
import { OAuthToken } from './models/oauth-token.model';
@Injectable()
export class OAuthService {
    private oauthTokenKey = 'oauthToken';
    private injector: Injector;
constructor(injector: Injector) {
        this.injector = injector;
    }
login(source: string): Promise {
        return this.getOAuthService(source).login().then(accessToken => {
            if (!accessToken) {
                return Promise.reject('No access token found');
            }
let oauthToken = {
                accessToken: accessToken,
                source: source
            };
            this.setOAuthToken(oauthToken);
            return oauthToken;
        });
    }
getOAuthService(source?: string): IOathProvider {
        source = source || this.getOAuthToken().source;
        switch (source) {
            case 'facebook':
                return this.injector.get(FacebookOauthProvider);
            case 'google':
                return this.injector.get(GoogleOauthProvider);
            default:
                throw new Error(`Source '${source}' is not valid`);
        }
    }
setOAuthToken(token: OAuthToken) {
        localStorage.setItem(this.oauthTokenKey, JSON.stringify(token));
    }
getOAuthToken(): OAuthToken {
        let token = localStorage.getItem(this.oauthTokenKey);
        return token ? JSON.parse(token) : null;
    }
}

身份验证提供程序和令牌接口 正如我们已经提到的,IOathProvider应该包含login()函数。因此,我们应该设置以下接口作为IOathProvider对象的抽象类型/模型。在oauth.provider.interface.ts文件夹中创建oauth文件,并在其中加入以下行:

export interface IOathProvider {
    login(): Promise;
}

Facebook和Google身份验证服务

下一步,我们应该为我们的应用程序拥有的每个身份验证提供程序实现服务,即FacebookOauthProviderGoogleOauthProvider

安装依赖项

这时ng2-cordova-oauth库很方便。我们可以通过执行命令来安装它: npm install ng2-cordova-oauth --save

此外,我们的应用程序依赖于Cordova InAppBrowser plugin。我们将安装它:

ionic plugin add cordova-plugin-inappbrowser

不要忘记在package.json文件中包含cordova-plugin-inappbrowser,因此只要您从头开始安装项目,就可以将其与其他插件一起安装。

实施Facebook和Google身份验证提供程序

让我们在facebook-oauth.provider.ts下创建oauth/facebook/ path文件。在此文件中,请在代码段中包含代码:

 import { Injectable } from '@angular/core';
    import { Http } from '@angular/http';
    import { IOathProvider } from '../oauth.provider.interface';
    import { CordovaOauth } from 'ng2-cordova-oauth/oauth';
    import { Facebook } from 'ng2-cordova-oauth/provider/facebook';
    import { Config } from '../../../config';
    interface ILoginResponse {
        access_token: string;
    }
    @Injectable()
    export class FacebookOauthProvider implements IOathProvider {
        private cordovaOauth: CordovaOauth;
        private http: Http;
        private config: Config;
        private facebook: Facebook;
    constructor(http: Http, config: Config) {
            this.http = http;
            this.config = config;
            this.facebook = new Facebook({ clientId: config.facebook.appId, appScope: config.facebook.scope });
            this.cordovaOauth = new CordovaOauth();
        }
    login(): Promise {
            return this.cordovaOauth.login(this.facebook)
                .then((x: ILoginResponse) => x.access_token);
        }
    }

同样,使用CordovaOauth提供的ng2-cordova-oauth library对象,我们将使用自己的Google authentication provider函数实现login()。但是,这里我们从Config传递另一个clientId,该Google对应于我们使用Google Developer Console使用google-oauth.provider.ts配置的应用程序。 因此,请创建一个import { Injectable } from '@angular/core'; import { IOathProvider } from '../oauth.provider.interface'; import { OAuthProfile } from '../models/oauth-profile.model'; import { CordovaOauth } from 'ng2-cordova-oauth/oauth'; import { Google } from 'ng2-cordova-oauth/provider/google'; import { Config } from '../../../config'; import { Http } from '@angular/http'; interface ILoginResponse { access_token: string; } @Injectable() export class GoogleOauthProvider implements IOathProvider { private http: Http; private config: Config; private cordovaOauth: CordovaOauth; private google: Google; constructor(http: Http, config: Config) { this.http = http; this.config = config; this.google = new Google({ clientId: config.google.appId, appScope: config.google.scope }); this.cordovaOauth = new CordovaOauth(); } login(): Promise { return this.cordovaOauth.login(this.google).then((x: ILoginResponse) => x.access_token); } getProfile(accessToken: string): Promise { let query = `access_token=${accessToken}`; let url = `${this.config.google.apiUrl}userinfo?${query}`; return this.http.get(url) .map(x => x.json()) .map(x => { let name = x.name.split(' '); return { firstName: name[0], lastName: name[1], email: x.email, provider: 'google' }; }) .toPromise(); } } 文件并粘贴以下行:

const PORT = 3000;
var objServer = require('express')();
var http = require('http').Server(objServer);
var io = require('socket.io')(http);

//objServer.use(express.static("public"));

objServer.get('/', function (req, res) {
    res.sendFile(__dirname + '/public/index_test.html');
});

//node http server started
http.listen(PORT, function () {
    console.log('listening on *:' + PORT);
});

this文章的完整学分,您可以在Github中找到Working Code。我还没有涵盖整个教程,只包括该教程的部分(谷歌和Facebook)。我们需要安装什么插件以及如何使用TypeScript,如果您需要,那么您可以参考该教程

答案 1 :(得分:2)

我的一个项目的例子。用于JWT身份验证的Java客户端:

import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.web.client.RestTemplate;

import java.util.Map;

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends ResourceServerConfigurerAdapter {

    public MicroserviceSecurityConfiguration() {
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .csrf()
            .disable()
            .headers()
            .frameOptions()
            .disable()
        .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
            .authorizeRequests()
            .antMatchers("/api/profile-info").permitAll()
            .antMatchers("/api/**").authenticated()
            .antMatchers("/management/health").permitAll()
            .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/swagger-resources/configuration/ui").permitAll();
    }

    @Bean
    public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(
            @Qualifier("loadBalancedRestTemplate") RestTemplate keyUriRestTemplate) {

        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setVerifierKey(getKeyFromAuthorizationServer(keyUriRestTemplate));
        return converter;
    }

    @Bean
    public RestTemplate loadBalancedRestTemplate(RestTemplateCustomizer customizer) {
        RestTemplate restTemplate = new RestTemplate();
        customizer.customize(restTemplate);
        return restTemplate;
    }

    private String getKeyFromAuthorizationServer(RestTemplate keyUriRestTemplate) {
        HttpEntity<Void> request = new HttpEntity<Void>(new HttpHeaders());
        return (String) keyUriRestTemplate
            .exchange("http://someserver/oauth/token_key", HttpMethod.GET, request, Map.class).getBody()
            .get("value");

    }
}

社交按钮的Java后端,包括(Google和Facebook):

import package.repository.SocialUserConnectionRepository;
import package.repository.CustomSocialUsersConnectionRepository;
import package.security.social.CustomSignInAdapter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.social.UserIdSource;
import org.springframework.social.config.annotation.ConnectionFactoryConfigurer;
import org.springframework.social.config.annotation.EnableSocial;
import org.springframework.social.config.annotation.SocialConfigurer;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.ConnectionRepository;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.web.ConnectController;
import org.springframework.social.connect.web.ProviderSignInController;
import org.springframework.social.connect.web.ProviderSignInUtils;
import org.springframework.social.connect.web.SignInAdapter;
import org.springframework.social.facebook.connect.FacebookConnectionFactory;
import org.springframework.social.google.connect.GoogleConnectionFactory;
import org.springframework.social.security.AuthenticationNameUserIdSource;
import org.springframework.social.twitter.connect.TwitterConnectionFactory;
// jhipster-needle-add-social-connection-factory-import-package

import javax.inject.Inject;

/**
 * Basic Spring Social configuration.
 *
 * <p>Creates the beans necessary to manage Connections to social services and
 * link accounts from those services to internal Users.</p>
 */
@Configuration
@EnableSocial
public class SocialConfiguration implements SocialConfigurer {
    private final Logger log = LoggerFactory.getLogger(SocialConfiguration.class);

    @Inject
    private SocialUserConnectionRepository socialUserConnectionRepository;

    @Inject
    Environment environment;

    @Bean
    public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator,
            ConnectionRepository connectionRepository) {

        ConnectController controller = new ConnectController(connectionFactoryLocator, connectionRepository);
        controller.setApplicationUrl(environment.getProperty("spring.application.url"));
        return controller;
    }

    @Override
    public void addConnectionFactories(ConnectionFactoryConfigurer connectionFactoryConfigurer, Environment environment) {
        // Google configuration
        String googleClientId = environment.getProperty("spring.social.google.clientId");
        String googleClientSecret = environment.getProperty("spring.social.google.clientSecret");
        if (googleClientId != null && googleClientSecret != null) {
            log.debug("Configuring GoogleConnectionFactory");
            connectionFactoryConfigurer.addConnectionFactory(
                new GoogleConnectionFactory(
                    googleClientId,
                    googleClientSecret
                )
            );
        } else {
            log.error("Cannot configure GoogleConnectionFactory id or secret null");
        }

        // Facebook configuration
        String facebookClientId = environment.getProperty("spring.social.facebook.clientId");
        String facebookClientSecret = environment.getProperty("spring.social.facebook.clientSecret");
        if (facebookClientId != null && facebookClientSecret != null) {
            log.debug("Configuring FacebookConnectionFactory");
            connectionFactoryConfigurer.addConnectionFactory(
                new FacebookConnectionFactory(
                    facebookClientId,
                    facebookClientSecret
                )
            );
        } else {
            log.error("Cannot configure FacebookConnectionFactory id or secret null");
        }

        // Twitter configuration
        String twitterClientId = environment.getProperty("spring.social.twitter.clientId");
        String twitterClientSecret = environment.getProperty("spring.social.twitter.clientSecret");
        if (twitterClientId != null && twitterClientSecret != null) {
            log.debug("Configuring TwitterConnectionFactory");
            connectionFactoryConfigurer.addConnectionFactory(
                new TwitterConnectionFactory(
                    twitterClientId,
                    twitterClientSecret
                )
            );
        } else {
            log.error("Cannot configure TwitterConnectionFactory id or secret null");
        }

        // jhipster-needle-add-social-connection-factory
    }

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

    @Override
    public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
        return new CustomSocialUsersConnectionRepository(socialUserConnectionRepository, connectionFactoryLocator);
    }

    @Bean
    public SignInAdapter signInAdapter() {
        return new CustomSignInAdapter();
    }

    @Bean
    public ProviderSignInController providerSignInController(ConnectionFactoryLocator connectionFactoryLocator, UsersConnectionRepository usersConnectionRepository, SignInAdapter signInAdapter) throws Exception {
        ProviderSignInController providerSignInController = new ProviderSignInController(connectionFactoryLocator, usersConnectionRepository, signInAdapter);
        providerSignInController.setSignUpUrl("/social/signup");
        providerSignInController.setApplicationUrl(environment.getProperty("spring.application.url"));
        return providerSignInController;
    }

    @Bean
    public ProviderSignInUtils getProviderSignInUtils(ConnectionFactoryLocator connectionFactoryLocator, UsersConnectionRepository usersConnectionRepository) {
        return new ProviderSignInUtils(connectionFactoryLocator, usersConnectionRepository);
    }
}

package package.repository;

import package.domain.SocialUserConnection;

import org.springframework.social.connect.*;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class CustomSocialUsersConnectionRepository implements UsersConnectionRepository {

    private SocialUserConnectionRepository socialUserConnectionRepository;

    private ConnectionFactoryLocator connectionFactoryLocator;

    public CustomSocialUsersConnectionRepository(SocialUserConnectionRepository socialUserConnectionRepository, ConnectionFactoryLocator connectionFactoryLocator) {
        this.socialUserConnectionRepository = socialUserConnectionRepository;
        this.connectionFactoryLocator = connectionFactoryLocator;
    }

    @Override
    public List<String> findUserIdsWithConnection(Connection<?> connection) {
        ConnectionKey key = connection.getKey();
        List<SocialUserConnection> socialUserConnections =
            socialUserConnectionRepository.findAllByProviderIdAndProviderUserId(key.getProviderId(), key.getProviderUserId());
        return socialUserConnections.stream()
            .map(SocialUserConnection::getUserId)
            .collect(Collectors.toList());
    };

    @Override
    public Set<String> findUserIdsConnectedTo(String providerId, Set<String> providerUserIds) {
        List<SocialUserConnection> socialUserConnections =
            socialUserConnectionRepository.findAllByProviderIdAndProviderUserIdIn(providerId, providerUserIds);
        return socialUserConnections.stream()
            .map(SocialUserConnection::getUserId)
            .collect(Collectors.toSet());
    };

    @Override
    public ConnectionRepository createConnectionRepository(String userId) {
        if (userId == null) {
            throw new IllegalArgumentException("userId cannot be null");
        }
        return new CustomSocialConnectionRepository(userId, socialUserConnectionRepository, connectionFactoryLocator);
    };
}


package package.security.social;

import package.config.JHipsterProperties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.web.SignInAdapter;
import org.springframework.web.context.request.NativeWebRequest;

import javax.inject.Inject;

public class CustomSignInAdapter implements SignInAdapter {

    @SuppressWarnings("unused")
    private final Logger log = LoggerFactory.getLogger(CustomSignInAdapter.class);

    @Inject
    private UserDetailsService userDetailsService;

    @Inject
    private JHipsterProperties jHipsterProperties;

    @Override
    public String signIn(String userId, Connection<?> connection, NativeWebRequest request) {
        UserDetails user = userDetailsService.loadUserByUsername(userId);
        Authentication newAuth = new UsernamePasswordAuthenticationToken(
            user,
            null,
            user.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(newAuth);
        return jHipsterProperties.getSocial().getRedirectAfterSignIn();
    }
}

仅适用于JWT的示例

import io.github.jhipster.config.JHipsterProperties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.web.SignInAdapter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.context.request.ServletWebRequest;
import javax.servlet.http.Cookie;

public class CustomSignInAdapter implements SignInAdapter {

    @SuppressWarnings("unused")
    private final Logger log = LoggerFactory.getLogger(CustomSignInAdapter.class);

    private final UserDetailsService userDetailsService;

    private final JHipsterProperties jHipsterProperties;

    private final TokenProvider tokenProvider;


    public CustomSignInAdapter(UserDetailsService userDetailsService, JHipsterProperties jHipsterProperties,
            TokenProvider tokenProvider) {
        this.userDetailsService = userDetailsService;
        this.jHipsterProperties = jHipsterProperties;
        this.tokenProvider = tokenProvider;
    }

    @Override
    public String signIn(String userId, Connection<?> connection, NativeWebRequest request){
        try {
            UserDetails user = userDetailsService.loadUserByUsername(userId);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                user,
                null,
                user.getAuthorities());

            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            String jwt = tokenProvider.createToken(authenticationToken, false);
            ServletWebRequest servletWebRequest = (ServletWebRequest) request;
            servletWebRequest.getResponse().addCookie(getSocialAuthenticationCookie(jwt));
        } catch (AuthenticationException ae) {
            log.error("Social authentication error");
            log.trace("Authentication exception trace: {}", ae);
        }
        return jHipsterProperties.getSocial().getRedirectAfterSignIn();
    }

    private Cookie getSocialAuthenticationCookie(String token) {
        Cookie socialAuthCookie = new Cookie("social-authentication", token);
        socialAuthCookie.setPath("/");
        socialAuthCookie.setMaxAge(10);
        return socialAuthCookie;
    }
}

JHipsterProperties只是简单的Properties配置之前的层。 这是由JHipster生成的。您可以生成单片应用程序,并查看示例后端和前端应如何工作以启用社交按钮。

答案 2 :(得分:1)

要保护数据库中的数据,您必须使用任何“散列”数据。算法。我建议使用HMAC SHA 256算法。哈希能够解密数据。要进行散列,您需要使用密钥,您可以根据需要将密钥设置为静态或动态。使用任何RSA加密算法将加密密钥存储在数据库中,并且在散列时,您可以解密密钥并传入散列方法。

使用apache HmacUtils进行散列。

String hashedPassword = HmacUtils.hmacSha1Hex(password,passwordkey);

要进行加密和解密请使用以下教程RSA Encryption and Decryption