Security Exercise
The Process
Register
- The user sends a register (POST) request to the server with a username and password.
- The server checks if the username is taken.
- If the username is taken, the server returns an error.
- If the username is not taken, the server creates a user and returns a JWT token.
Login
- The user sends a login (POST) request to the server with a username and password.
- The server checks if the username and password are valid.
- If the username and password are valid, the server returns a JWT token.
- If the username and password are invalid, the server returns an error.
Authenticate
- The user sends a request to a protected endpoint with the JWT token in the header (‘Authorization’: ‘Bearer ‘ + token).
- A
before
filter calls theauthenticate
method in the SecurityController and check if the token is valid - If it is valid the user is added to the context as an attribute and the request is passed on to the endpoint.
- If the JWT token is valid, the server checks if the user has the required roles to access the endpoint.
- If the JWT token is invalid or missing, the server returns an error.
- If the user doesn’t have the required roles, the server returns an error.
Part 3 Protect Endpoints
- Create an endpoint that can only be accessed by authenticated users with a role of ADMIN.
- The endpoint should return an error if the JWT token is invalid.
- The endpoint should return an error if the JWT token is valid but the user doesn’t have the ADMIN role.
- The endpoint should return the protected resource if the JWT token is valid and the user is authorized.
- Add more endpoints so that there are endpoints for only USER and only ADMIN roles and endpoints that are open to all users.
Part 4 Testing
- Use Rest Assured, JUnit 5, and Hamcrest matchers to test all the finished endpoints.
- Test protected Endpoints by storing a valid JWT token in a variable and use it in the test. Below security exercise builds on the previous weeks Rest project. Either use the Hotel project from last week or create a new Rest project to your liking. Then apply Register, Login, Password hashing, and JWT security to the project as specified below.
Assistance
- video help for below code examples
- hashing passwords video
- Code snippets Use an existing Rest project from last week or create a new one. Now add security and Rest Assured tests to the project.
Part 1 Hashing Passwords
- Create a
User
Entity and aRole
Entity withManyToMany
relationship between them - Let the
User
implement an interface with like this one:
public interface ISecurityUser {
Set<String> getRolesAsStrings();
boolean verifyPassword(String pw);
void addRole(Role role);
void removeRole(String role);
}
- Create a UserDAO that implements the following interface:
public interface ISecurityDAO {
User getVerifiedUser(String username, String password) throws ValidationException; // used for login
User createUser(String username, String password); // used for register
Role createRole(String role);
User addUserRole(String username, String role);
}
- Use bcrypt to hash the password before storing it in the database.
- bcrypt link: https://www.mindrot.org/projects/jBCrypt/
-
add pom.xml property:
<jbcrypt.version>0.4</jbcrypt.version>
<dependency> <groupId>org.mindrot</groupId> <artifactId>jbcrypt</artifactId> <version>${jbcrypt.version}</version> </dependency>
-
Hint: Hash the password in the User constructor:
BCrypt.hashpw(userPass, BCrypt.gensalt());
- Hint: Verify the password in the User´s verifyPassword method:
BCrypt.checkpw(password, this.password);
Part 2 Login and Register Endpoints
- Create a SecurityRoutes class with the following routes:
- POST /register // returns a JWT token
- POST /login // returns a JWT token
Create a SecurityController
- Create a SecurityController that implements the following interface methods:
Handler login(); // to get a token after checking username and password
Handler register(); // to make a new User and get a token
Handler authenticate(); // to verify that a token was sent with the request and that it is a valid, non-expired token
boolean authorize(UserDTO userDTO, Set<String> allowedRoles); // to verify user roles
String createToken(UserDTO user) throws Exception;
UserDTO verifyToken(String token) throws Exception;
-
You will need these dependency in pom.xml file with various JWT utility classes:
<dependency> <groupId>com.github.Hartmannsolution</groupId> <artifactId>TokenSecurity</artifactId> <version>${token.security.version}</version> </dependency>
-
And this property:
<token.security.version>1.0.4</token.security.version>
-
You also need to add this to the pom.xml file just after the properties:
<repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories>
The jitpack.io repository is where the TokenSecurity library is located. It is not in the Maven Central Repository.
Update the ApplicationConfig
- Add a SecurityFilter, that looks to see if the user has the correct roles to access the endpoint:
app.beforeMatched(ctx -> { // Before matched is different from before, in that it is not called for 404 etc.
if (ctx.routeRoles().isEmpty()) // no roles were added to the route endpoint so OK
return;
// 1. Get permitted roles
Set<String> allowedRoles = ctx.routeRoles().stream().map(role -> role.toString().toUpperCase()).collect(Collectors.toSet());
if (allowedRoles.contains("ANYONE")) {
return;
}
// 2. Get user roles
UserDTO user = ctx.attribute("user"); // the User was put in the context by the SecurityController.authenticate method (in a before filter on the route)
// 3. Compare
if(user == null)
ctx.status(HttpStatus.FORBIDDEN)
.json(jsonMapper.createObjectNode()
.put("msg", "Not authorized. No username was added from the token"));
if(!SecurityController.getInstance().authorize(user, allowedRoles)) {
// throw new UnAuthorizedResponse(); // version 6 migration guide
throw new ApiException(HttpStatus.FORBIDDEN.getCode(), "Unauthorized with roles: " + user.getRoles() + ". Needed roles are: " + allowedRoles);
}
});