Jhipster + REST client + authentication

2019-02-17 05:37发布

问题:

I need to understand how to authenticate a REST client (could be Paw, could be an android app, an iOs app using AFNetworking with jHipster and I think, more in general, with spring-boot of which I am no expert).

While I am able to obtain a token when logged in a browser, and subsequently use this token in the following requests, I do not understand how I can authenticate in the first place using RESTful best practices.

For example, in Paw.app, I can pass a Basic authentication, or Oauth2, but I don't understand how to get the session token simply authenticating as I do on a web browser.

Similarly, in AFNetworking I am able to pass basic authentication, e.g.

NSString*auth=[NSString stringWithFormat:@"%@:%@", @"admin", @"admin"];
NSString *authValue = [NSString stringWithFormat:@"Basic %@", [auth base64EncodedString]];
[manager.requestSerializer setValue:authValue forHTTPHeaderField:@"Authorization"];

But I struggle to understand how to authenticate with the session security which is bundled in jHipster/spring boot.

回答1:

First of all, do not use HTTP session authentication for mobile apps.

On the other hand, Oauth2 or JWT works fine with mobile apps. The basic idea behind them is to get a token from Jhipster to mobile the token has an expiry time. In that time you can use the token to access any REST API of Jhipster to access data.

Below I am showing the code snippet of how I was using the Jhipster rest API in my angularjs based ionic app. I hope it gives the idea of what you need to do.

uncomment cors in application.yml inside jhipster

cors: #By default CORS are not enabled. Uncomment to enable.
        allowed-origins: "*"
        allowed-methods: GET, PUT, POST, DELETE, OPTIONS
        allowed-headers: "*"
        exposed-headers:
        allow-credentials: true
        max-age: 1800

To access REST API with Oauth2 authentication in ionic you must first get the token in the ionic app by

    $http({
    method: "post", 
    url: "http://192.168.0.4:8085/[Your app name]/oauth/token",
    data:  "username=admin&password=admin&grant_type=password&scope=read write&client_secret=my-secret-token-to-change-in-production&client_id=auth2Sconnectapp",
    withCredentials: true,
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'Accept': 'application/json',
      'Authorization': 'Basic ' + 'YXV0aDJTY29ubmVjdGFwcDpteS1zZWNyZXQtdG9rZW4tdG8tY2hhbmdlLWluLXByb2R1Y3Rpb24='
      }
  })                
  .success(function(data) {
      alert("success: " + data);
  })
  .error(function(data, status) {
      alert("ERROR: " + data);
  });

Here "YXV0aDJTY29ubmVjdGFwcDpteS1zZWNyZXQtdG9rZW4tdG8tY2hhbmdlLWluLXByb2R1Y3Rpb24=" is equal to (clientId + ":" + clientSecret)--all base64-encoded

The above $http if successful will give you this JSON which contains token and it's expiry time

{
  "access_token": "2ce14f67-e91b-411e-89fa-8169e11a1c04",
  "token_type": "bearer",
  "refresh_token": "37baee3c-f4fe-4340-8997-8d7849821d00",
  "expires_in": 525,
  "scope": "read write"
}

Take notice of "access_token" and "token_type" if you want to access any API this is what you have to use. We send the token with API to access data until the token expires then we either refresh it or access for a new one.

For example

$http({
    method: "get", 
    url: "http://192.168.0.4:8085/auth-2-sconnect/api/countries",
    withCredentials: true,
    headers: {
      'Authorization':' [token_type] + [space] + [access_token] '
      }
  })                
  .success(function(data) {
      alert("success: " + data);
  })
  .error(function(data, status) {
      alert("ERROR: " + data);
  });


回答2:

Here a summarisation of how I implemented the solution. It’s real swift code, but please take it as pseudocode, as it might be incorrect.

  1. make a call to whatever method you need to call, passing in such method a callback (block, or equivalent) for the success and one for the failure

    func action(
        URLString:String,
        method:Method,
        encoding:Encoding = .JSON,
        parameters:[String : AnyObject]?,
        success:(statusCode:Int, responseObject:AnyObject)->Void,
        failure:(statusCode:Int, error:NSError)->Void
    )
    
  2. Inside the method es. /events you handle a particular case of failure, which is when the status code is 401.

     if(r!.statusCode==ResponseCodes.HTTP_UNAUTHORIZED.rawValue){
    
         loginAndAction(URLString, method: method, encoding: encoding, parameters: parameters, success: success, failure: failure)
    
     }else{
    
         failure(statusCode: response.response!.statusCode, error:response.result.error!)
    
     }
    
  3. In this particular case, instead of returning back the result and calling the failure callback, you call a login() method which, after the necessary parameters, accept the original success() callback

    func loginAndAction(
        URLString:String,
        method:Method,
        encoding: Encoding,
        parameters:[String:AnyObject]?,
        success:(statusCode:Int, responseObject:AnyObject)->Void,
        failure:(statusCode:Int, error:NSError)->Void
        )->Void
    
  4. if the authentication succeeds

    var d:[String:AnyObject] = response.result.value as! [String:AnyObject]
    self.authToken = d["access_token"] as! String
    
    action(URLString, method: method,encoding:encoding, parameters: parameters, success: success, failure: failure)
    

at this point the method action could use a proper working token.

This should happen only once a day (based on the token expiration), and it is a mechanism appliable to the oauth2 refresh_token call.