Authorization in JAX-RS - java

I am developing an application using javaEE / Wildfly and JAX-RS for the restful service.
I have this kind of endpoint :
#POST
#Path("/add")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response addSchool(SchoolDto schoolDto, #HeaderParam("token") String userToken) {
List<String> actionsNeeded = new ArrayList<String>(
Arrays.asList(
"create school"
));
if (authService.userHasActionList(userToken, actionsNeeded) == false )
{
return authService.returnResponse(401);
}
Response addSchoolServiceResponse = schoolResponse.create(schoolDto);
return addSchoolServiceResponse;
}
Using the token in Header my auth service will check if the user account has, in his list of authorized actions, those that are necessary to use the checkpoint.
It's working, but I'm repeating that on each checkpoint ... I'm looking for a way to do that :
#POST
#Path("/add")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#Annotation("action 1 needed", "Action 2 needed")
public Response addSchool(SchoolDto schoolDto, #HeaderParam("token") String userToken) {
Response addSchoolServiceResponse = schoolResponse.create(schoolDto);
return addSchoolServiceResponse;
}
an annotation where i can pass some parameters (my actions and most important be able to have the user token) who trigger using filter or whatever the security check return a 401 or let the method to be executed if user is allowed to be there.
I've find a lot of stuff (#Secured etc...) for security based on role but not on action like that
Is someone already did something like that ?

Finally I've started all over and it's working, my principal problem was to access token in the header and working with annotations and it's ok now (just need to insist and try one more time i assume ...) here is what it's look likes :
#Provider
#Actions
public class AuthorizationFilter implements ContainerRequestFilter {
#EJB
AuthService authService;
#Context
private ResourceInfo resourceInfo;
List<String> actionsNeeded = new ArrayList<String>();
#Override
public void filter(ContainerRequestContext reqContext) throws IOException {
Actions annotations = resourceInfo.getResourceMethod().getAnnotation(Actions.class);
String token;
try {
token = reqContext.getHeaders().get("token").get(0);
for (String annotation : annotations.value()) {
actionsNeeded.add(annotation);
}
if (authService.userHasActionList(token, actionsNeeded) == false )
{
reqContext.abortWith(authService.returnResponse(401));
return;
}
} catch (Exception e) {
System.out.println("Headers 'token' does not exist !");
reqContext.abortWith(authService.returnResponse(400));
}
}
}

Related

How to make REST api that can redirect to url in SpringBoot

I have created a REST api which can used to save different urls those url have auto-increment feature that assign them an id one endpoint is to add urls and other is to fetch urls from id
I want to do something like if I pass localhost:8080/getUrlById?id=4/ my browser should redirect me to that url which is there at 4th no.
my controller code -
#GetMapping("/addUrl")
public ResponseEntity<?> addUrlByGet(String url) {
return new ResponseEntity<>(sortnerService.addUrlByGet(url),HttpStatus.OK);
}
#GetMapping("/findUrlById")
public ResponseEntity<?> findSortnerById(Integer id){
return new ResponseEntity<>(sortnerService.findUrlById(id), HttpStatus.OK);
}
service class -
#Service
public class SortnerService {
#Autowired
private SortnerRepo sortnerRepo;
public Sortner addUrlByGet(String url) {
Sortner sortner = new Sortner();
sortner.setUrl(url);
return sortnerRepo.save(sortner);
}
// finding by particular Id
public List<Sortner> findUrlById(Integer id){
return sortnerRepo.findSortnerById(id);
}
}
Can anyone suggest me any way to do it I am really new to SpringBoot Sorry if I have made any silly mistake.
Based on the information from the comments, I suggest that the Sortner class looks like this:
public class Sortner {
Long id;
URL url;
}
So to redirect to the URL by the Id from your service you need to rewrite your controller to look like this:
#GetMapping("/findUrlById")
public void findSortnerById(Integer id, HttpServletResponse response) throws IOException {
List<Sortner> urls = sortnerService.findUrlById(id);
if(urls != null && urls.size() > 0) {
response.sendRedirect(urls.get(0).getUrl().toString());
}
response.sendError(HttpServletResponse.SC_NOT_FOUND)
}
response.sendRedirect redirects to the required URL
response.sendError returns 404 as the URL cannot be found in the database

How can i modify the order of Micronaut Http Filter so it get executed after Micronaut Security

I have a checkForBankId annotation
#Target(value = { ElementType.METHOD, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
#Retention(value=RetentionPolicy.RUNTIME)
public #interface checkForBankId {
boolean ignore() default false;
}
And i have a BankIdSecurityService
public class BankIdSecurityService {
/*
* This is declarative client to call the
* InternalAPIs to get all details about the user;
*/
#Inject
UserRepositoryClient client;
static final boolean AUTHORIZED = true;
static final boolean UnAUTHENTICATED = false;
Flowable<Boolean> checkAuthentication(HttpRequest<?> request) {
if (routeMatch instanceof MethodBasedRouteMatch) {
MethodBasedRouteMatch<?, ?> methodBasedRouteMatch =
(MethodBasedRouteMatch<?, ?>) routeMatch;
if (methodBasedRouteMatch.hasAnnotation(checkForBankId.class)) {
String userName = getUserName(request).get();
if(!request.getParameter().contains("bankId")) {
return Flowable.just(UNAUTHORIZED);
}
return this.client.getUser(userName).map(userDetails -> {
String bankId = request.getParameter().get("bankId");
BankAccount[] bankAccounts = userDetails.getPayload().getBankAccounts();
boolean bankIdFound = false;
for(BankAcconut bankAccout: bankAccounts) {
if(bankAccount.getId().equals(bankId)) {
bankIdFound = true;
break;
}
}
return bankIdFound ? AUTHENTICATED : UNAUTHORIZED;
});
}
}
return Flowable.just(AUTHENTICATED);
}
Optional<String> getUserName(HttpRequest<?> request) {
// get jwt from request header
then parse the jwt and extract
the username from it and return;
}
}
then i have Filter which executes once per request
#Filter(Filter.MATCH_ALL_PATTERN)
#AllArgsConstructor
#Slf4j
public class BankIdSecurityFilter extends OncePerRequestHttpServerFilter {
private final BankIdSecurityService bankIdSecurityService;
#Override
protected Publisher<MutableHttpResponse<?>> doFilterOnce(
HttpRequest<?> request, ServerFilterChain chain
) {
log.info("request route: {}", request);
return this.bankIdSecurityService
.checkAuthentication(request).switchMap(authResult -> {
log.info("authentication result: {}", authResult);
return authResult ? // if authResult is true then proceed as usual
chain.proceed(request) :
Publishers.just(HttpResponse.status(HttpStatus.BAD_REQUEST)
.body("This bankId doesn't belond to this user.")
); // return bad request
});
}
#Override
public int getOrder() {
return ServerFilterPhase.SECURITY.after();
}
}
Inside controller we apply the #CheckForBankId where we want to filter the request
#CheckForBankId
#GET("get-bank-account?{bankId}")
Single<BankAccount> getBankAccount(String bankId) {
return someService.getBankAccount(bankId);
}
The filter is working fine. The thing which is missing is that it gets executed before microanut
security so, the token which is coming in header is unauthenticated and from some cases it can
lead to some problems like what if an expired token passed to an header and my parser will parse
the token and will return the user from it but it makes no sense as the token is already invalid. It should have already been reject before coming to the filter..so, like this filter to
get executed after micronaut security. I tried overridding the getOrder method but it didn't work :(
You didn't specify which Micronaut version you're working with, but the answer is that the behavior is a bug. It should work as expected in Micronaut 2.5.4

Is there a way to get a list of all jersey urls a user, based on their role and #RolesAllowed, has access to?

I am hoping to offer a rest api call to clients(via plain jersey, not spring) to return a list of all endpoints allowed for the specific user based on the JWT they send in the header. I have found on stackoverflow(thanks contributors!) example code to get all endpoints, regardless of role, but not the subset based on role. I have found how to get the annotations per method as well, but would like to avoid re-inventing the wheel of "if #PermitAll and not #DenyAll, or role in RolesAllowed, etc...".
Any chance Jersey 2.0 has a a method I can call that will resolve to true/false given SecurityContext and url endpoint or method?
boolean allowed = isMethodAllowed(SecurityContext ctx, String url);
Or
boolean allowed = isMethodAllowed(SecurityContext ctx, Class method);
Thanks to this post: Listing all deployed rest endpoints (spring-boot, jersey)
Specifically Johanne Jander's post(thank you!), I've come up with the below code which seems to work in my case, a simple jersey use case. Providing it here in case it's useful to others.
#Path("/v1/userinterface")
#Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public class MyUI extends MyRestApi {
private final static Logger log = LoggerFactory.getLogger(MyUI.class);
#Context
private Configuration configuration;
#Context
private SecurityContext security;
#Path("/allowedendpoints")
#GET
#Operation(summary = "List API access points allowed for the currently authenticated user", tags = {
"ui" }, description = "Returns a list of urls", responses = {})
public Response showAll(#Context UriInfo ui) {
log.debug("Get list of all allowed endpoints for user: " + security.getUserPrincipal().getName());
HashMap<String, ArrayList<String>> map = new HashMap<String, ArrayList<String>>();
for (Class<?> c : configuration.getClasses()) {
// Since all of my endpoint classes extend MyRestApi,
// only scan them, not all classes
if (MyRestApi.class.isAssignableFrom(c)) {
scanClass(c, map);
}
}
return Response.ok().entity(map).build();
}
public void scanClass(Class<?> baseClass, HashMap<String, ArrayList<String>> map) {
Builder builder = Resource.builder(baseClass);
if (null != builder) {
Resource resource = builder.build();
String uriPrefix = "";
process(uriPrefix, resource, map);
}
}
private void process(String uriPrefix, Resource resource, HashMap<String, ArrayList<String>> map) {
// recursive method
String pathPrefix = uriPrefix;
List<Resource> resources = new ArrayList<>();
resources.addAll(resource.getChildResources());
if (resource.getPath() != null) {
pathPrefix = (pathPrefix + "/" + resource.getPath()).replaceAll("//", "/");
}
for (ResourceMethod method : resource.getAllMethods()) {
if (method.getType().equals(ResourceMethod.JaxrsType.SUB_RESOURCE_LOCATOR)) {
resources.add(Resource
.from(resource.getResourceLocator().getInvocable().getDefinitionMethod().getReturnType()));
} else {
if (isPathAllowed(security, method.getInvocable().getDefinitionMethod())) {
if (map.containsKey(pathPrefix))
map.get(pathPrefix).add(method.getHttpMethod());
else
map.put(pathPrefix, new ArrayList<String>(Arrays.asList(method.getHttpMethod())));
}
}
}
for (Resource childResource : resources) {
process(pathPrefix, childResource, map);
}
}
public boolean isPathAllowed(SecurityContext ctx, Method method) {
// #DenyAll on the method takes precedence over #RolesAllowed and #PermitAll
if (method.isAnnotationPresent(DenyAll.class)) {
return (false);
}
// #RolesAllowed on the method takes precedence over #PermitAll
RolesAllowed rolesAllowed = method.getAnnotation(RolesAllowed.class);
if (rolesAllowed != null) {
return (hasRole(ctx, rolesAllowed.value()));
}
// #PermitAll on the method takes precedence over #RolesAllowed on the class
if (method.isAnnotationPresent(PermitAll.class)) {
return (true);
}
// #DenyAll can't be attached to classes
// #RolesAllowed on the class takes precedence over #PermitAll on the class
rolesAllowed = method.getDeclaringClass().getAnnotation(RolesAllowed.class);
if (rolesAllowed != null) {
return (hasRole(ctx, rolesAllowed.value()));
}
// #PermitAll on the class
if (method.getDeclaringClass().isAnnotationPresent(PermitAll.class)) {
return (true);
}
return (false); // default
}
private boolean hasRole(SecurityContext ctx, String[] rolesAllowed) {
for (final String role : rolesAllowed) {
if (ctx.isUserInRole(role)) {
return (true);
}
}
return (false);
}
Which returns endpoints the currently authenticated user has access to based on SecurityContext markup with the #DenyAll, #PermitAll and #RolesAllowed annotations.
Full disclosure, my app is simple with just basic class and method annotations at the endpoints. ymmv.
Sample output:
{
"/v1/resources" : [
"POST",
"GET"
],
"/v1/resources/{id}" : [
"DELETE",
"GET"
]
}

Spring Rest API - spurious/not requested parameters strategy

According to this discussion - "RESTful API - Correct behavior when spurious/not requested parameters are passed in the request", we shouldn't ignore not requested parameters but how we can process this situation on all endpoint?
For example for this endpoint:
#RequestMapping(value = "/transactions/",
method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public RestResultSupport getCommandsById(#PathVariable("id") String id) throws IOException {
validateId(id);
....
return result;
}
We'll get the same result for 2 different requests:
curl localhost:8080/?id=1200
and
curl localhost:8080/?id=1200&unknown=incorrect
If we imagine that we should process this situation on 20 endpoints, how can we simplify our code? Does Spring provide some tools for that?
I found only one way to do this - implement HandlerInterceptor.
Please have a look at an example:
public class RequestInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
Set<String> innerParams = request.getParameterMap().keySet();
Set<String> describedParams = new HashSet<>();
for (MethodParameter methodParameter : ((HandlerMethod) handler).getMethodParameters()) {
if (methodParameter.hasParameterAnnotation(RequestParam.class)) {
RequestParam requestParam = methodParameter.getParameterAnnotation(RequestParam.class);
describedParams.add(requestParam.name());
}
}
for (String inputRequestParam : innerParams) {
if (!describedParams.contains(inputRequestParam)) {
throw new BadDataException("Please provide valid request paramaters. [ Valid request parameters - " + describedParams + " ]");
}
}
return true;
}
... empty other required methods ...
}
Code analyzes required parameters and if it gets something unknown it will throw RuntimeException

Entity not getting saved in database even though getting 204 as HTTP Status code

I'm using JPA 2.0 with Hibernate 4.3.0.Final along with Google Guice 3.0 with dropwizard framework. In my Resource class, I have applied #Transactional annotation at the top.
#Path("/users")
#Slf4j
#Transactional
#Produces(MediaType.APPLICATION_JSON)
public class UsersResource {
#PUT
#Timed
#Path("/{userId}/userAddress")
#Consumes(MediaType.APPLICATION_JSON)
public void setUserAddress(#Valid SetUserAddressRequest request,
#PathParam("userId") String userId) {
if (!userId.equals(request.getUserId())) {
throw new BadRequestException(Constants.USERID_ID_DO_NOT_MATCH);
}
request.setUserId(userId);
setUserAddressCommandProvider
.get()
.withRequest(request)
.run();
}
}
My run function in command class looks like this
private User getUserByUserId(String userId) throws ResourceNotFoundException {
Optional<User> optionalUser = userRepository.getUser(userId);
if (optionalUser.isPresent()) {
return optionalUser.get();
}
throw new ResourceNotFoundException(Constants.COULD_NOT_FIND_USER);
}
public Void run() throws ResourceNotFoundException, WebApplicationException {
User user = getUserByUserId(request.getUserId());
String address = request.getAddress();
if (user.getAddress() != null) {
throw new ResourceConflictException(Constants.ADDRESS_ALREADY_EXISTS);
}
user.setAddress(address);
user.setUpdatedAt(new Date());
return null;
}
The problem is that most of the times address gets saved but sometimes it doesn't even though I'm getting 204 as response code. Any pointers on how to debug it would be much appreciated.
Update: When I turn the debug mode on, I see this exception in logs
org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl: Exception clearing
maxRows/queryTimeout [org.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement
with address: "com.mysql.jdbc.JDBC4PreparedStatement#76eb0f79: EXCEPTION:
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException:
No operations allowed after statement closed." is closed.]
Have you tried the #UnitOfWork annotation on the setUserAddress method? https://dropwizard.github.io/dropwizard/manual/hibernate.html

Categories