Как выполнить базовую проверку подлинности ресурса в Dropwizard

Я считаю, что у меня работает базовая аутентификация, но я не уверен, как защитить ресурсы, чтобы они могли быть доступны только при входе пользователя.

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();
    }
}

Мой ресурс Signin выглядит так:

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

И я подписываю пользователя с помощью:

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

Вопрос

Скажем, пользователь подписывается из браузера или собственного мобильного приложения, используя конечную точку /myapp/signin. Как тогда я могу защитить другую конечную точку, скажем, /myapp/{username}/getstuff, для которой требуется, чтобы пользователь был подписан в

@GET
@Path("/myapp/{username}/getstuff")
public Stuff getStuff(@PathParam("username") String username) {
    //some logic here
    return new Stuff();
}

Ответы

Ответ 1

Есть две вещи, когда вы пытаетесь реализовать REST. Одна из них - это аутентификация (которая кажется, что у вас есть ее работа), а другая - авторизация (вот что я считаю вашим вопросом).

Как я уже справился с этим в dropwizard, с каждым подписчиком пользователя вы возвращаете какой-то access_token (это доказывает, что они аутентифицированы) обратно клиенту, который должен быть возвращен ими в КАЖДОМ последовательном вызове, который они делают как часть некоторого заголовка (обычно это делается через заголовок "Авторизация" ). На стороне сервера вам нужно будет сохранить/сопоставить этот access_token с этим пользователем, прежде чем возвращать его клиенту, и когда все последующие вызовы будут сделаны с помощью этого access_token, вы посмотрите на пользователя, сопоставленного с этим access_token, и определите, является ли этот пользователь имеет право доступа к этому ресурсу или нет. Теперь пример:

1) Пользователь подписывается с помощью /myapp/signin

2) Вы аутентифицируете пользователя и отправляете обратно access_token в качестве ответа, сохраняя его на своей стороне, например access_token → userIdABCD

3) Клиент возвращается к /myapp/ {username}/getstuff. Если клиент не предоставил заголовок "Авторизация" с предоставленным вами access_token, вы должны сразу же отправить 401 Несанкционированный код.

4) Если клиент действительно предоставляет access_token, вы можете найти пользователя на основе этого access_token, который вы сохранили на шаге 2, и проверить, имеет ли этот userId доступ к этому ресурсу. Если это не так, верните 401 несанкционированный код, и если у него есть доступ, верните фактические данные обратно.

Теперь выходим из части заголовка "Авторизация" . Вы можете получить доступ к заголовку "Authoroziation" во всех своих вызовах, используя параметр "@Context HttpServletRequest hsr", но имеет ли смысл добавить этот параметр в каждый вызов? Нет, нет. Здесь защитные фильтры помогают в dropwizard. Вот пример того, как добавить фильтр безопасности.

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 authorization to the resource ...
}

Теперь, какой ресурс этот защитный фильтр действительно защищает? Для этого вам нужно будет добавить этот фильтр к определенным ресурсам, которые вы хотите защитить, что можно сделать следующим образом:

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

Помните о том, что как ваши URL/myapp/signin, так и /myapp/ {username}/getstuff, оба будут проходить через этот фильтр безопасности, НО, /myapp/signin НЕ будет иметь access_token, очевидно, t, предоставленных клиенту. Это необходимо позаботиться в самом фильтре, например:

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
}

URL, который вы защищаете, будет зависеть от того, как структурированы ваши URL-адреса и что вы хотите защитить. Лучшие URL-адреса, которые вы разрабатываете, тем легче будет писать фильтры защиты для их защиты. С добавлением этого фильтра безопасности поток будет выглядеть следующим образом:

1) Пользователь переходит в /myapp/signin. Вызов будет проходить через фильтр, и из-за этого оператора "if" он будет продолжать ваш ACTUAL-ресурс/myapp/signin, и вы назначите access_token на основе успешной аутентификации

2) Пользователь делает вызов /myapp/ {username}/mystuff с помощью access_token. Этот вызов будет проходить через тот же фильтр безопасности и будет проходить через инструкцию else, где вы фактически выполняете авторизацию. Если авторизация пройдет, вызов продолжит ваш фактический обработчик ресурсов, и если он не авторизован, ему необходимо вернуть 401.

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");
        }
    }
}

Надеюсь, это поможет! Принял меня около двух дней, чтобы понять это сам!

Ответ 2

Извините за то, что вы простой пользователь. Я считаю, что вы можете защитить ресурс, используя пользователя пользователя @Auth.

public Service1Bean Service1Method1(
    @Auth User user,
    @QueryParam("name") com.google.common.base.Optional<String> name) {