Configuration
To enable fsecurity
place the @EnableFsecurity
anywhere in your project.
@EnableFsecurity
public class FsecurityConfiguration {}
It's a good idea to place it in a separate configuration class. @EnableFsecurity
is a @Configuration
itself you don't have to define @Configuration
on your configuration class.
Authentication
Next you should add a bean of AuthenticationHandler
which will be used to authenticate users.
public class JwtAuthenticationHandler extends AuthenticationHandler {
@Override
public Identity authenticate(HttpServletRequest request) {
//Your authentication logic here
//You should return an object of type Identity if the user is authenticated
//Otherwise you should return null or throw AuthenticationException
}
}
You could have exposed this class as a @Component
but you can as well expose it as a @Bean
from the configuration class.
@EnableFsecurity
public class FsecurityConfiguration {
@Bean
public AuthenticationHandler authenticationHandler() {
return new JwtAuthenticationHandler();
}
}
And that is it, now if you want to secure an endpoint you use the @Authenticated annotation.
public class TestController {
@Authenticated
@GetMapping("/test")
public ResponseEntity<Identity> test(Identity user){
return new ResponseEntity<>(user, HttpStatus.OK);
}
}
Request will be intercepted, and your authentication handler will be used to try to authenticate the request. If it succeeds the request will be processed. You can inject the Identity
to your controller method by just declaring it as a parameter.
Authorization
If you want to secure an endpoint with a specific role you can use the different options of the @Authenticated
annotation.
hasRole
public class TestController {
@Authenticated(hasRole = "ADMIN")
@GetMapping("/test")
public ResponseEntity<Identity> test(Identity user){
return new ResponseEntity<>(user, HttpStatus.OK);
}
}
It should be self-explanatory, the user should have the role ADMIN
to access this endpoint.
hasAnyRole
public class TestController {
@Authenticated(hasAnyRole = {"ADMIN", "USER"})
@GetMapping("/test")
public ResponseEntity<Identity> test(Identity user){
return new ResponseEntity<>(user, HttpStatus.OK);
}
}
Again, should be self-explanatory, the user should have any of the roles ADMIN
or USER
to access this endpoint.
hasAllRoles
public class TestController {
@Authenticated(hasAllRoles = {"ADMIN", "USER"})
@GetMapping("/test")
public ResponseEntity<Identity> test(Identity user){
return new ResponseEntity<>(user, HttpStatus.OK);
}
}
And finally, the user should have all the roles ADMIN
and USER
to access this endpoint.
WARNING
If you specify more than one option, all options will be checked and their results will be combined
So if you specify hasRole = "USER"
and hasAnyRole = {"ADMIN", "MANAGER"}
and the user has the role USER
the decision will be true && false
which is false
. So the user will not be able to access the endpoint.
Exception handling
By default, fsecurity will throw an AuthenticationException
if the user is not authenticated and an AuthorizationException
if the user is not authorized to access the endpoint.
In the case of AuthenticationException
the response will be 401 Unauthorized
and in the case of AuthorizationException
the response will be 403 Forbidden
.
If you want to handle these exceptions differently you can set both the authenticationExceptionHandler
and the authorizationExceptionHandler
on your AuthenticationHandler
.
SecurityExceptionHandler
is an interface with a single method handleException
which takes a HttpServletRequest
, HttpServletResponse
and an Exception
as parameters.
@Bean
public AuthenticationHandler authenticationHandler() {
return new JwtAuthenticationHandler()
.authorizationExceptionHandler((request, response, e) -> {
System.out.println("Authorization exception handler custom");
response.sendError(403);
})
.authenticationExceptionHandler((request, response, e) -> {
System.out.println("Authentication exception handler custom");
response.sendError(401);
});
}
Since the SecurityExceptionHandler
is a functional interface you can use a lambda expression to set the handlers.
Multiple AuthenticationHandlers
You can have multiple AuthenticationHandler
beans in your project. If you have more than one AuthenticationHandler
you must explicitly specify which one you want to use for a specific endpoint.
public class TestController {
@Authenticated(hasAnyRole = {"ADMIN", "USER"}, handler = BasicAuthenticationHandler.class)
@GetMapping("/test")
public ResponseEntity<Identity> test(Identity user){
return new ResponseEntity<>(user, HttpStatus.OK);
}
}
In this example the BasicAuthenticationHandler
will be used to authenticate the request. If at the point of processing the request the BasicAuthenticationHandler
is not the only type of AuthenticationHandler
in the project, an NoUniqueBeanDefinitionException
will be thrown and you should see a message like this in the logs:
Multiple AuthenticationHandler beans found, you must specify which one to use on the @Authenticated annotation
TIP
There is no need to define a handler if you only have one AuthenticationHandler
in your project.