I implemented a basic JPA authentication following this tutorial.
I wanted to create an endpoint /mydetails to display user information (profile info).
What I've tried:
#GetMapping("/mydetails")
public Optional<User> getUser(HttpServletRequest request) {
Optional<User> foundUser = Optional.ofNullable(userRepo.getUserByUsername(request.getUserPrincipal().getName()));
return foundUser;
}
Outcome:
{
"id":1,
"username":"name.surname#companyname.com",
"password":"$2a$10$7YzUO6scaC06LV6IgOsSXetFm4/U0WM.UZykhRfQcJBzKacyZFMK",
"first_name":"John",
"last_name":"Walker",
"organization_name":"ABC",
"role":"Admin",
"credibility_rating":"100"
}
The problem is that this literally takes out all the information and I want everything except the password.
How could I stop the response from sending the password information?
I am totally new to Spring and have not used Java for many years.
Any insight would be highly appreciated.
It seems you are talking about a REST controller that returns JSON. With the default configuration, Spring Boot uses Jackson to transform objects to JSON. The most simple fix would be to tell Jackson to ignore the password field in your User class:
public class User {
...
#JsonIgnore
private String password;
...
}
See this article for more information.
Related
I am pretty new in Spring Security and I am working on a Spring Boot project that uses Basic Authentication in order to protect some APIs. I am starting from an existing tutorial code (a Udemy course) trying to adapt it to my own use cases.
In this project I have this SecurityConfiguration used to configure the basic authentication.
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
private static String REALM = "REAME";
private static final String[] USER_MATCHER = { "/api/utenti/cerca/**"};
private static final String[] ADMIN_MATCHER = { "/api/utenti/inserisci/**", "/api/utenti/elimina/**" };
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().disable()
.authorizeRequests()
.antMatchers(USER_MATCHER).hasAnyRole("USER")
.antMatchers(ADMIN_MATCHER).hasAnyRole("ADMIN")
.anyRequest().authenticated()
.and()
.httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint()).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public AuthEntryPoint getBasicAuthEntryPoint()
{
return new AuthEntryPoint();
}
/* To allow Pre-flight [OPTIONS] request from browser */
#Override
public void configure(WebSecurity web)
{
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
#Bean
public BCryptPasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
};
#Bean
#Override
public UserDetailsService userDetailsService()
{
UserBuilder users = User.builder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users
.username("ReadUser")
.password(new BCryptPasswordEncoder().encode("BimBumBam_2018"))
.roles("USER").build());
manager.createUser(users
.username("Admin")
.password(new BCryptPasswordEncoder().encode("MagicaBula_2018"))
.roles("USER", "ADMIN").build());
return manager;
}
}
So from what I have understand:
Here it id defined the list of API that can be accessed by a nornmal user and the list of API that can be accessed by and admin user:
private static final String[] USER_MATCHER = { "/api/utenti/cerca/**"};
private static final String[] ADMIN_MATCHER = { "/api/utenti/inserisci/**", "/api/utenti/elimina/**" };
Into the previous configure() method basically it is stating that the API URL matching with the USER_MATCHER are accessible by logged user having role USER while API having URL matching ADMIN_MATCHER are accessible by logged user having role ADMIN. Is this interpretation correct?
Finnally the UserDetailsService bean simply define two users: one belonging to the USER "group" and the other one belonging to both the USER and ADMIN "group".
So, if I well understood, the first one will be aple only to access to the API having enpoint URL /api/utenti/cerca/** while the second one will be able to access also to the APIs having endpoint URLs /api/utenti/inserisci/** and /api/utenti/elimina/**
Is it my reasoning correct?
And now my doubt: into a controller class of this project I defined this method:
#RestController
#RequestMapping("api/users")
#Log
public class UserController {
#Autowired
UserService userService;
//#Autowired
//private BCryptPasswordEncoder passwordEncoder;
//#Autowired
//private ResourceBundleMessageSource errMessage;
#GetMapping(value = "/test", produces = "application/json")
public ResponseEntity<String> getTest() throws NotFoundException {
log.info(String.format("****** getTest() START *******"));
return new ResponseEntity<String>("TEST", HttpStatus.OK);
}
..............................................................................................................
..............................................................................................................
..............................................................................................................
}
As you can see this method handling a GET request toward the localhost:8019/api/users/test endpoint.
This endpoint URL is not in any of the previous two list related the protected endpoint (it is not into the USER_MATCHER list neither into the ADMIN_MATCHER list. So I expected that simply this endpoint was not protected and accessible to everyone. But performing the previous request using PostMan, I obtain this error message:
HTTP Status 401 : Full authentication is required to access this resource
So basically it seems to me that also if this endpoint not belong to any protected endpoint list it is in some way protected anyway (it seems to me that at least the user must be authenticated (infact trying both the previous user I can obtain the expected output, so it should mean that the endpoint is not protected by the user rule but it is protected againts not authenticated access).
Why? Maybe it depende by the previous configure() method settings, in particular this line?
.anyRequest().authenticated()
In case is it possible to disable in some way to implement something like this:
If a called endpoint belong to one of the previous two lists (USER_MATCHER and ADMIN_MATCHER) --> the user must be authenticated and need to have the correct role.
If a called endpoint not belong to one of the previous lists --> everybody can access, also not authenticated user.
This approach make sense or am I loosing something?
I take this occasion to ask you also another information: do you think that it is possible to configure Spring security of this specific project in order to protect some specific endpoints using the basic authentication and some other specific endpoints using the JWT authentication.
Sone further notes to explain why this last question. This project is a microservice that at the moment is used by another microservice (used to generate JWT token) in order to obtain user information. (the other microservice call an API of this project in order to receive user information so it can generate a JWT token that will be used in my application. The comunication between these 2 microservice must use basic authentication).
Since this project contains all the entity classes used to map the tables related to the users on my DB, my idea was to use this project also for generic user management, so it could include functionality like: add a brand new user, changes information of an existing user, obtain the list of all the users, search a specific user, and so on.
These new APIs will be protected by JWT token because each API can be called from a specific user type having different privileges on the system.
So I am asking if in a situation like this I can add without problem 2 different types of authentication (basic authentication for the API that retrieve a user so the other microservice can obtain this info) and JWT authentication for all the other APIs. It make sense or is it better to create a brand new project for a new user management microservice?
So, if I well understood, the first one will be aple only to access to the API having enpoint URL /api/utenti/cerca/** while the second one will be able to access also to the APIs having endpoint URLs /api/utenti/inserisci/** and /api/utenti/elimina/**
Yes.
Why? Maybe it depende by the previous configure() method settings, in particular this line?
Yes, when using .anyRequest().authenticated(), any requests that have not been matched will have to be authenticated.
If a called endpoint not belong to one of the previous lists --> everybody can access, also not authenticated user.
You can achieve this by doing anyRequest().permitAll(). But this is not so secure because you are allowing access to every other endpoints, instead you should stay with anyRequest().authenticated() and allow access to specific endpoints manually, like so:
http
.authorizeRequests()
.antMatchers(USER_MATCHER).hasAnyRole("USER")
.antMatchers(ADMIN_MATCHER).hasAnyRole("ADMIN")
.antMatchers("/api/users/test").permitAll()
.anyRequest().authenticated()
...
I'm new to REST and I'm making simple REST application with users and articles. I wonder what's the difference between two samples below:
#GetMapping("/user/{id}")
public User getUserById(PathVariable("id") String id) {
.....
return userService.getUserById();
}
and
#GetMapping("/user/{id}")
public ResponseEntity<User> getUserById(PathVariable("id") String id) {
.....
return new ResponseEntity<> ....
}
Which one is better to use?
And what's the main difference between two of them?
ResponseEntity is containing the entire HTTP response that returns as a response which gives the flexibility to add headers, change status code and do similar things to the response.
Another hand sending PJO class directly like returning users in the example is somewhat similar to return ResponseEntity.ok(user) which responded to user details successfully to the user. But the ability to change headers, status codes is not available if you return PJO directly.
It is good to use ResponseEntity over PJO when there is a scenario you need to change the headers or you need to change status according to the result.
eg: show not found when there is no data you can return ResponseEntity.status(404).body(<-body->).
at least in the Response Entity, you can set the http status and you can use ResponseEntity<?> where ? is generic any object its very convenient to use
I have 2 android app: ClientApp and CashierApp. It different app's but bouth app's can login on my server. I need implement login functional for this applications. On server side I have one controller - UserController and 2 methods:
#PostMapping(value = "/user/login")
public UserDto login(#RequestBody UserDto userDto) {
return userService.getUserWithAuth(userDto);
}
#PostMapping(value = "/cashier/login")
public CashierDto loginCashier(#RequestBody CashierDto cashierDto) {
return userService.geCashierWithAuth(cashierDto);
}
I do not understand how to implement this correctly:
use different URLs and DTO models for each application(like now)
Create enum UserType.USER, UserType.CASHIER add it to one UserDto and use one endpoint(URL). Then pass UserDto to userService and split the login type by enam
some other way
in the first way I do not like that when a new application appears that needs to be authorized, you need to create a new URL, new method and etc.
in the second version I do not understand how I correctly login in the service(switch case???). And the fact that one model will contain an extra field
I am trying to get spring-boot-starter-security to work along with spring-boot-starter-web and spring-boot-starter-tomcat. I tried following the guide from spring-boot-sample-secure and spring-boot-sample-web-secure however I did not get it to work.
I am trying to build a REST application without any ui interactions. Hence I found both samples are not fully suitable for my purpose. Currently my solution is by using AOP.
ControllerMonitor.java:
#Before("execution(* my.zin.rashidi.openshift.tomcat.controller.*.*(..)) && args(authorization, ..)")
public void authenticate(String authorization) {
if (!isEmpty(authorization)) {
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("user", "N/A",
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"))
);
}
}
UserController.java:
#RequestMapping(method = GET)
public ResponseEntity<User> get(#RequestHeader String authorization, #RequestBody User user) {
HttpStatus status = OK;
User returnObject = null;
try {
returnObject = service.get(user);
} catch (AuthenticationCredentialsNotFoundException e) {
status = UNAUTHORIZED;
}
return new ResponseEntity<User>(returnObject, status);
}
The solutions is working for me. However I would like to know if this is a good solution. I'm curious if there is a better solution.
Thanks in advanced for your helps!
I wouldn't want to add an optional parameter to every controller method, and because it's optional the risk of forgetting is quite high. You asked for an opinion, and I think a better solution is to use a more normal Spring Security approach with a filter (standard basic auth if that's good enough, or maybe a custom filter like a pre-auth, for instance, if it doesn't meet your needs). You get http basic security out of the box in a Spring Boot app, so you don't really need to do anything at all to get started.
I'm still new to Spring in general and I'm trying to use Spring boot. I have a (hopefully) quick question. I'm trying to build a ReSTful service which will return JSON. I have followed the Building a RESTful Web Service Guide and can successfully return JSON. I've integrated JPA into my web service so that my data is backed by a database.
Now, I need to make a route in which, users can create an object and I would like the object to be validated. I've followed the Validation Form Input Guide but I'm not really trying to create a service that serves up web content. What I want is, whenever a validation error occurs, to return my own custom JSON. Thus far, I haven't been able to find any resources for making this happen though I've tried following Petri's Sweet Rest API Guide which I've found helpful on multiple occasions but doesn't seem to quite work in this scenario. I'm using hibernate-validator:5.0.1.Final and hibernate for the following.
#Entity
#Table(name = "PEOPLE")
public class Person{
#Id
#Column(unique = true)
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Min(18)
private long age;
private String name;
//Necessary for JPA
protected Person() {}
public Person(long age, String name) {
this.age = age;
this.name = name;
}
// Getters Omitted
}
Then my PersonController:
#Controller
public class PersonController {
#RequestMapping(value="person/", method=RequestMethod.POST)
#ResponseBody
public ResponseEntity<Person> create(#Valid #RequestBody Person person) {
// Create in DB and return
}
}
This works in the most strict way, in that, if you send garbage JSON to this route, it will return a 400 which is pretty nice. But the body of the response is an HTML page which is not as nice. So, my question is, is there some way to catch a validation error? I've tried adding the following to my Controller but with no success:
#ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity handleError(MethodArgumentNotValidException ex) {
//generate my own error message
return new ResponseEntity(customErrorClass, HttpStatus.BAD_GATEWAY);
}
I'm aware Bad Gateway is not a valid return code, but I used it just to prove that the Exception handler is never called. When I POST to my rest service, I still see 400 Bad Request + HTML. I would assume that there is some sensible default that I can override but I can't seem to figure out where it is. I've tried googling and searching stackoverflow to no luck.
Thanks in advance.
UPDATE
If I modify the Controller to include a BindingResult in the method signature:
#Controller
public class PersonController {
#RequestMapping(value="person/", method=RequestMethod.POST)
#ResponseBody
public ResponseEntity<Person> create(#Valid #RequestBody Person person, BindingResult bindingResult) {
if(bindingResult.hasErrors()){
//handle errors and return
} else {
// Create in DB and return
}
}
}
I can get it to work. (Note I also had to add the jasper-el jar to my dependencies) I had tried this before and didn't get it to work but the reason is not intuitive. I was posting with the following JSON: { "id" : "foo", "age": 22, "name" : "James Bond" } According to the JSON Spec, this is valid JSON. Obviously, my Person Model cannot cast the String "foo" to a long. I won't go into whether or not the error should be a 400 or not, my new question is this: How can I catch this 400?
To handle malformed or non-convertible JSON you can catch the HttpMessageNotReadableException class
#ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity handleBadInput(HttpMessageNotReadableException ex) {
Throwable cause = ex.getCause();
The problem is that you set the field id in your model as a long, with the string foo. This is the reason for the 400 http error. Let's say then that this kind of exception is managed correctly yet by the expressive power of the http status. The key point is that you can think to manage the 400 error and the solution of zeroflagL works fine but it was a good solution if you use the #ExceptionHandler like below:
#ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity handleBadInput(HttpMessageNotReadableException ex) {
// manage the exceptio for instance log it
.....
return ResponseEntity.badRequest().body(/** body may be optional if you want send a description of the error*/);
}
It is very important that your exception handler manage the exception in sense that log it as an error for a your analysis but then you should return a http response with 400 http status.
It is important because the http respon is correct, you had post a json that for your service didn't make sense and preserve this information may be vital for discovery problem in your client app for instance, the key point hear is that this http status speaking the your entity didn't make sense for your endpoint.
I hope that it can help you