LoginSignup
11
10

More than 5 years have passed since last update.

【メモ】DropwizardでCookieのsession idで認証してみる

Posted at

やってること

せっかくfreemarkerとか梱包されてるので、普通のWEBアプリケーションを作ってみる上で、認証をSessionIDでやってみる。
そしてきっと探せば自作しなくてもすでにあるのかもしれない。

/* TODO コードとか作りとか色々汚いので使うときにはちゃんと修正する */
  • Cookieにsidが設定されていない場合はログイン画面に飛ばす
  • ログイン時にCookieにsidを設定してあげる

コード

とりあえず書いてみた適当なコード

jp.hoge.app.auth.SessionUserAuthFactory.java
package jp.hoge.app.auth;

import java.util.Arrays;
import java.util.Optional;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;

import io.dropwizard.auth.AuthFactory;
import io.dropwizard.auth.AuthenticationException;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.UnauthorizedHandler;

/**
 * ほぼほぼio.dropwizard.auth.basic.BasicAuthFactoryの写経でいけた
 */
public class SessionUserAuthFactory<T> extends
        AuthFactory<HogeTokenCredentials, T> {

    @Context
    private HttpServletRequest request;
    private final boolean required;
    private UnauthorizedHandler unauthorizedHandler = new HogeUnauthorizedHandler();
    private final Class<T> generatedClass;
    private String prefix = "Sid"; // 使わないけど
    private final String realm; // 使わないけど

    public SessionUserAuthFactory(
            Authenticator<HogeTokenCredentials, T> authenticator,
            final String realm,
            final Class<T> generatedClass) {
        super(authenticator);
        this.required = false;
        this.realm = realm;
        this.generatedClass = generatedClass;
    }
    private SessionUserAuthFactory(final boolean required,
            final Authenticator<HogeTokenCredentials, T> authenticator,
            final String realm,
            final Class<T> generatedClass) {
        super(authenticator);
        this.required = required;
        this.realm = realm;
        this.generatedClass = generatedClass;
    }

    public SessionUserAuthFactory<T> responseBuilder(UnauthorizedHandler unauthorizedHandler) {
        this.unauthorizedHandler = unauthorizedHandler;
        return this;
    }

    @Override
    public T provide() {
        // ラムダ式で書いてみたかったの (正しいのか知らん)
        Optional<HogeTokenCredentials> credentials = Optional.ofNullable(request)
                .map(req -> req.getCookies())
                .map(cookies -> Arrays.asList(cookies).stream()
                        .filter(cookie -> "sid".equals(cookie.getName()))
                        .findFirst().orElseGet(null))
                .map(sidcookie -> sidcookie.getValue())
                .map(sid -> new HogeTokenCredentials(sid));

        if (credentials.isPresent()) {
            try {
                // guava出てきてもうた
                final com.google.common.base.Optional<T> result = authenticator().authenticate(credentials.get());
                if (result.isPresent()) return result.get();
            } catch (AuthenticationException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        } 
        // 認証必須のリソースの場合はUnauthorizedHandlerを利用してログイン画面に飛ばす
        if (required) {
            throw new WebApplicationException(unauthorizedHandler.buildResponse(prefix, realm));
        }

        return null;
    }

    @Override
    public void setRequest(HttpServletRequest request) {
        this.request = request;
    }

    @Override
    public AuthFactory<HogeTokenCredentials, T> clone(boolean required) {
        return new SessionUserAuthFactory<>(required, authenticator(),this.realm, this.generatedClass).responseBuilder(unauthorizedHandler);
    }

    @Override
    public Class<T> getGeneratedClass() {
        return generatedClass;
    }

}

jp.hoge.app.auth.HogeUnauthorizedHandler.java
package jp.hoge.app.auth;

import java.net.URI;
import java.net.URISyntaxException;

import javax.ws.rs.core.Response;

import io.dropwizard.auth.UnauthorizedHandler;

public class HogeUnauthorizedHandler implements UnauthorizedHandler {

    @Override
    public Response buildResponse(String prefix, String realm) {

        try {
            // ログイン画面にリダイレクト
            return Response.seeOther(new URI("/login")).build();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

        return Response.ok().build();
    }
}

jp.hoge.app.auth.HogeTokenCredentials.java
package jp.hoge.app.auth;

public class HogeTokenCredentials {

    private final String token;
    public HogeTokenCredentials(String token) {
        this.token = token;
    }
    public String getToken() {
        return token;
    }
}

jp.hoge.app.auth.HogeSessionAuthenticator.java
package jp.hoge.app.auth;

import com.google.common.base.Optional;

import jp.hoge.app.core.HogeUser;
import io.dropwizard.auth.AuthenticationException;
import io.dropwizard.auth.Authenticator;

public class HogeSessionAuthenticator implements Authenticator<HogeTokenCredentials, HogeUser> {

    @Override
    public Optional<HogeUser> authenticate(HogeTokenCredentials credentials)
            throws AuthenticationException {
        // 本来はredisとかに問い合わせて有効か確認してユーザ情報を取得する
        if ("abcdefg".equals(credentials.getToken())) {
            return Optional.of(new HogeUser("myID"));
        }

        return Optional.absent();
    }

}

jp.hoge.app.core.HogeUser.java
package jp.hoge.app.core;

public class HogeUser {

    private final String userId;

    public HogeUser(String userId) {
        this.userId = userId;
    }

    public String getUserId() {
        return userId;
    }

}

jp.hoge.app.resources.LoginResource.java
package jp.hoge.app.resources;


import java.net.URI;
import java.net.URISyntaxException;

import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;

import jp.hoge.app.views.LoginView;

@Path("/login")
public class LoginResource {

    @GET
    @Path("/")
    @Produces(MediaType.TEXT_HTML)
    public LoginView getLoginView() {
        return new LoginView(LoginView.Template.FREEMARKER);
    }

    @POST
    @Path("/signin")
    public Response signup(@FormParam("email") String email , @FormParam("password") String password) {
        // アイパス認証
        if ("test".equals(email) && "test".equals(password)) {
            // Cookie生成 (これだとブラウザ閉じるまでの間だけ)
            NewCookie cookies = new NewCookie("sid","abcdefg","/",null,"",NewCookie.DEFAULT_MAX_AGE,false);;
            try {
                // Cookieセットしてマイページ画面へリダイレクト
                return Response.seeOther(new URI("/mypage")).cookie(cookies).build();
            } catch (URISyntaxException e) {
                e.printStackTrace();
            }
        }

        try {
            // 認証失敗したらログイン画面へ戻る
            return Response.seeOther(new URI("/login")).build();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

        return Response.serverError().build();
    }
}
jp.hoge.app.resources.HogeUserResource.java
package jp.hoge.app.resources;

import io.dropwizard.auth.Auth;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import jp.hoge.app.core.HogeUser;
import jp.hoge.app.views.MypageTopView;

@Path("/mypage")
public class HogeUserResource {

    @GET
    @Path("/")
    @Produces(MediaType.TEXT_HTML)
    public MypageTopView getMypageView(@Auth HogeUser user) {
        // @AuthでCookie認証後にUserが取得される
        return new MypageTopView(MypageTopView.Template.FREEMARKER,user);
    }
}

jp.hoge.app.views.LoginView.java
package jp.hoge.app.views;


import io.dropwizard.views.View;

public class LoginView extends View {

    public enum Template {
        FREEMARKER("login.ftl");

        private String templateName;
        private Template(String templateName){
            this.templateName = templateName;
        }

        public String getTemplateName(){
            return templateName;
        }
    }

    public LoginView(LoginView.Template templateName) {
        super(templateName.getTemplateName());
    }

}
jp.hoge.app.views.MypageTopView.java
package jp.hoge.app.views;

import jp.hoge.app.core.HogeUser;
import io.dropwizard.views.View;

public class MypageTopView extends View {

    private final HogeUser user;
    public enum Template {
        FREEMARKER("mypage_top.ftl");

        private String templateName;
        private Template(String templateName){
            this.templateName = templateName;
        }

        public String getTemplateName(){
            return templateName;
        }
    }

    public MypageTopView(MypageTopView.Template templateName,HogeUser user) {
        super(templateName.getTemplateName());
        this.user = user;
    }

    public HogeUser getUser() {
        return user;
    }

}

jp.hoge.app.HogeApplication.java
package jp.hoge.app;

import jp.hoge.app.auth.HogeSessionAuthenticator;
import jp.hoge.app.auth.SessionUserAuthFactory;
import jp.hoge.app.core.HogeUser;
import jp.hoge.app.resources.LoginResource;
import jp.hoge.app.resources.HogeUserResource;

import com.google.common.collect.ImmutableMap;

import io.dropwizard.Application;
import io.dropwizard.auth.AuthFactory;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import io.dropwizard.views.ViewBundle;

public class HogeApplication extends Application<HogeConfiguration> {

    public static void main(String[] args) throws Exception {
        new HogeApplication().run(args);
    }

    @Override
    public String getName() {
        return "hoge-session-auth";
    }
    @Override
    public void initialize(Bootstrap<HogeConfiguration> bootstrap) {
        bootstrap.addBundle(new ViewBundle<HogeConfiguration>() {
            @Override
            public ImmutableMap<String, ImmutableMap<String, String>> getViewConfiguration(HogeConfiguration config) {
                return config.getViewRendererConfiguration();
            }
        });
{
    }

    @Override
    public void run(HogeConfiguration configuration,Environment environment) throws Exception {
        environment.jersey().register(AuthFactory.binder(new SessionUserAuthFactory<HogeUser>(new HogeSessionAuthenticator(), "HOGE USER", HogeUser.class)));

        final LoginResource loginResource = new LoginResource();
        environment.jersey().register(loginResource);
        final HogeUserResource hogeUserResource = new HogeUserResource();
        environment.jersey().register(hogeUserResource);
    }
}

jp.hoge.app.HogeConfiguration.java
package jp.hoge.app;

import javax.validation.constraints.NotNull;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.ImmutableMap;

import io.dropwizard.Configuration;

public class HogeConfiguration extends Configuration {

    @NotNull
    private ImmutableMap<String, ImmutableMap<String, String>> viewRendererConfiguration =  ImmutableMap.of();

    @JsonProperty("viewRendererConfiguration")
    public ImmutableMap<String, ImmutableMap<String, String>> getViewRendererConfiguration() {
        return viewRendererConfiguration;
    }
}

resources/jp/hoge/app/views/login.ftl
<#-- @ftlvariable name="" type="jp.hoge.app.views.LoginView" -->
<html>
  <head><meta charset="UTF-8"></head>
  <body>
   <h1>ログイン</h1>

   <form action="login/signup" method="post">
     <p>Eメール : <input type="text" name="email" size="40"></p>
     <p>パスワード : <input type="password" name="password" size="40"></p>
     <input type="submit" value="ログイン">
   </form> 
  </body>
</html>
resources/jp/hoge/app/views/mypage_top.ftl
<#-- @ftlvariable name="" type="jp.hoge.app.views.LoginView" -->
<html>
  <head>
    <meta charset="UTF-8">
  </head>
  <body>
   <!-- calls getUser().getUserId() and sanitizes it -->
   <h1>Hello, ${user.userId?html}!</h1>
  </body>
</html>
hoge.yml
# the key needs to match the suffix of the renderer
viewRendererConfiguration:
    .ftl:
        strict_syntax: yes
        whitespace_stripping: yes
server:
  applicationConnectors:
    - type: http
      port: 8082
    - type: https
      port: 8443
      validateCerts: false
  adminConnectors:
    - type: http
      port: 8084
    - type: https
      port: 8444
      validateCerts: false
build.gradle

plugins {
  id 'java'
  id 'com.github.johnrengelman.shadow' version '1.2.0'
}

apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'application'

defaultTasks 'shadowJar'

project.ext {
    authorName = 'Hogeo HOGETA'
    dropwizardVersion = '0.8.0'
}

sourceCompatibility = 1.8
targetCompatibility = 1.8
group = 'jp.hoge.app'
version = '0.1'

applicationName = 'hoge-session-auth'
mainClassName = 'jp.hoge.app.HogeApplication'

repositories {
    mavenLocal()
    mavenCentral()
}

def defaultEncoding = 'UTF-8'
tasks.withType(AbstractCompile)*.options*.encoding = defaultEncoding
tasks.withType(GroovyCompile)*.groovyOptions*.encoding = defaultEncoding

dependencies {
    // 全部は要らないけど消すのめんどいんで
    compile "io.dropwizard:dropwizard-core:${dropwizardVersion}"
    compile "io.dropwizard:dropwizard-assets:${dropwizardVersion}"
    compile "io.dropwizard:dropwizard-views:${dropwizardVersion}"
    compile "io.dropwizard:dropwizard-views-freemarker:${dropwizardVersion}"
    compile "io.dropwizard:dropwizard-migrations:${dropwizardVersion}"
    compile "io.dropwizard:dropwizard-hibernate:${dropwizardVersion}"
    compile "io.dropwizard:dropwizard-jdbi:${dropwizardVersion}"
    compile "io.dropwizard:dropwizard-auth:${dropwizardVersion}"

    testCompile "junit:junit:4.11"
    compile "io.dropwizard:dropwizard-testing:${dropwizardVersion}"

}

task wrapper(type: Wrapper) {
    gradleVersion = '2.2'
}

shadowJar {
    archiveName = String.format("%s-%s.jar", applicationName, version)
    mergeServiceFiles()
    exclude 'META-INF/*.SF'
    exclude 'META-INF/*.DSA'
    exclude 'META-INF/*.RSA'
}

jar {
    manifest {
        attributes(
            'Implementation-Title': applicationName,
            'Implementation-Version': version,
            'Built-By': authorName,
            'Built-Time': new Date(),
            'Main-Class': mainClassName,
            'Class-Path': configurations.compile.collect { it.getName() }.join(' ')
            )
    }
}

11
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
10