16

I believe I have basic authentication working but I'm not sure how to protect resources so that they can only be accessed when the user is signed in.

public class SimpleAuthenticator implements Authenticator<BasicCredentials, User> {
    UserDAO userDao;

    public SimpleAuthenticator(UserDAO userDao) {this.userDao = userDao;}

    @Override
    public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException    
    {
        User user = this.userDao.getUserByName(credentials.getUsername());
        if (user!=null &&
                user.getName().equalsIgnoreCase(credentials.getUsername()) &&
                BCrypt.checkpw(credentials.getPassword(), user.getPwhash())) {
            return Optional.of(new User(credentials.getUsername()));
        }
        return Optional.absent();
    }
}

My Signin resource is like this:

@Path("/myapp")
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {
    @GET
    @Path("/signin")
    public User signin(@Auth User user) {
        return user;
    }
}

And I sign the user with:

~/java/myservice $ curl -u "someuser" http://localhost:8080/myapp/signin
Enter host password for user 'someuser':
{"name":"someuser"}

Question

Let's say the user signs in from a browser or native mobile app front end using the /myapp/signin endpoint. How then can I protect another endpoint, say, /myapp/{username}/getstuff which requires a user to be signedin

@GET
@Path("/myapp/{username}/getstuff")
public Stuff getStuff(@PathParam("username") String username) {
    //some logic here
    return new Stuff();
}
birdy
  • 8,476
  • 22
  • 98
  • 170

2 Answers2

26

There are 2 things when you are trying to implement REST. One is Authentication (which seems that you have got it working) and other is Authorization (which is what I believe your question is).

The way I have handled it in dropwizard before is, with every user signin, you return some kind of access_token (this proves they authenticated) back to the client which has to be returned by them in EVERY successive call they make as a part of some header (normally this is done through "Authorization" header). On the server side, you will have to save/map this access_token to THAT user before returning it back to the client and when all the successive calls are made with that access_token, you look up the user mapped with that access_token and determine if that user is authorized to access that resource or not. Now an example:

1) User signs in with /myapp/signin

2) You authenticate the user and send back an access_token as a response while saving the same on your side, such as, access_token --> userIdABCD

3) The client comes back to /myapp/{username}/getstuff. If the client does not provided the "Authorization" header with the access_token you gave them, you should return 401 Unauthorized code right away.

4) If the client does provide the access_token, you can look up the user based on that access_token you saved in step # 2 and check if that userId has access to that resource of not. If it does not, return 401 unauthorized code, and if it does have access, return the actual data back.

Now coming ot the "Authorization" header part. You could get access to "Authoroziation" header in all of your calls using the "@Context HttpServletRequest hsr" parameter but does it make sense to add that parameter in each call? No it doesn't. This is where the Security Filters help in dropwizard. Here's an example to how to add security filter.

public class SecurityFilter extends OncePerRequestFilter{
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException{
String accessToken = request.getHeader("Authorization");
// Do stuff here based on the access token (check for user's authorization to the resource ...
}

Now, which resource does this security filter really protects? For that you will need to add this filter to the specific resources you want to protect which can be done as follows:

environment.addFilter(SecurityFilter, "/myapp/*");

Remember on thing here that both your urls /myapp/signin and /myapp/{username}/getstuff, both will go through this security filter, BUT, /myapp/signin will NOT have an access_token, obviously because you haven't given any to the client yet. That wil have to be taken care of in the filter itself such as:

String url = request.getRequestURL().toString();
if(url.endsWith("signin"))
{
// Don't look for authorization header, and let the filter pass without any checks
}
else
{
// DO YOUR NORMAL AUTHORIZATION RELATED STUFF HERE
}

The url that you are protecting will depend on the how your urls are structured and what you want to protect. The better urls you design, the easier it will be to write security filters for their protection With the addition of this security filter the flow will be like this:

1) User goes to /myapp/signin. The call will go through the filter and because of that "if" statement, it will continue to your ACTUAL resource of /myapp/signin and you will assign an access_token based on successful authentication

2) User makes a call to /myapp/{username}/mystuff with the access_token. This call will go through the same security filter and will go through the "else" statement where you actually do your authorization. If the authorization goes through, the call will continue to you actual resource handler, and if not authorized, 401 should be returned.

public class SecurityFilter extends OncePerRequestFilter
{

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException
    {
        String url = request.getRequestURL().toString();
        String accessToken = request.getHeader("Authorization");
        try
        {
            if (accessToken == null || accessToken.isEmpty())
            {
                throw new Exception(Status.UNAUTHORIZED.getStatusCode(), "Provided access token is either null or empty or does not have permissions to access this resource." + accessToken);
            }
            if (url.endsWith("/signin"))
            {
    //Don't Do anything
                filterChain.doFilter(request, response);
            }
            else
            {
    //AUTHORIZE the access_token here. If authorization goes through, continue as normal, OR throw a 401 unaurhtorized exception

                filterChain.doFilter(request, response);
            }
        }
        catch (Exception ex)
        {
            response.setStatus(401);
            response.setCharacterEncoding("UTF-8");
            response.setContentType(MediaType.APPLICATION_JSON);
            response.getWriter().print("Unauthorized");
        }
    }
}

I hope this helps! Took me about 2 days to figure this out myself!

kylieCatt
  • 9,293
  • 4
  • 39
  • 50
shahshi15
  • 2,332
  • 2
  • 17
  • 23
  • Awesome. thanks for a great explanation. two questions: 1) what are you using to create the unique session id each time? `UUID`? 2) what are you using to store the sessionIds and match them agains the ones coming in? HashMap? – birdy Dec 18 '13 at 19:28
  • when you say unique session id what do you exactly mean? Access token? If so, yes, I use UUID for access token. These access tokens expire every hour (I am using OAuth so that's according to the standards, for basic authentication it might be different) and so in a clustered environment, they are not stored in memory but all the way to the database. We use couchbase (NoSql) database so essentially the whole database is a big huge Hashmap for us :). If you do it in memory, in a clustered environment, you will start having problems with managing this in-memory map across app servers. – shahshi15 Dec 18 '13 at 22:37
  • Forgot to mention. REST is stateless per standards, so there are no session ids (although, sometimes I have seen people implement session within REST as well .. not recommended). Each request is stateless and to authorize each request some kind of token (or basic authentication on each request) has to be done. – shahshi15 Dec 18 '13 at 23:56
  • Good to know. For now I'm not envisioning a clustered environment. I'll only one one app server, so I'll just save the tokens in an in-memory map for now. Finally, are you letting users signup using OAuth? Would you mind sharing how you implemented OAuth in a gist? Thanks again – birdy Dec 19 '13 at 03:56
  • Also, in your above snippet. What is `OncePerRequestFilter`? – birdy Dec 19 '13 at 04:12
  • looks like you are using shiro – birdy Dec 19 '13 at 04:40
  • Actually `OncePerRequestFilter` is a Spring Framework class. More details here: [link](http://docs.spring.io/spring/docs/1.2.9/api/org/springframework/web/filter/OncePerRequestFilter.html). Now using OAuth really depends on your usecase. It depends on WHO is signing up. So for example, if a USER is signing up in a TENANT (company) using your apis, then the TENANT is going to be your REST client and the signup calls that the TENANT makes is going to have the access token (provided they already have access to clientId/secret and/or clientId/private key, depending on your REST implementation). – shahshi15 Dec 19 '13 at 08:32
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/43485/discussion-between-xmenymenzmen-and-birdy) – shahshi15 Dec 19 '13 at 08:37
3

Sorry for being a simple user . I believe you can protect the resource by using a @Auth User user

public Service1Bean Service1Method1(
    @Auth User user,
    @QueryParam("name") com.google.common.base.Optional<String> name) {
kylieCatt
  • 9,293
  • 4
  • 39
  • 50
  • Yes you can protect resources this way, but as @xmenymenzmen mentions, this is only authentication, and not authorization. – Geert Sep 24 '14 at 10:41