12
\$\begingroup\$

I'd like to get some feedback on an example web API I've put together. It's mavenized and available on GitHub here.

Setup

The only thing you'd have to change to run this locally is line 31 in src/main/java/com.example.config.PersistConfig.java:

String databaseLocation = "jdbc:derby:C:\\Users\\Kevin\\Desktop\\SpringExampleDB;create=true";

Change that to a path on your machine - it doesn't have to exist yet since Derby will create it for you.

The Application

The application is pretty basic: users can register, login, edit their "profile" (which is just a single description text), and view other member profiles.

Registration, login, and authentication are handled through Spring Security. Getting a list of members, information on a particular member, or editing your own information are handled through a web API.

Questions:

I'm mostly looking for criticisms on my overall setup, so I don't have a specific question. But in the interest of being more Stack Overflow-y:

Here is the JSP for viewing a particular member:

<!DOCTYPE html>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html lang="en">
  <head>
    <title>Spring Example</title>

    <script>var baseUrl = '<c:url value="/"/>';</script>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
    <script>
    function getMember(){
        var name = window.location.href.substring(window.location.href.lastIndexOf('/')+1);
        $.getJSON(baseUrl+"api/members/"+name, function(member) {
            console.log(member);
            $("#name").html(member.name);
            $("#description").html(member.description);
        });
    }

    $(getMember());

    </script>
  </head>
<body>
    <ul>
    <li><a href="<c:url value="/" />">Home</a></li>
    <li><a href="<c:url value="/login" />">Login</a></li>
    <li><a href="<c:url value="/logout" />">Logout</a></li>
    <li><a href="<c:url value="/members" />">Members</a></li>
    </ul>
    <hr/>
    <p id="name"></p>
    <p id="description"></p>
</body>
</html>
  • Is there a better way to get the base URL than <script>var baseUrl = '<c:url value="/"/>';</script>? This code will be used as an example, so it might be deployed to localhost:8080/SpringExample, or as localhost:8080/, or to a "real" domain, so I want to be as flexible as possible. This is easy in JSP, but passing in the base URL from JSP to JavaScript seems kludgey.
  • The <script>var baseUrl = '<c:url value="/"/>';</script> line happens on the server. Does that prevent this generated html from being cached?
  • Is this approach of fetching the member information via JavaScript reasonable? Or should I be using a JSP for this?
  • Would google be able to index the pages (assuming they were unauthenticated) that use JavaScript to load content?

Here is my API controller:

@Component
public class ApiController implements ApiControllerInterface{

    @Autowired
    private MemberDao memberDao;

    @RequestMapping(value = "/members", method = RequestMethod.GET)
    @Transactional
    public List<Member> getMembers(){
        return memberDao.getMembers();
    }

    @RequestMapping(value="/members/{name}", method = RequestMethod.GET)
    @Transactional
    public Member member(@PathVariable(value="name") String name){
        return memberDao.getMember(name);
    }

    @RequestMapping(value="/members/{memberName}", method = RequestMethod.POST)
    @Transactional
    public Member memberPost(Member member, Principal principal){
        if(principal.getName().equals(member.getName())){
            memberDao.updateMember(member);
        }

        return member;
    }

}
  • To make sure a member can only edit their own profile, I'm passing in the Principal to the memberPost() function. Is that a reasonable approach, or is there a better alternative I should consider?
  • Are there any security "holes" that I'm not covering?
  • I know that this might not be strictly REST since the server does have session information, but I'm okay with that... unless I shouldn't be?

My end goal is to create a tutorial that shows people how to create a web API that can then be used by either a website or a mobile app. Is this a reasonable barebones example to work from, or am I doing any glaringly stupid things?

\$\endgroup\$
1
\$\begingroup\$

I am not an expert but I will try to answer

  • Is there a better way to get the base URL

A better way would be to use the request attribute "javax.servlet.forward.request_uri"

In your case you can do <script>var baseUrl = '<c:url value="${requestScope['javax.servlet.forward.request_uri']}"/>';</script>

You can also access it by calling request.getAttribute("javax.servlet.forward.request_uri")

  • Does calling <script>var baseUrl = '<c:url value="/"/>';</script> prevent html from being cached?

As far as I am concerned, no. When you request the page, it will always return the same value. I think it would be a problem only if you have a dynamic url.

  • Is this approach of fetching the member information reasonable?

It depends on your needs. If you plan to make this service available in other apps, Endpoints apis are probably a good choice. Otherwise jstl tags would fit just nicely.

  • Are there any security holes?

You can limit the access of your Endpoint by using annotations and defined roles. For instance if you only want you users to execute your method you can modify it as follow:

@RolesAllowed("ROLE_USER")
@RequestMapping(value="/members/{memberName}", method = RequestMethod.POST)
@Transactional
  public Member memberPost(Member member, Principal principal){
    if(principal.getName().equals(member.getName())){
      memberDao.updateMember(member);
    }
  return member;
}

An other thing you can verify is to be sure that the user is currently authenticated. Your method would then look like this:

@RolesAllowed("ROLE_USER")
@RequestMapping(value="/members/{memberName}", method = RequestMethod.POST)
@Transactional
public Member memberPost(Member member, Principal principal){
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    if( !(auth instanceof AnonymousAuthenticationToken)){
        if(principal.getName().equals(member.getName())){
            memberDao.updateMember(member);
        }
    }
    return member;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.