For my Bachelor's-thesis I have to implement different kinds of Authentication and Authorization with different Frameworks.
Currently im at the OAuth2 chapter and have to implement it in the Play Framework (in Java). (I have already implemented it with Spring Boot)
While researching on how to approach this Problem, so far, I could not find a lot of helpful tips.
One of the main Questions I have is: after the Client authenticatet itselfe with the users credentials and has gotten the Token, how do I best verify the Token?
Basicly: What is the Play- counterpart to the "#PreAuthorize" annotation of Spring?
Any tip or link to a helpful website is appreciated.
So I think I solved my Problem. On the off chance that someone stumbles on the same question I'm gonna post the solution here:
As written in the Play-Docs (https://www.playframework.com/documentation/2.6.x/JavaOAuth) with OAuth2 and especially with the Password flow it is pretty simple.
First you need an Authorization-Service, here the implementation is straight forward. Just implement three methods:
POST /oauth/token for passing the user-credentials and recieving an access- and refresh-token
POST /oauth/refresh for when the access-token is no longer valid. Here the refresh-token is passed and a new acces token returned
POST /oauth/check_token for authorization. Here the access Token is passed and in my case I return the role, the user has. Alternativly it would also be possible and maybe eaven better to do the authorization process in the authorization-service. For this you would need to change the "check_token" method and pass the required role.
I just generated uuids as tokens and stored them in a database. I guess one could also use for example jwts and put the needed information (for example the expiration date) in the token.
Then my main question was about the annotations. I found this
https://github.com/bekce/oauthly
and just lookt at their implementation.
You basicly just need a class and a interface:
The Interface:
#With(AuthorizationServerAuthAction.class)
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface AuthorizationServerSecure {
boolean requireAdmin() default false;
boolean requirePersonnel() default false;
boolean requireGuest() default false;
}
The Class:
private WSClient ws;
private final String url = "http://localhost:9001/oauth/check_token";
#Inject
public AuthorizationServerAuthAction(WSClient ws) {
this.ws = ws;
}
private CompletionStage<JsonNode> callApi(String accessToken) {
CompletionStage<WSResponse> eventualResponse = ws.url(url).setContentType("application/x-www-form-urlencoded").setRequestTimeout(Duration.ofSeconds(10))
.addHeader("Authorization" , accessToken).post("none");
return eventualResponse.thenApply(WSResponse::asJson);
}
#Override
public CompletionStage<Result> call(Http.Context ctx) {
Optional<String> accessTokenOptional = ctx.request().header("Authorization");
JsonNode result = null;
if(!accessTokenOptional.isPresent()){
return CompletableFuture.completedFuture(unauthorized(Json.newObject()
.put("message", "No token found in header!")
));
}
CompletionStage<JsonNode> apiResponse = callApi(accessTokenOptional.get());
try {
result = apiResponse.toCompletableFuture().get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
if(result == null) {
return CompletableFuture.completedFuture(unauthorized(Json.newObject()
.put("message", "an error occurred")
));
}
String role = result.get("role").asText();
if(configuration.requireAdmin()){
if(role.equals("admin")) {
return delegate.call(ctx);
} else {
return CompletableFuture.completedFuture(unauthorized(Json.newObject()
.put("message", "The user is not authorized to perform this action!")
));
}
} else if(configuration.requirePersonnel()) {
if(role.equals("personnel") || role.equals("admin")) {
return delegate.call(ctx);
} else {
return CompletableFuture.completedFuture(unauthorized(Json.newObject()
.put("message", "The user is not authorized to perform this action!")
));
}
} else if(configuration.requireGuest()) {
if(role.equals("guest") || role.equals("personnel") || role.equals("admin")) {
return delegate.call(ctx);
} else {
return CompletableFuture.completedFuture(unauthorized(Json.newObject()
.put("message", "The user is not authorized to perform this action!")
));
}
}
return CompletableFuture.completedFuture(unauthorized(Json.newObject()
.put("message", "an error occurred")
));
}
}
Related
Here I have a method where fetchReport is an external call to a vendor API. I want to copy that data to Azure Blob Storage but not if There was an error. If there was an error then I want to return the CustomResponse with the error details. writeToBlob() also returns a CustomResponse. I want to be able to preserve the error message from the external API to give to the consumer.
Is there any way I can use some conditional logic like
if response.contains("Failed") -> then return response with error details
else -> write to blob
public Flux<CustomResponse> getAndSaveReport(Mono<JsonNode> fetchReport, String reportFilePrefix) {
Mono<JsonNode> reportMono = fetchReport
.doOnSuccess(result -> {
log.info(Logger.EVENT_UNSPECIFIED, "Successfully retrieved report");
})
.switchIfEmpty(Mono.just(objectMapper.convertValue(new CustomResponse("No content"), JsonNode.class)))
.onErrorResume(BusinessException.class, err -> {
log.error(Logger.EVENT_FAILURE, "Failed to retrieve report");
JsonNode errJson = null;
CustomResponse apiResponse = new CustomResponse();
apiResponse.setStatus("Failed");
apiResponse.setMessage("Error message: " + err.getMessage());
apiResponse.setType(reportFilePrefix);
errJson = objectMapper.convertValue(apiResponse, JsonNode.class);
return Mono.just(errJson);
});
return writeToBlob(reportMono.flux(), reportFilePrefix).flux();
}
Any help would be appreciated!
Not sure what fetchReport returns but the code could be simplified by applying flatMap. Also, not sure why are you using flux() everywhere when only one signal is passed - you can use Mono instead.
public Mono<CustomResponse> getAndSaveReport(Mono<JsonNode> fetchReport, String reportFilePrefix) {
return fetchReport
.flatMap(result -> {
if (result.response.contains("Failed")) {
// error handling
return Mono.just(errorResponse);
} else {
return writeToBlob(result.report, reportFilePrefix)
}
});
}
i am creating a android application which can create and login felicity for the xmpp server. but my question is the server setup such a way on creation of new user it will return a int value i need to display that in android application. could you please help me how to get that int value. how to capture the server response..?? thank you in advance
I have already tried some of the links but not very useful
xmpp server is giving some int response on user creation how can i get that display in android application
First create a API to register a user.
Upon registration the server should send you a JSON response code as :
{
"id" : "1"
"error": false,
}
To read the response in your application you can use AsyncTask, Volley, Ion.
Here is example to use ION.
Following code will send a registration request to your server and will receive JSON response as above.
if (AppUtilities.isInternetConnected(this)) {
final Builders.Any.B builder = Ion.with(this).load("your.registration.url.com");
if (BuildConfig.DEBUG) {
builder.setLogging(TAG, Log.VERBOSE);
}
builder.setHeader(ApiConstants.HEADER_KEY_ACCEPT, ApiConstants.HEADER_ACCEPT_VALUE)
.setBodyParameter("username", username)
.setBodyParameter("password", password)
.as(RegistrationResponse.class)
.setCallback(mRegistrationCallback);
} else {
// Do something..
}
Create a RegistrationResponse.class
public class RegistrationResponse {
#SerializedName("id")
private int id;
#SerializedName("error")
private boolean error;
public boolean isError() {
return error;
}
public int getID() {
return id;
}
}
After creating of the model class of Registration response that you are going to recieve create a Callback like this.
private FutureCallback<RegistrationResponse> mRegistrationCallback= new FutureCallback<RegistrationResponse>() {
#Override
public void onCompleted(Exception exception, RegistrationResponse serverResponse) {
if (null == exception) {
int idFromServer = serverResponse.getID();
//idFromServer contains the integer value you were looking for.
//To get the boolean value you can do this.
boolean responseError = serverResponse.isError();
}
}
};
Hope this will help.
I use #SubscribeMapping in a Spring Boot application which heavily relies on WebSockets for data exchange. The application is secured with Spring Security.
Client-side I use Stomp over WebSocket:
this.socket = new WebSocket(this.socketUrl);
this.stompClient = Stomp.over(this.socket);
this.stompClient.debug = null;
this.stompClient.connect({},
function(frame) {
this.$.chartLoader.generateRequest();
}.bind(this),
function(e) {
window.setTimeout(function() {
this.connectWs();
}.bind(this), 2500);
}.bind(this)
);
this.stompClient.subscribe('/topic/chart/' + chart.id,
function(message, headers) {
this.setChartData(chart, JSON.parse(message.body));
}.bind(this), {
"id" : "" + chart.id
}
);
Server-side, how can I get the currently logged user in the annotated methods ?
#SubscribeMapping("/chart/{id}")
public void subscribeMapping(#DestinationVariable("id") final short id) {
// Here I would need the current user...
messagingTemplate.convertAndSend("/topic/chart/" + id, chartService.getChartData(account, sensor, new Date(), new Date()));
}
I have tried SecurityContextHolder.getContext().getAuthentication(), but it returns null.
You can try to add a Principal object as a method parameter. This interface is extended by Authentication which has several implementations available (Spring will inject the good one according to your configuration).
public void subscribeMapping(#DestinationVariable("id") final short id, Principal principal) {
principal.getName();
}
This article may also help you.
I have an issue with using mobile app that I created in Ionic and back-end APIs that I coded in play framework, my issue simply is I need way to handle security matter for calling APIs that need to be secured, mean user must be logged in to do actions, as example guest can view group but can join only if logged in.
My issue that I believe that cookies is not supported for Mobile, i have code checking session that stored in cookies, its working for website, but it will not work for mobile, right?
Currently I'm trying to send a token that generated in back-end and returned with login response and stored in localStorage in Ionic, but my issue is that I can't sent token to be validated with request.
Front End:
I have the following http interceptor :
angular
.module('app.core')
.factory('sessionInjector', sessionInjector);
/** #ngInject */
function sessionInjector($q, sessionService, $rootScope) {
return {
request: function (config) {
$rootScope.$broadcast('loading:show');
if (!sessionService.isAnonymous())
config.headers['x-session-token'] = sessionService.getToken();
}
return config;
}
}
Back-End:
Controller:
#Security.Authenticated(Secure.class)
public Result joinOrganization() {
// Do some business
}
Secure.java :
#Override
public String getUsername(Http.Context ctx) {
// Is this correct way? I get null here
String str = ctx.request().getHeader("x-session-token");
String userId = ctx.session().get("usedId");
if (userId == null) {
return null;
}
User user = Play.application().injector().instanceOf(UserService.class).findUserById(Integer.parseInt(userId));
if (user != null && user.isActive) {
return user.id;
} else {
return null;
}
}
#Override
public Result onUnauthorized(Http.Context ctx) {
return unauthorized(results);
}
Note: Tokens stored in database:
Entity:
#Entity
#Table(name = "AUTHTOKEN")
public class AuthToken extends BaseModel {
#OneToOne(targetEntity = User.class, cascade = CascadeType.REFRESH, optional = false)
public User user;
#Column(nullable = false)
public String token;
#Column
public long expiration;
public AuthToken() {
}
}
For cookies working, but need to remove cookies and use tokens, or use them together cookies for website, tokens for mobile .
Mobile is no different to otherwise in regards of cookies. There are restrictions if AJAX, or cross-domain requests are used, and specially with Apple stuff, but they apply to non-mobile too. If your cookies work on a PC/Mac, they should do on a mobile device just as well. It's more of a form factor than anything else...
I found solution and it was complicated because there are many issues starting from that ngResource does not apply request interceptor its an issue opened from long time.
Second issue was how to send the token with ngResource, its simply with adding headers, the another issue here is getting dynamically the token, this "dynamically" means because the localStorage in memory getting lost when refresh so you need to get back it, this can be done with service, and function call for getting the token, something like this :
$resource('/user/:userId/card/:cardId', {userId:123, cardId:'#id'}, {
charge: {method:'POST', params:{charge:true}, headers = {
'x-session-token': function () {
return sessionService.getToken()
}}
});
Inside sessionService :
// this to recreate the cache in memory once the user refresh, it will keep the data if exisit but will point to it again in memory
if (CacheFactory.get('profileCache') == undefined) {
//if there is no cache already then create new one and assign it to profileCache
CacheFactory.createCache('profileCache');
}
function getCurrentSession() {
var profileCache = CacheFactory.get('profileCache');
if (profileCache !== undefined && profileCache.get('Token') !== undefined) {
return profileCache.get('Token');
}
return null;
}
function getToken() {
var currentSession = getCurrentSession();
if (currentSession != null && currentSession != '') {
return currentSession.token;
}
return null;
}
And then this method will work inside Secure.java
protected User getUser(Http.Context ctx) {
String token = ctx.request().getHeader("x-session-token");
if (token != null) {
return securityService.validateToken(token);
}
return null;
}
I'm trying to delete objects from the datastore (using cloud endpoints)
I know the connection is valid because I'm pulling/inserting objects with no problem
However when I try to delete using various approaches I get the same exception
java.lang.illegalArgumentException:DELETE with non-zero content length is not supported
approach 1(using the raw datastore service and the key I stored when inserting the item):
#ApiMethod(name = "removeRPurchase")
public RPurchase removeRPurchase(RPurchase purchase) {
NamespaceManager.set(purchase.getAccount());
DatastoreService d=DatastoreServiceFactory.getDatastoreService();
Key k=KeyFactory.stringToKey(purchase.getKeyrep());
try {
d.delete(k);
} catch (Exception e) {
e.printStackTrace();
purchase=null;
}
return purchase;
}
Approach 2
#ApiMethod(name = "removeRPurchase")
public RPurchase removeRPurchase(RPurchase purchase) {
NamespaceManager.set(purchase.getAccount());
Key k=KeyFactory.stringToKey(purchase.getKeyrep());
EntityManager mgr = getEntityManager();
RPurchase removed=null;
try {
RPurchase rpurchase = mgr.find(RPurchase.class, k);
mgr.remove(rpurchase);
removed=rpurchase;
} finally {
mgr.close();
}
return removed;
}
Ive also tried various variations with the entity manager and the Id, but all with the same exception
The object that i've passed in does contain the namespace in the account, and it does contain the 'KeytoString' of the key associated with the object
the endpoint is called as it should in an AsyncTask endpoint.removeRPurchase(p).execute();
Any help suggestions are appreciated
Make your API method a POST method like this:
#ApiMethod(name = "removeRPurchase" path = "remove_r_purchase", httpMethod = ApiMethod.HttpMethod.POST)
public RPurchase removeRPurchase(RPurchase purchase) {
NamespaceManager.set(purchase.getAccount());
DatastoreService d=DatastoreServiceFactory.getDatastoreService();
Key k=KeyFactory.stringToKey(purchase.getKeyrep());
try {
d.delete(k);
} catch (Exception e) {
e.printStackTrace();
purchase=null;
}
return purchase;
}
I had the same problem because I was using httpMethod = ApiMethod.HttpMethod.DELETE. The error it gives is correct. Simply change it to a POST and do whatever you want inside that API method like delete entities, return entities, etc.
How about trying out the following :
#ApiMethod(
name = "removeRPurchase",
httpMethod = HttpMethod.DELETE
)
public void removeRPurchase(#Named("id") String id) {
//Now take the id and plugin in your datastore code to retrieve / delete
}