5.3. 自定义验证

身份验证机制可以通过密钥、链接、LDAP 登录名和密码等提供 access token。REST API 使用特有的身份验证机制,无法被修改。要使用自定义身份验证过程,需要创建 REST controller 并使用其 URL。

下面我们看看自定义身份验证机制,该机制可以通过推广码获取 OAuth token。在下面的示例中,我们将使用包含带有 code 属性的 Coupon 实体的示例应用程序。我们将此属性的值作为 GET 请求中的身份验证参数发送。

  1. 创建一个带有 code 属性的 Coupon 实体:

    @Column(name = "CODE", unique = true, length = 4)
    protected String code;
  2. 创建一个带有 cuba.restApi.enabled 特殊权限的角色并为角色赋予读取所需实体和属性的权限。

  3. 使用 promo-user 登录名创建一个 用户 ,这个用户将会进行验证。

  4. promo-user 用户分配刚创建的角色。

  5. web 模块的根包(com.company.demo)下创建一个新的名为 rest-dispatcher-spring.xml 的 Spring 配置文件。文件内容必须如下:

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
    
        <context:component-scan base-package="com.company.demo.web.rest"/>
    
    </beans>
  6. 将文件添加至 modules/web/src/web-app.properties 文件中的 cuba.restSpringContextConfig 应用程序属性中:

    cuba.restSpringContextConfig = +com/company/demo/rest-dispatcher-spring.xml
  7. web 模块的根包下创建 rest 包,并在其中实现自定义的 Spring MVC controller。在自定义身份验证后,使用 OAuthTokenIssuer bean 为用户生成并发送 REST API token:

    @RestController
    @RequestMapping("auth-code")
    public class AuthCodeController {
    
        @Inject
        private OAuthTokenIssuer oAuthTokenIssuer;
        @Inject
        private Configuration configuration;
        @Inject
        private DataManager dataManager;
        @Inject
        private MessageTools messageTools;
        @Inject
        private TrustedClientService trustedClientService;
    
        // here we check secret code and issue token using OAuthTokenIssuer
        @RequestMapping(method = RequestMethod.GET)
        public ResponseEntity get(@RequestParam("code") String authCode) {
            // 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 {
                // find coupon with code
                LoadContext<Coupon> loadContext = LoadContext.create(Coupon.class)
                        .setQuery(LoadContext.createQuery("select c from demo_Coupon c where c.code = :code")
                                .setParameter("code", authCode));
    
                if (dataManager.load(loadContext) == null) {
                    // if coupon is not found - code is incorrect
                    return new ResponseEntity<>(new ErrorInfo("invalid_grant", "Bad credentials"), HttpStatus.BAD_REQUEST);
                }
    
                // generate token for "promo-user"
                OAuthTokenIssuer.OAuth2AccessTokenResult tokenResult =
                        oAuthTokenIssuer.issueToken("promo-user", 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);
            }
        }
    
        // POJO for JSON error messages
        public static class ErrorInfo implements Serializable {
            private String error;
            private String error_description;
    
            public ErrorInfo(String error, String error_description) {
                this.error = error;
                this.error_description = error_description;
            }
    
            public String getError() {
                return error;
            }
    
            public String getError_description() {
                return error_description;
            }
        }
    }
  8. web/core 模块的扫描中排除 rest 包:OAuthTokenIssuer bean 仅在 REST API 上下文中可用,在应用程序上下文中扫描它会导致错误。

    <context:component-scan base-package="com.company.demo">
        <context:exclude-filter type="regex" expression="com\.company\.demo\.web\.rest\..*"/>
    </context:component-scan>
  9. 现在,用户将能够使用带有 code 参数的 GET HTTP 请求获取 OAuth2 access code

    http://localhost:8080/app/rest/auth-code?code=A325

    结果将是:

    {"access_token":"74202587-6c2b-4d74-bcf2-0d687ea85dca","token_type":"bearer","expires_in":43199,"scope":"rest-api"}

    然后,应将获得的 access token 传递给 REST API,如文档中所述。