4.4.3. Custom Authentication
Authentication mechanisms can provide access tokens by key, link, LDAP login and password, etc. REST API uses its own authentication mechanism that cannot be modified. In order to use custom authentication process, you need to create a REST controller and use its URL.
Let’s consider the custom authentication mechanism that enables getting an OAuth token by a promo code. In the following example we will use a sample application that contains the Coupon
entity with code
attribute. We will send this attribute’s value as an authentication parameter in GET request.
-
Create a
Coupon
entity with thecode
attribute:@Column(name = "CODE", unique = true, length = 4) protected String code;
-
Create a user with promo-user login on behalf of which the authentication will be performed.
-
Create a new Spring configuration file with name
rest-dispatcher-spring.xml
under the root package (com.company.demo
) of web module. The content of the file must be as follows:<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>
-
Include the file into the
cuba.restSpringContextConfig
application property in themodules/web/src/web-app.properties
file:cuba.restSpringContextConfig = +com/company/demo/rest-dispatcher-spring.xml
-
Create the
rest
package under the root package of web module and implement the custom Spring MVC controller in it. Use theOAuthTokenIssuer
bean to generate and issue the REST API token for a user after the custom authentication:@RestController @RequestMapping("auth-code") public class AuthCodeController { @Inject private OAuthTokenIssuer oAuthTokenIssuer; @Inject private LoginService loginService; @Inject private Configuration configuration; @Inject private DataManager dataManager; @Inject private MessageTools messageTools; // 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 = loginService.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; } } }
-
Exclude the
rest
package from scanning in web/core modules: theOAuthTokenIssuer
bean is available only in REST API context, and scanning for it in the application context will cause an error.<context:component-scan base-package="com.company.demo"> <context:exclude-filter type="regex" expression="com\.company\.demo\.web\.rest\..*"/> </context:component-scan>
-
Now users will be able to obtain OAuth2 access code using GET HTTP request with the
code
parameter tohttp://localhost:8080/app/rest/auth-code?code=A325
The result will be:
{"access_token":"74202587-6c2b-4d74-bcf2-0d687ea85dca","token_type":"bearer","expires_in":43199,"scope":"rest-api"}
The obtained access token should then be passed to REST API, as described in the documentation.