Доступ к REST API CUBA через кастомный запрос

Добрый день.

На текущий момент есть фронтэнд работающее используя REST со своим бэкэндом (spring boot). Бэкэнд переводится на CUBA. Фрондэнд использует свои URL для REST и, в связи с необходимостью обратной совместимости, изменить их нельзя.

Например для получения получения токена используется следующая ссылка: /oauth/login, когда в кубе ссылка: /rest/v2/oauth/token.

Подскажите пожалуйста каким способ можно реализовать доступность REST API CUBA по кастомному URL (написать контроллер, который будет делать форвард запроса или инжектировать внутренний сервис…)?

Спасибо.

Здравствуйте,

Вы можете реализовать дополнительный контроллер аутентификации на любом URL, как показано в документации: Собственный механизм аутентификации

Кроме того, для маппинга oauth/login можно зарегистрировать дополнительный DispatcherServlet или просто зарегистрировать дополнительный HttpServlet в web.xml, который выполнит редирект.

Ну и последняя опция, просто на прокси-сервере (например Nginx) сделать форвард запросов на нужный URL.

1 симпатия

Спасибо, Юрий. Буду разбираться.

Добрый день, Юрий. Просто хочу уточнить: верно ли я понимаю, что использовать кубовский механизм получения токенов с кастомного url не используя прокси-серверов нельзя?

Спасибо.

Пожалуй это самый простой путь, перенаправить запрос с нужного адреса на URL аутентификации REST-API. И если бы у меня стояла такая задача интеграции, я бы предпочёл обойтись именно настройкой Nginx, а не программированием. А если уж дошло до программирования, то проще клиент настроить / поправить (ну и это странно, если базовый адрес сервера нельзя быстро поменять в клиенте).

1 симпатия

В качестве обходного пути, вы можете добавить новый маппинг для REST сервлета в web.xml:

<servlet-mapping>
    <servlet-name>rest_api</servlet-name>
    <url-pattern>/rest/*</url-pattern>

    <!-- add new URL pattern -->
    <url-pattern>/oauth/*</url-pattern>
</servlet-mapping>

В этом случае, сервлет REST (и все его методы) будет доступен по 2 путям: /rest/ и /oauth/.

Затем, следуя инструкции создать контроллер с маппингом на /login.

В этом примере вход всегда выполняется от admin, реальную логику обработки запроса вам нужно разработать самим:

@Controller
public class DemoAuthController {
    @Inject
    private OAuthTokenIssuer oAuthTokenIssuer;
    @Inject
    private Configuration configuration;
    @Inject
    private TrustedClientService trustedClientService;
    @Inject
    private MessageTools messageTools;

    @RequestMapping(method = RequestMethod.POST, path = "login")
    public ResponseEntity post() {
        // obtain system session to be able to call middleware services
        WebAuthConfig webAuthConfig = configuration.getConfig(WebAuthConfig.class);
        UserSession systemSession;
        try {
            systemSession = trustedClientService.getSystemSession(webAuthConfig.getTrustedClientPassword());
        } catch (LoginException e) {
            throw new RuntimeException("Error during system auth");
        }

        // set security context
        AppContext.setSecurityContext(new SecurityContext(systemSession));
        try {
            // generate token for "promo-user"
            OAuthTokenIssuer.OAuth2AccessTokenResult tokenResult =
                    oAuthTokenIssuer.issueToken("admin", messageTools.getDefaultLocale(), Collections.emptyMap());
            OAuth2AccessToken accessToken = tokenResult.getAccessToken();

            // set security HTTP headers to prevent browser caching of security token
            HttpHeaders headers = new HttpHeaders();
            headers.set(HttpHeaders.CACHE_CONTROL, "no-store");
            headers.set(HttpHeaders.PRAGMA, "no-cache");
            return new ResponseEntity<>(accessToken, headers, HttpStatus.OK);
        } finally {
            // clean up security context
            AppContext.setSecurityContext(null);
        }
    }
}

Полный код проекта можно найти на Github.

Добрый вечер, Юрий.

Спасибо за ответы. Очень полезно.

Добрый день.

В итоге использовался маленький финт ушами. Используется метод получения токена платформы защищенный базовой аутентификацией платформы по пути /rest/oauth/token.

rest-dispatcher-spring.xml модуля portal

    <!-- According to the spec the token endpoint should be secured by the basic authentication -->
<security:http pattern="/rest/oauth/**"
               create-session="stateless"
               authentication-manager-ref="clientAuthenticationManager"
               xmlns="http://www.springframework.org/schema/security">
    <intercept-url pattern="/**" access="isAuthenticated()"/>
    <http-basic entry-point-ref="clientAuthenticationEntryPoint"/>
    <csrf disabled="true"/>
    <cors configuration-source-ref="cuba_RestCorsSource"/>
</security:http>

<bean id="clientAuthenticationEntryPoint"
      class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"/>

<!-- Authentication manager that is used by token endpoint. Checks client credentials in http header -->
<security:authentication-manager id="clientAuthenticationManager">
    <security:authentication-provider user-service-ref="clientDetailsUserDetailsService"/>
</security:authentication-manager>

<bean id="clientDetailsUserDetailsService"
      class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
    <constructor-arg ref="clientDetailsService"/>
</bean>

<!-- The predefined list of API clients. External apps must provide this credentials in Authentication header when
accessing the auth token -->
<oauth2:client-details-service id="clientDetailsService">
    <oauth2:client client-id="${cuba.rest.client.id}"
                   secret="${cuba.rest.client.secret}"
                   access-token-validity="${cuba.rest.client.tokenExpirationTimeSec}"
                   refresh-token-validity="${cuba.rest.client.refreshTokenExpirationTimeSec}"
                   authorized-grant-types="${cuba.rest.client.authorizedGrantTypes}"
                   scope="rest-api"/>
</oauth2:client-details-service>

<bean id="oauthAuthorizationRequestManager"
      class="org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory">
    <constructor-arg name="clientDetailsService" ref="clientDetailsService"/>
</bean>

<!-- Specifies token endpoint.-->
<oauth2:authorization-server token-endpoint-url="/oauth/token"
                             authorization-request-manager-ref="oauthAuthorizationRequestManager"
                             client-details-service-ref="clientDetailsService"
                             token-services-ref="tokenServices">
    <oauth2:refresh-token/>
    <oauth2:password authentication-manager-ref="userAuthenticationManager"/>
</oauth2:authorization-server>

<security:authentication-manager id="userAuthenticationManager">
    <security:authentication-provider ref="userAuthenticationProvider"/>
</security:authentication-manager>

<bean id="userAuthenticationProvider" class="com.haulmont.restapi.auth.CubaUserAuthenticationProvider"/>

<bean id="tokenEnhancer" class="com.haulmont.restapi.auth.CubaTokenEnhancer"/>

<bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
    <property name="supportRefreshToken" value="${cuba.rest.supportRefreshToken}"/>
    <property name="reuseRefreshToken" value="${cuba.rest.reuseRefreshToken}"/>
    <property name="tokenStore" ref="tokenStore"/>
    <property name="clientDetailsService" ref="clientDetailsService"/>
    <property name="tokenEnhancer" ref="tokenEnhancer"/>
</bean>

<bean id="tokenStore" class="com.haulmont.restapi.auth.ClientProxyTokenStore">
    <property name="authenticationKeyGenerator">
        <bean class="com.haulmont.restapi.auth.UniqueAuthenticationKeyGenerator"/>
    </property>
</bean>

<bean id="cuba_ExternalOAuthTokenGranter" class="com.haulmont.restapi.auth.ExternalOAuthTokenGranter">
    <constructor-arg name="tokenServices" ref="tokenServices"/>
    <constructor-arg name="clientDetailsService" ref="clientDetailsService"/>
    <constructor-arg name="requestFactory" ref="oauthAuthorizationRequestManager"/>
</bean>

<!-- Required for HTTP request access from ClientProxyTokenStore
     in case it is called from OAuth2AuthenticationProcessingFilter -->
<bean id="requestContextFilter" class="org.springframework.web.filter.RequestContextFilter"/>

<oauth2:resource-server id="resourceFilter" token-services-ref="tokenServices"/>