calling rest api with requestbody and authenticationprincipal - java

I have a rest api which has role based access to its http POST method. It uses spring-security to access api only for authorized users.
I have 2 questions,
how external client can pass request body and user object (#AuthenticationPrincipal) to make this api call
How can I write junit to test below piece of code,
#PreAuthorize("hasAuthority('ADMIN')")
#PostMapping("/api/access/submit")
public ResponseEntity<OrderAdminResponse> create(#RequestBody OrderAdminRequest orderAdminSubmitRequest,#AuthenticationPrincipal UserObject user)
{
return ResponseEntity.accepted().body(orderService.submit(orderAdminSubmitRequest));
}
My User Object is below,
<code>
public class UserObject {
private final String name;
private final String id;
private final String email;
private UserObject(String name, int id, String email){
this.name = name; this.id = id; this.email = email
}
public Collection<String> getRoles() {
return
(Collection)this.getAuthorities().stream()
.map(GrantedAuthority::getAuthority).collect(Collectors.toList());
}
public boolean isUserInRole(String role) {
return this.getAuthorities().stream().anyMatch((a) -> {
return a.getAuthority().equals(role);
})
}
}
</code>

The controller will automatically populate the Authentication Principal, when you make a request with an authenticated user.
For example, if you are using HTTP basic authentication to secure your endpoint, then the principal will be populated from the Authorization header. Your request body remains the same regardless of whether you are extracting the principal or not.
An easy way to test your controller is to use the support provided by Spring Security.
If you are using MockMvc, one option is to use a post processor to call your endpoint with different types of users.
this.mvc.perform(post("/api/access/submit")
.content("...")
.with(user("user").roles("USER")))
.andExpect(status().isUnauthorized());
this.mvc.perform(post("/api/access/submit")
.content("...")
.with(user("admin").roles("ADMIN")))
.andExpect(status().isOk());
You can learn more about Spring Security test support in the documentation here.

Related

How can i retrieve the username form the keycloak user?

I'm running a quarkus setup with keycloak. To start of I am trying to implement the tutorial into my code. I have to note here that i didn't copy the realm that is used. The reason for this is that we received a realm from our tutors.
This is the code I am trying to implement. The problem with is that it doesn't return the user.
#Path("/api/users")
public class UserResource {
#Inject
SecurityIdentity identity;
#GET
#Path("/me")
#NoCache
public User me() {
return new User(identity);
}
public static class User {
private final String userName;
User(SecurityIdentity identity) {
this.userName = identity.getPrincipal().getName();
}
public String getUserName() {
return userName;
}
}
}
When I enabled policy enforcer, I wasn't even able to login, all request became 403 or 401.
quarkus.keycloak.policy-enforcer.enable=true
How can we solve this issue?
Another question would be if it is possible to retrieve some sort of userId from keycloak?

How to use spring-cloud-gateway for authentication and authorization?

I am very confused about this architecture. I am not even sure is it possible.
I have more than 10 microservises and a API Gateway. I want to add authentication and authorization to this system. One of this services is authentication-server and it has
an endpoint which is /signin
#PostMapping(value = "/signin")
public UserLoginResponse login(#Valid #RequestBody UserLoginRequest userLoginRequest) {
return authService.login(userLoginRequest);
}
public class UserLoginResponse {
private String accessToken; //accessToken is jwt token and it has ROLE field.
}
public class UserLoginRequest {
private String username;
private String password;
}
Here is the confusing part for me: Right now gateway creates code duplication. When I add to a new endpoint, I need to add almost same controller/service/models to API Gateway.
For example:
Lets say microservice A has /product endpoint, these are (veeery roughly) the classes I should have
// Controller
class ProductController {
#GetMapping("/product/{id}")
public ProductResponse getProduct(#PathVariable String id) {
return productService.getProduct(id)
}
}
// Service
class ProductService {
public getProduct(String id){
return productRepository.get(id);
}
}
// Response DTO
class ProductResponse(){
private String id;
private String name;
}
Our team also has implemented classes in the gateway.
//Controller has authorization with #PreAuthorize annotation.
#RestController
#PreAuthorize("hasAnyRole('USER', 'ADMIN')")
class ProductController {
#GetMapping("/product/{id}")
public ProductResponse getProduct(PathVariable String id) {
return productService.getProduct(id)
}
}
// Service
class ProductService {
private final ClientApi productClientApi;
public ProductService(ClientApi productClientApi) {
this.productClientApi = productClientApi;
}
public getProduct(String id){
return productClientApi.getProduct(id);
}
}
//This is feign client. It makes http requests to product-api
#FeignClient(
"product-api",
url = "\${product-api.base-url}",
configuration = [FeignClientConfiguration::class]
)
interface ClientApi(){
#GetMapping( value = {"product/{id}"}, consumes = {"application/json"} )
ProductResponse getProduct(#PathVariable String id);
}
// Response DTO
class ProductResponse(){
private String id;
private String name;
}
When a request comes to /product its jwt token controlled here and if it has proper permission, it goes to the service layer,
Service layer makes request to product-api(which is microservice A)
returns the response
Question: There should be easier way. Every new endpoint in the services costs us code duplication in Gateway. I think I want just routing. Whatever comes to gateway directly should be routed to services and it should still has the authentication/authorization responsibility. I know that I can do that as below with spring-cloud-gateway but I couldn't figure out how can i do that with authentication and authorization. Can anyone explain me that am i thinking wrong ?
spring:
application:
name: "API-GATEWAY"
cloud:
gateway:
routes:
- id: product-service
uri: 'http://product-url:8083'
predicates:
- Path=/product/**

Spring Boot REST: #DeleteMapping that consuming form_urlencoded not work as expect

I'm using Spring boot 1.4.0, Consider below code in a #RestController, what I expect is, the server side will receive a http body with form_urlencoded content type, but unfortunately it demands me a query parameter type with email and token. What's the problem here and how to fix?
#DeleteMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
#ResponseStatus(HttpStatus.NO_CONTENT)
public void removeAdmin(#RequestParam(value = "email") String email, #RequestParam(value = "token") String token) {
//...
}
#DeleteMapping is only a convenience extension the provides #RequestMapping(method=DELETE) It will not handle request paramters. You will still have to map those in the controllers method signature if you need the data to perform the work.
Since you want a body, You could create an object and mark it as #RequestBody:
public class DeleteBody {
public String email;
public String token;
}
public void removeAdmin(#RequestBody DeleteBody deleteBody) {
...
}

How to get JSON in Java REST / AngularJS frontend

Can someone please help me how to get a JSON String in a Webservice. I's sending JSON to my /api/register that looks like:
{"name":"MyName","surname":"MySurename","email":"mail#asd.de","street":"MyStreet","number":"3","zip":"12345","city":"myCity","pass":"myPassword"}
Here is my register.java file:
#Path("/register")
#Stateless
public class RegisterWS {
#EJB
UserBS userBS;
#POST
#Consumes(MediaType.APPLICATION_JSON)
public void createUser(){
// code to get data from json
userBS.createUser(name, surename, email, adress, number, zip, city, password);
}
}
My AngularJS Controller and Service. The Data comes from a form, that is parsed to a JSON object.
app.service('RegisterService', function ($http) {
return {
registerUser : function(user) {
$http.post('http://localhost:8080/myApp/api/register')
.success(function (user) {
return user;
})
.error(function (data) {
// failed
});
}
}
});
app.controller('RegisterCtrl', function($scope, RegisterService) {
$scope.register = function(){
RegisterService.registerUser(angular.toJson($scope.user));
}
});
You should have a POJO, which maps to the received JSON object, for example a User class. In this case this would be a very simple Java Bean, with mostly String properties for each field in the JSON.
#XmlRootElement
public class User {
String name;
String surname;
String email;
String street;
Integer number;
String zip;
String city;
String pass;
}
Of course you would use private fields, with getters and setters, but I did not want to add clutter. By the way the #XmlRootElement is a JAXB annotation, and JAX-RS uses JAXB internally.
After you have this, you just need to change your method like this
#POST
#Consumes(MediaType.APPLICATION_JSON)
public void createUser(User user) {
...
}
You should not need to change anything on the AngularJS side, as the default for the $http.post method is JSON communication.
For your Java code, you have to add a User POJO, I dont know if you will use some persistence API or not, so the user POJO must implement serializable to output user object as JSON.
Here's a an example of REST app with EJB ... : http://tomee.apache.org/examples-trunk/rest-on-ejb/README.html
For your client app, you need to specify the content type : "Content-Type" = "application/json"
See this questions: change Content-type to "application/json" POST method, RESTful API

Proper place for setting form fields in ResourceSupport which don't exist in Entity

I have a model for logging in user in my REST API, corresponds to User table (email and password as table columns)
#Entity
public class User {
#Id
#GeneratedValues
private Long id;
private String email;
private String password;
+GET , +SET
}
Then there is #Controller which is making call to above User Entity using JPAService
#Controller
#RequestMapping("/rest/auths")
public class AuthController {
#Autowired
private UserService authService;
#RequestMapping(value = "/login", method = RequestMethod.POST)
public #ResponseBody ResponseEntity<AuthLoginFormResource> login(#RequestBody AuthLoginFormResource sentAuth) {
User user = authService.login(sentAuth.toUser());
AuthLoginFormResource res = new AuthLoginFormResourceAsm().toResource(user);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(URI.create(res.getLink("self").getHref()));
return new ResponseEntity<AuthLoginFormResource>(res, HttpStatus.OK);
}
}
AuthLoginFormResource : -
public class AuthLoginFormResource extends ResourceSupport {
private String email;
private String password;
private boolean success;
public User toUser() {
User user = new User();
user.setEmail(email);
user.setPassword(password);
//user.setSuccess(false);
return user;
}
+GET, +SET
}
AuthLoginFormResourceAsm : -
public class AuthLoginFormResourceAsm extends ResourceAssemblerSupport<User, AuthLoginFormResource> {
public AuthLoginFormResourceAsm() {
super(User.class, AuthLoginFormResource.class);
}
#Override
public AuthLoginFormResource toResource(User user) {
AuthLoginFormResource res = new AuthLoginFormResource();
res.setEmail(user.getEmail());
res.setPassword(user.getPassword());
//res.setSuccess(user.isSuccess()); // Success is not existing in USER
res.add(linkTo(AuthController.class).withSelfRel());
return res;
}
}
There are 2 issues -
I need to send a success flag as boolean in response for which i have added a boolean success to AuthLoginFormResource. But, AuthLoginFormResource gets set
only from AuthLoginFormResourceAsm.toResource method , which in turn does
it from entity User. As User entity models database where there is
no success column, I am not able to set success at this place.
So, should I add dummy success field to User Entity and set that from service
method , though there is no such field in database or create a new Entity representing Login Form here and return that ?
Same problem with another field that is a token for authentication
which does not exist in database but is part of response.
What is correct place for setting such fields in ResourceSupport object - inside database Entity and return from Service / creating another Form Model entity on top of Domain Model and return from service.
This is basic question I am facing in many places where data model and forms don't match one to one.
I strongly recommend the following;
Modify UserService.login method to return true or false based on successfull authentication instead of retrieved user object from database.
Return only true or false with status OK and FAIL, as part of the response not the entire AuthLoginFormResource. This is a bad practice because you are sending out the username and password as part of the request and response, back and forth in a roundtrip. If someone is evesdropping they can easily figure out what username passwords work and what don't.
Or
Consider using Basic Authorization, Digest Authorization or OAuth if you fancy than this custom Authentication Implementation. Using Spring Security you can achieve any of the aforementioned really easily.

Categories