使用电子邮件/密码,Facebook和谷歌进行JWT身份验证,使用单独的API和客户端服务(Java Spring Boot,Angular)

时间:2018-04-11 19:57:37

标签: angular rest authentication spring-boot jwt

我最近写了一个小型的完整堆栈Web应用程序,它有一个单独的rest api(Node.JS)和一个客户端(React)。在那里,我使用JWT实现了无状态身份验证。为此,我使用了npm包护照 passport-facebook-token passport-google-token passport-local 。通过此设置,我能够向用户显示电子邮件/密码登录信息,还可以通过 Facebook Google 进行登录。

在进一步了解详细信息之前,我的主要问题是:是否可以使用电子邮件/密码使用JWT实现状态/无会话身份验证,使用Jaca Spring Boot作为RESTful API时使用Google和Facebook登录和Angular 5作为客户端(在不同的端口/服务器上运行完全分离的项目)?

简而言之,我在NodeJS / React应用程序中实现了如下行为:

NodeJS API - 本地,脸书和谷歌登录的不同路线:

router.post('/login', (req, res, next) => {
  return passport.authenticate('local-login', (err, token, userData) => {
    processAuthResponse(err, res, token, userData);
  })(req, res, next);
});

router.post('/facebook', (req, res, next) => {
  return passport.authenticate('facebook-token', (err, token, userData) => {
    processAuthResponse(err, res, token, userData);
  })(req, res, next);
});

router.post('/google', (req, res, next) => {
  return passport.authenticate('google-token', (err, token, userData) => {
    processAuthResponse(err, res, token, userData);
  })(req, res, next);
});

这里processAuthResponse()只检查护照中传递的args并将一些最终的json发送给客户端。

NodeJS API - Passport Facebook令牌策略。摘要:从Facebook获取配置文件信息,检入数据库,是否存在具有邮件的用户(否则创建)并创建JWT以发送给React客户端。

import FacebookTokenStrategy from 'passport-facebook-token';

import config from '../../config';
import logger from '../services/logger';
import authService from '../services/authService';
import { User } from '../sequelize/models';

const facebook = config.AuthProviders.Facebook;

export default new FacebookTokenStrategy(
  {
    clientID: facebook.ClientID,
    clientSecret: facebook.ClientSecret,
    session: false
  },
  (accessToken, refreshToken, profile, done) => {
    process.nextTick(() => {
      if (!profile.emails) {
        logger.error('Facebook profile has no email address');
        return done({
          success: false,
          message: 'Das Facebook-Profil enthält keine E-Mail Adresse. Füge ihm eine E-Mail Adresse hinzu oder registriere dich mit deiner E-Mail Adresse'
        });
      }

      User.findOne({
        where: { email: profile.emails[0].value } // TODO: How do I know the main email address of profile?
      }).then(user => {
        if (user) {
          const updateData = authService.getSequelizeUpdateObjectForUser(
            user,
            profile.name.givenName,
            profile.name.familyName,
            profile.photos[0].value // TODO: How do I know the main picture of profile?
          );
          if (updateData) {
            user.update(updateData).then(updatedUser => {
              const token = authService.createJwtFromUser(updatedUser.get({
                plain: true
              }));
              return done(null, token);
            });
          } else {
            const token = authService.createJwtFromUser(user.get({
              plain: true
            }));
            return done(null, token);
          }
        } else {
          const email = profile.emails[0].value; // TODO: How do I know the main email address of profile?
          const firstName = profile.name.givenName;
          const lastName = profile.name.familyName;
          const imageUrl = profile.photos[0].value; // TODO: How do I know the main picture of profile?

          authService.createUser('facebook', email, firstName, lastName, imageUrl, (newUser, error) => {
            if (!newUser) {
              logger.error(error);
            }

            const token = authService.createJwtFromUser(newUser);

            return done(null, token);
          });
        }
      });
    });
  }
);

NodeJS API - 与本地策略几乎相同:

import bcrypt from 'bcryptjs';
import { Strategy as LocalStrategy } from 'passport-local';

import logger from '../services/logger';
import authService from '../services/authService';
import { User, UserAuth } from '../sequelize/models';

const ErrorCodes = require('../constants/ErrorCodes');

export default new LocalStrategy(
  {
    usernameField: 'email',
    passwordField: 'password',
    session: false,
    passReqToCallback: true
  },
  (req, email, password, done) => {
    const userData = {
      email: email.trim(),
      password
    };

    User.findOne({
      where: { email: userData.email },
      include: [{ model: UserAuth }]
    }).then(user => {
      if (!user) {
        logger.info(`Local login: User with email '${email}' not found`);
        const error = new Error('E-Mail oder Passwort falsch');
        error.name = ErrorCodes.INCORRECT_CREDENTIALS_ERROR;
        return done(error);
      }

      // TODO: What to do if email is not verified yet?

      user = user.get({ plain: true });

      if (!user.UserAuth || !user.UserAuth.password) {
        logger.info(`Local login: No UserAuth object or password for user with id ${user.id}`);

        const error = new Error('E-Mail oder Passwort falsch');
        error.name = ErrorCodes.INCORRECT_CREDENTIALS_ERROR;

        return done(error);
      }

      bcrypt.compare(password, user.UserAuth.password, (err, isMatch) => {
        if (err) return done(err);

        if (!isMatch) {
          logger.info('Local login: Password did not match');

          const error = new Error('E-Mail oder Passwort falsch');
          error.name = ErrorCodes.INCORRECT_CREDENTIALS_ERROR;

          return done(error);
        }

        const token = authService.createJwtFromUser(user);

        return done(null, token);
      });
    });
  }
);

还有一个Google令牌策略,其工作方式类似于facebook令牌实现。

反应客户端 - Facebook登录组件

import React, { Component } from 'react';
import { FacebookLogin } from 'react-facebook-login-component';
import { Icon } from 'antd';

import './style.css';

import config from '../../../../config';
import authService from '../../../../services/authService';

class FacebookLoginButton extends Component {
  loginFacebook = res => {
    authService.loginFacebook(res.accessToken);
  };

  render = () => {
    return (
      <FacebookLogin
        socialId={config.AuthProviders.Facebook.ClientID}
        className="login-btn login-btn-facebook"
        language="de_DE"
        scope="public_profile,email"
        responseHandler={this.loginFacebook.bind(this)}
        xfbml={true}
        version="v2.5"
      >
        <Icon type="facebook" /> Mit Facebook anmelden
      </FacebookLogin>
    );
  };
}

export default FacebookLoginButton;

这里,react-facebook-login-component是一个下载的npm包。谷歌也有类似的东西。我自己实施了电子邮件/密码登录表单。所有这些组件都会触发我的authService,它会对auth api路由进行API调用(使用axios)(/ auth / login,/ auth / facebook,/ auth / google)。

现在又来了:

我希望在新的完整堆栈应用程序中使用 Java Spring Boot 用于RESTful API,而 Angular 5 用于客户端,这两个都是分离的项目

列出的要求:

  • 使用Java Spring Boot的RESTful API服务
  • 使用Angular 5的SPA客户端
  • 使用Json Web Token进行会话/无状态身份验证(服务器对令牌进行签名,客户端将其存储并随每次服务器请求一起发送)
  • 使用电子邮件/密码登录时,服务器应将新的JWT传递给客户端
  • 使用Facebook或Google登录时,服务器还应将新的JWT(自己的JWT,而不是社交提供商本身的访问令牌)传递给客户端

我现在使用/ login路由实现了我的Java Spring Boot REST Api,它已经为本地登录创建了JWT。它来自本教程:https://www.callicoder.com/spring-boot-spring-security-jwt-mysql-react-app-part-2/

但是,我不知道如何实现这两者:电子邮件/密码和社交提供商(Facebook / Google)身份验证,因为我是Java Spring Boot的新手。

是否需要 Spring Social (我现在不知道它是如何维护的)或 Spring Security 足够的情况?

非常感谢你提前解答的每一个小提示!

0 个答案:

没有答案