简体   繁体   English

Typo3 自定义 FE 登录没有表单

[英]Typo3 custom FE login without form

Update (2020-04-07) : Solution code based on accepted answer below the line.更新 (2020-04-07) :基于行下方已接受答案的解决方案代码。

I am building an extension for Typo3 v9.5 using extbase where I am trying to implement a different entry point for logging in frontend users (think: one-time token via e-mail).我正在使用 extbase 为 Typo3 v9.5 构建一个扩展,我试图实现一个不同的入口点来登录前端用户(想想:通过电子邮件的一次性令牌)。 So the login form would be bypassed and the credentials retrieved from the DB to log in the user via code.因此,登录表单将被绕过,并从数据库中检索凭据以通过代码登录用户。 With that said, I want to reuse as much of the login and session logic as possible, that is already there.话虽如此,我想尽可能多地重用登录和会话逻辑,这已经存在。

I have found a passable solution that superficially seems to work, but I'm not sure, if that is something that will keep working across updates ( $GLOBALS['TSFE']->fe_user being converted to the Context API) and I'm pretty convinced it's not the most elegant way.我找到了一个表面上似乎可行的可行解决方案,但我不确定,如果这会在更新中继续有效( $GLOBALS['TSFE']->fe_user被转换为 Context API)并且我'我非常确信这不是最优雅的方式。 Ideally, I could use this with minor adaptions in the upcoming Typo3 v10.理想情况下,我可以在即将发布的 Typo3 v10 中稍作修改来使用它。 In some cases I'm not even sure I'm using APIs that are supposed to be used publicly.在某些情况下,我什至不确定我使用的是应该公开使用的 API。

Let me lay out what I've done so far in the most compact way I can think of:让我以我能想到的最紧凑的方式列出我迄今为止所做的事情:

<?php
# FILE: my_ext/Classes/Authentication/CustomAuthentication.php
use \TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;

class CustomAuthentication extends FrontendUserAuthentication {
  // FrontendUserAuthentication seems to be hard-coded to gather credentials
  // that were submitted via login form, so we have to work around that
  protected $_formData = [];

  // new method, set credentials normally entered via form
  public function setLoginFormData(array $data) {
    $this->_formData = $data;
  }

  // new method, set storage PID where user records are located, set via Extbase Controller/TS
  public function setStoragePid(int $pid) {
    $this->checkPid_value = $pid;
  }

  // override, ignore parent logic, simply return custom data
  public function getLoginFormData() {
    return $this->_formData;
  }
}
<?php
# FILE: my_ext/Classes/Controller/SessionController.php
use \TYPO3\CMS\Core\Context\Context;
use \TYPO3\CMS\Core\Context\UserAspect;
use \TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use MyExt\MyVendor\Authentication\CustomAuthentication;

class SessionController extends ActionController {
  // controller action
  public function someAction() {
    /*...*/
    $loginData = [ /* assume this is retrieved from somewhere */ ];
    $this->login($loginData);
    /*...*/
  }

  // perform login
  protected function login($data) {
    $feAuth = $this->objectManager->get(CustomAuthentication::class);
    // use my new methods to inject data
    $feAuth->setLoginFormData($data);
    $feAuth->setStoragePid($this->settings['pid']);
    // the next part imitates what is going on in 
    // typo3/sysext/frontend/Classes/Middleware/FrontendUserAuthenticator.php
    $feAuth->start();
    $feAuth->unpack_uc(); // necessary?
    $feAuth->fetchGroupData();

    // login successful?
    if(is_array($feAuth->user) && !empty($feAuth->groupData['uid']) {
      $this->setGlobals($feAuth, $feAuth->groupData['uid']);
      $feAuth->updateOnlineTimestamp(); // necessary?
    }
  }

  // perform logout
  protected function logout() {
    //$feAuth = $GLOBALS['TSFE']->fe_user; // deprecated?
    $feAuth = $this->objectManager->get(FrontendUserAuthentication::class);
    // 'rehydrate' the pristine object from the existing session
    $feAuth->start();
    $feAuth->unpack_uc(); // necessary?

    $feAuth->logoff();
    $feAuth->start(); // create fresh session ID, so we can use flash messages and stuff
    $this->setGlobals($feAuth);
  }

  protected function setGlobals(FrontendUserAuthentication $auth, array $grpData=[]) {
    $GLOBALS['TSFE']->fe_user = $feAuth; // TODO remove in Typo3 v10?

    $ctx = $this->objectManager->get(Context::class);
    $ctx->setAspect('frontend.user', $this->objectManager->get(UserAspect::class, $feAuth, $groupData));
    $feAuth->storeSessionData(); // necessary?
  }
}

I guess the question I have would be, whether there is a better way to do that or if anyone more familiar with the internals of Typo3 could comment, if this is acutally a viable possibility to achieve what I want to do with it.我想我的问题是,是否有更好的方法来做到这一点,或者是否有更熟悉 Typo3 内部结构的人可以发表评论,如果这实际上是实现我想要做的事情的可行可能性。 Thanks!谢谢!


Update (2020-04-07):更新 (2020-04-07):

I followed the suggestion from the accepted answer, and I'm posting my code, so other people may be able to use it, if necessary (in mostly abbreviated form).我遵循了接受的答案中的建议,并发布了我的代码,因此其他人可能会在必要时使用它(主要以缩写形式)。

Below is the service class, that will handle token verification.下面是服务类,它将处理令牌验证。

# FILE: my_ext/Classes/Service/TokenAuthenticationService.php
<?php
use \TYPO3\CMS\Core\Authentication\AbstractAuthenticationService;
use \TYPO3\CMS\Core\Authentication\LoginType;
use \TYPO3\CMS\Core\Database\ConnectionPool;
use \TYPO3\CMS\Core\Utility\GeneralUtility;

class TokenAuthenticationService extends AbstractAuthenticationService {
  protected $timestamp_column = 'tstamp';  // last-changed timestamp
  protected $usertoken_column = 'tx_myext_login_token';

  public function getUser() {
    if($this->login['status'] !== LoginType::LOGIN) {
      return false;
    }

    if((string)$this->login['uname'] === '') {
      $this->logger->warning('Attempted token login with empty token', ['remote'=>$this->authInfo['REMOTE_ADDR']]);
      return false;
    }

    // fetch user record, make sure token was set at most 12 hours ago
    $qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->db_user['table']);
    $where_clause = $qb->expr()->andX(
      $qb->expr()->eq($this->usertoken_column, $qb->expr()->literal($this->login['uname'])),
      $qb->expr()->gte($this->timestamp_column, (int)strtotime('-12 hour'))
    );

    // Typo3 v10 API will change here!
    $user = $this->fetchUserRecord('', $where_clause);
    if(!is_array($user)) {
      $this->logger->warning('Attempted token login with unknown or expired token', ['token'=>$this->login['uname']]);
      return false;
    } else {
      $this->logger->info('Successful token found', ['id'=>$user['uid'], 'username'=>$user['username'], 'token'=>$this->login['uname']]);
    }

    return $user;
  }

  public function authUser(array $user): int {
    // check, if the token that was submitted matches the one from the DB
    if($this->login['uname'] === $user[$this->usertoken_column]) {
      $this->logger->info('Successful token login', ['id'=>$user['uid'], 'username'=>$user['username'], 'token'=>$this->login['uname']]);
      return 200;
    }

    return 100;  // let other auth services try their luck
  }
}

Then register the Service:然后注册服务:

# FILE: my_ext/ext_localconf.php
// Add auth service for token login
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addService(
  $_EXTKEY,
  'auth',
  \MyVendor\MyExt\Service\TokenAuthenticationService::class,
  [
    'title' => 'Token Auth',
    'description' => 'Allows FE user login via one-time token',
    'subtype' => 'getUserFE,authUserFE',
    'available' => true,
    'priority' => 60,
    'quality' => 50,
    'className' => \MyVendor\MyExt\Service\TokenAuthenticationService::class
  ]
);

When creating a token, the user gets a link to a page with the added token parameter, like:创建令牌时,用户会获得一个带有添加的令牌参数的页面链接,例如:

[...]/index.php?id=123&tx_myext_pi[action]=tokenAuth&tx_myext_pi[token]=whatever-was-stored-in-the-db

Since we need a few parameters to trigger the login middleware, we render a mostly hidden form on that landing page, which prompts the user to 'confirm'.由于我们需要一些参数来触发登录中间件,因此我们在该登录页面上呈现一个大部分隐藏的表单,提示用户“确认”。

<!-- FILE: my_ext/Resources/Private/Templates/Some/TokenAuth.html -->
<f:if condition="{token}">
  <h2>Token Login</h2>

  <p>Please confirm</p>

  <f:form action="doLogin" fieldNamePrefix="">
    <f:form.hidden name="logintype" value="login" />
    <f:form.hidden name="pid" value="{settings.membersPid}" />
    <f:form.hidden name="user" value="{token}" />
    <f:form.button>Confirm</f:form.button>
  </f:form>
</f:if>

That request will now automatically perform the login, with all the right parameters.该请求现在将使用所有正确的参数自动执行登录。 In your controller action, you can then add some kind of flash message or redirect to wherever it makes sense.在您的控制器操作中,您可以添加某种 Flash 消息或重定向到任何有意义的地方。

# FILE: my_ext/Classes/Controller/SomeController.php
<?php
use \TYPO3\CMS\Core\Context\Context;
use \TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class SomeController extends ActionController {
  public function doLoginAction() {
    $ctx = $this->objectManager->get(Context::class);
    if($ctx->getPropertyFromAspect('frontend.user', 'isLoggedIn')) {
      // success
    } else {
      // failure
    }
  }
}

You should implement an authentication service this Service only needs to imlement the "authUser()" function.你应该实现一个身份验证服务,这个服务只需要实现“authUser()”函数。 So every thing else might be handled by the core authentication.因此,其他所有事情都可能由核心身份验证处理。

See here for details https://docs.typo3.org/m/typo3/reference-services/8.7/en-us/Authentication/Index.html有关详细信息,请参阅此处https://docs.typo3.org/m/typo3/reference-services/8.7/en-us/Authentication/Index.html

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM