I have several user roles, let's say: user and admin.
I want to grant access to certain routes only to users with the role of admin. For example for this route:
#GetMapping("/all")
public ResponseEntity getAll(){
List<User> users = this.userRepository.findAll();
return new ResponseEntity<>(users, HttpStatus.OK);
}
How is it possible to create different middlewares in Spring so that I can use them to limit access to a certain user role?
Here is my User model:
#Entity
#Table(name = "users")
public class User implements Serializable {
private static final long serialVersionUID = -7643506746013338699L;
public User() { }
public User(String username, String email, String password, String firstName, String lastName) {
this.username = username;
this.password = password;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
#Id
#NotEmpty
#Size(min = 5, max = 15)
#Column(name = "username")
private String username;
#Email
#NotEmpty
#Column(name = "email")
private String email;
#NotEmpty
#Size(min = 5)
#Column(name = "password")
private String password;
//some more user properties
...
#ManyToMany(fetch=FetchType.EAGER)
#JoinTable(name = "user_roles", joinColumns = #JoinColumn(name = "username"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
// getters and setters
...
}
I have the JWT authentication implemented. Please let me know if I should upload it as well.
Thank you!
You can use hasRole("ADMIN") on that route "/all" in spring security config
https://github.com/spring-projects/spring-security/blob/master/samples/boot/oauth2resourceserver/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java
otherwise you can extract role from Jwt like so and test for the correct role:
https://github.com/spring-projects/spring-security/blob/master/samples/boot/oauth2resourceserver/src/main/java/sample/OAuth2ResourceServerController.java
Related
I have REST api with User model - DTO and Create / update form. My userService checks if user is administrator, then allow to getAllUsers in List. When I want to get all users, I get Bad request 400, but it should return Forbidden. It used to work but when I added some changes to my code I got bad request. I don't know what I'm missing...
My User.java
///Lombok annotations
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Setter(AccessLevel.NONE)
#Column(unique = true)
private Long id;
#Setter(AccessLevel.NONE)
#EqualsAndHashCode.Include
#Column(nullable = false, unique = true)
private UUID uuid = UUID.randomUUID();
#Column(unique = true, nullable = false, length = 254)
private String login;
#Column(nullable = false, length = 254)
private String firstName;
#Column(nullable = false, length = 254)
private String lastName;
#Enumerated(EnumType.STRING)
private RoleType roleType;
#Column(nullable = false, length = 254)
private String password;
#Email
#Column(nullable = false, length = 254)
private String email;
#Positive
private Double cost;
public User(String login, String firstName, String lastName, RoleType roleType, String password,
String email, Double cost) {
this.login = login;
this.firstName = firstName;
this.lastName = lastName;
this.roleType = roleType;
this.password = password;
this.email = email;
this.cost = cost;
}
UserController
#GetMapping("users")
public ResponseEntity<List<UserDto>> getAllUsers(#RequestParam UUID uuid) {
return userService.getListResponseEntity(uuid);
}
UserService
public ResponseEntity<List<UserDto>> getListResponseEntity(UUID adminUuid) {
if (authService.adminAuth(adminUuid)) {
List<User> users = userRepo.findAll();
List<UserDto> userDto = users
.stream()
.map(user -> userMapper.mapToUserDto(user))
.collect(Collectors.toList());
return new ResponseEntity<>(userDto, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
}
}
UserDto
#Builder
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class UserDto {
private String login;
private String firstName;
private String lastName;
private RoleType roleType;
private String email;
private Double cost;
I think you missed uuid parameter in request header.
It will be like this. http://localhost:8080/users?uuid="enter_your_uuid_here"
There could be changes that your JSON request data is not matching with you DTO data fields.
Validate following points in your JSON request
there could be issue of wrong field Name
may be not sending proper data as per the datatype.
I want to assign role to a new user, the role is called ROLE_USER in database and has an id of 3. but I dont know how to pass that value in method save(). At the bottom, I throw the most important classes and save method.
User model class
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false)
private String firstName;
#Column(nullable = false)
private String lastName;
#Column(nullable = false, unique = true)
private String email;
#Column(nullable = false)
private String password;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(
name = "user_role",
joinColumns = {#JoinColumn(name = "USER_ID", referencedColumnName = "ID")},
inverseJoinColumns = {#JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")})
private List<Role> roles;
public User(String firstName, String lastName, String email, String password, List<Role> roles) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.password = password;
this.roles = roles;
}
Getters and setters ...
Role model class
#Entity
#Data
#Table(name = "roles")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(unique = true)
#NotNull
private String name;
#ManyToMany(mappedBy="roles")
private List<Role> users;
}
UserRegistrationDto class
public class UserRegistrationDto {
private String firstName;
private String lastName;
private String email;
private String password;
private List<Role> roles;
}
method for save in UserService class
public User save(UserRegistrationDto registrationDto) {
User user = new User(registrationDto.getFirstName(),
registrationDto.getLastName(),
registrationDto.getEmail(),
passwordEncoder.encode(registrationDto.getPassword()),
ROLE ???);
return userRepository.save(user);
}
I'm starting to use Spring and Hibernate.
I have this Entity :
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false, unique = true)
private String username;
#NotEmpty
private String password;
#NotEmpty
private String firstname;
#NotEmpty
private String lastname;
#NotEmpty
private String email;
#ManyToMany (cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "user_role",
joinColumns = { #JoinColumn(name = "user_id") },
inverseJoinColumns = { #JoinColumn(name = "role_id") })
private Set<Role> roles = new HashSet<>();
}
and this DTO :
public class UserDTO {
private String username;
private String password;
private String firstname;
private String lastname;
private String email;
private List<String> roles;
}
Role is like that (RoleType being an enum) :
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Enumerated(EnumType.STRING)
#Column(unique = true)
private RoleType name;
public Role() {}
public Role(RoleType name) {
this.name = name;
}
}
Now, in my controller, I want to create a new user :
public void createNewUser(UserDTO user){
ModelMapper _modelMapper=new ModelMapper();
User _newUser=_modelMapper.map(user, User.class);
//SECOND PART
Set<Role> _roles=new HashSet<>();
for (String _item : user.getRoles()) {
RoleType _roleType=RoleType.valueOf(_item);
Role _role=_roleRepository.findByName(_roleType);
_roles.add(_role);
}
_newUser.setRoles(_roles);
_userRepository.save(_newUser);
}
My concern is the second part (It does the trick but I'm not sure in term of good practices). I have 2 questions :
-is it the right way to fill the roles, being said that my UserDTO accepts a List<String> ?
-is it possible to map everything with the ModelMapper ? (I have tried but it fills my sql table only with the id, the name is null and it adds a line for each new user).
Thanks.
I'm having a weird issue on my Spring backend, where I am sending a post request with a domain User object from my angular2 frontend that is being recieved by a REST API endpoint and translating it into the Spring User model object. The issue is that the password field from the JSON request seems to not be translating to the password field on the backend. All the other fields (username, name, email, etc.) are coming through fine, but the password is null.
Request payload as seen from network tab in chrome
email : "asdf#asdf.com" firstName : "asdfasdjk" lastName : "asdfs"
login : "adfsjk" password : "fasdfasdfasdsd"
Response seen from network tab
error: "Internal Server Error" exception: "javax.validation.ConstraintViolationException" message: "Validation failed for classes [domain.User] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[ ConstraintViolationImpl{interpolatedMessage='may not be null', propertyPath=password, rootBeanClass=class domain.User, messageTemplate='{javax.validation.constraints.NotNull.message}'}]"
path:"/api/users" status: 500
Spring Rest method:
#RequestMapping(value = "/users",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public ResponseEntity<User> createUser(#RequestBody User user) throws URISyntaxException {
User result = userRepository.save(user);
return ResponseEntity.created(new URI("/api/users/" + result.getId()))
.headers(HeaderUtil.createEntityCreationAlert("user", result.getId().toString()))
.body(result);
}
Spring domain object
#Entity
#Table(name = "user")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class User extends AbstractAuditingEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#Size(max = 100)
#Column(length = 100, unique = true)
private String login;
#JsonIgnore
#NotNull
#Size(min = 5, max = 60)
#Column(length = 60)
private String password;
#Size(max = 50)
#Column(name = "first_name", length = 50)
private String firstName;
#Size(max = 50)
#Column(name = "last_name", length = 50)
private String lastName;
#Email
#Size(max = 100)
#Column(length = 100, unique = true)
private String email;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_tag",
joinColumns = #JoinColumn(name = "users_id", referencedColumnName = "ID"),
inverseJoinColumns = #JoinColumn(name = "tags_id", referencedColumnName = "ID"))
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private List<Tag> tags = new ArrayList<>();
}
Angular2 domain object
export class User {
id: number;
login: string;
password: string;
firstName: string;
lastName: string;
email: string;
}
The problem is with using #JsonIgnore on the password field, this makes the filed ignorable when reading from or writing to JSON message, one solution is to use #JsonProperty with access = Access.WRITE_ONLY as follows
public class User extends AbstractAuditingEntity implements Serializable {
// Other fileds
#NotNull
#JsonProperty(access = Access.WRITE_ONLY)
#Size(min = 5, max = 60)
#Column(length = 60)
private String password;
}
Another solution is to implement a getter and setter for the password field, and annotate the getter with #JsonIgnore and the setter with #JsonProperty, as follows
public class User extends AbstractAuditingEntity implements Serializable {
// Other fileds
#NotNull
#Size(min = 5, max = 60)
#Column(length = 60)
private String password;
#JsonIgnore
public String getPassword() {
return password;
}
#JsonProperty
public void setPassword(String password) {
this.password = password;
}
}
I'm currently taking a look into Hibernate (with Spring, if important) and I tried to make a small web application in which someone can register and log in. There is a secured area which is protected by Spring Security and I already got my UserDetailsService working, so Spring Security is using the Hibernate stuff to do the login.
Now I am working on the registration for new users. I thought about how to do it and I came to having a separate table for the activations which would basically have 3 columns: activationId, username and activation_code. The activation code would be some kind of hash but I guess its not relevant for this question.
My question basically is, how to do the relationship between my users table and the activations. I need to link the username from the activations to the username from the users table.
Do I have to use a One To One Mapping? Or how can I link exactly one column from another table into the activation table?
Here's my User Entity:
#Entity
#Table(name = "users")
public class User {
#Id
#Column(name = "username", unique = true, nullable = false, length = 45)
private String username;
#Column(name = "password", nullable = false, length = 64)
private String password;
#Column(name = "enabled", nullable = false)
private boolean enabled;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<UserRole> userRole = new HashSet<UserRole>(0);
#Column(name = "name")
private String name;
#Column(name = "lastname")
private String lastname;
public User() {
}
public User(String username, String password, boolean enabled) {
this.username = username;
this.password = password;
this.enabled = enabled;
}
public User(String username, String password, boolean enabled, Set<UserRole> userRole) {
this.username = username;
this.password = password;
this.enabled = enabled;
this.userRole = userRole;
}
// Getters and setters for all attributes omitted
}
If you need the UserRole Entity too, just tell me and I will add it but I think it's not needed. Here is also the current version of my UserActivation Entity, but of course it is not finished yet:
import javax.persistence.*;
#Entity
#Table(name = "activations")
public class UserActivation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long activationId;
#Column(length = 64)
private String activationCode;
#Column
private String userName; // here we need some magic relationship
}
You can use the ManyToOne mapping :
#Entity
#Table(name = "activations")
public class UserActivation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long activationId;
#Column(length = 64)
private String activationCode;
#ManyToOne(optional=false)
#JoinColumn(name="USER_NAME")
private User user; // since your User Id is username, the join column will be username
}
if you want the username to be unique, wich means one activation for a single user :
#Entity
#Table(name = "activations",uniqueConstraints=#UniqueConstraint(columnsnames="USER_NAME"))