跳到主要内容

授权和会话管理

在这个例子中,我们将创建一个简单的授权应用,包括 登录/注册 场景 和会话管理。

ActiveJ不包括内置的授权模块或解决方案,因为这个过程可能会有很大的不同,取决于 项目的业务逻辑。 这个例子代表了一个简单的 "最佳实践",你 ,可以根据你的需要进行扩展和修改。 你可以在以下网站找到完整的例子来源 GitHub

在这个例子中,我们将只考虑使用ActiveJ HttpServerLauncherAsyncServlet创建的 服务器。 这种方法允许在大约100行的代码中创建一个嵌入式应用服务器,没有 额外的XML配置或第三方依赖。

创建启动器

让我们创建一个 AuthLauncher,它是应用程序的主要部分,因为它管理着应用程序的生命周期、路由 和授权过程。 我们将使用ActiveJ HttpServerLauncher 并扩展它。

public final class AuthLauncher extends HttpServerLauncher {  public static final String SESSION_ID = "SESSION_ID";
  @Provides  AuthService loginService() {    return new AuthServiceImpl();  }
  @Provides  Executor executor() {    return newSingleThreadExecutor();  }
  @Provides  StaticLoader staticLoader(Executor executor) {    return StaticLoader.ofClassPath(executor, "site/");  }
  @Provides  SessionStore<String> sessionStore() {    return SessionStoreInMemory.<String>create()        .withLifetime(Duration.ofDays(30));  }
  @Provides  AsyncServlet servlet(SessionStore<String> sessionStore,      @Named("public") AsyncServlet publicServlet, @Named("private") AsyncServlet privateServlet) {    return SessionServlet.create(sessionStore, SESSION_ID, publicServlet, privateServlet);  }

让我们更详细地探讨所提供的对象: - AuthService - 授权和注册逻辑

  • Executor - StaticLoader需要。
  • StaticLoader - 从 /site 目录中加载静态内容。
  • SessionStore - 便于存储有关会议的信息
  • AsyncServlet servlet - 结合公共和私人servlet的主要servlet(用于授权和未授权会话)。 正如你所看到的,由于DI,这个servlet只需要两个没有自己的依赖性的servlet 现在让我们提供 publicprivate servlets。 AsyncServlet publicServlet* - 管理未经授权的会话。
@Provides@Named("public")AsyncServlet publicServlet(AuthService authService, SessionStore<String> store, StaticLoader staticLoader) {  StaticServlet staticServlet = StaticServlet.create(staticLoader, "errorPage.html");  return RoutingServlet.create()      //[START REGION_3]      .map("/", request -> HttpResponse.redirect302("/login"))      //[END REGION_3]      .map(GET, "/signup", StaticServlet.create(staticLoader, "signup.html"))      .map(GET, "/login", StaticServlet.create(staticLoader, "login.html"))      //[START REGION_4]      .map(POST, "/login", request -> request.loadBody()          .then(() -> {            Map<String, String> params = request.getPostParameters();            String username = params.get("username");            String password = params.get("password");            if (authService.authorize(username, password)) {              String sessionId = UUID.randomUUID().toString();
              return store.save(sessionId, "My object saved in session")                  .map($ -> HttpResponse.redirect302("/members")                      .withCookie(HttpCookie.of(SESSION_ID, sessionId)));            }            return staticServlet.serve(request);          }))      //[END REGION_4]      .map(POST, "/signup", request -> request.loadBody()          .map($ -> {            Map<String, String> params = request.getPostParameters();            String username = params.get("username");            String password = params.get("password");
            if (username != null && password != null) {              authService.register(username, password);            }            return HttpResponse.redirect302("/login");          }));}

让我们仔细看看我们是如何为Servlet设置路由的。 ActiveJ的方法类似于Express。 例如, 这里是未授权用户对主页的请求。

.map("/", request -> HttpResponse.redirect302("/login"))

方法 map(@Nullable HttpMethod method, String path, AsyncServlet servlet) 将路由添加到 呼叫中心

  • 方法 (可选)是HTTP方法之一(GETPOST,等等)。
  • path 是服务器上的路径。
  • servlet 定义了请求处理的逻辑。 如果你需要在处理过程中从 request ,你可以使用一些数据。
  • request.getPathParameter(String key/request. etQueryParameter(密钥) (见查询参数使用示例) 以提供所需参数的密钥并接收相应的字符串
  • request.getPostParameters() 以获得所有请求参数的地图 GET 请求的路径是 "/login""/signup" 上传需要的HTML页面。 POST 请求的路径是 "/login""/signup" ,分别负责登录和注册的逻辑。
.map(POST, "/login", request -> request.loadBody()    .then(() -> {      Map<String, String> params = request.getPostParameters();      String username = params.get("username");      String password = params.get("password");      if (authService.authorize(username, password)) {        String sessionId = UUID.randomUUID().toString();
        return store.save(sessionId, "My object saved in session")            .map($ -> HttpResponse.redirect302("/members")                .withCookie(HttpCookie.of(SESSION_ID, sessionId)));      }      return staticServlet.serve(request);    }))

请注意 POST "/login" 路线。 serveFirstSuccessful() 接收两个servlet并等待,直到其中一个 成功完成处理。 因此,如果授权失败,将返回 null 的承诺(AsyncServlet.NEXT), ,这意味着失败。 在这种情况下,将创建一个简单的 StaticServlet ,以加载 errorPage。 成功登录将为用户生成一个会话 id ,并将字符串 "My saved object in session" 保存到会话存储中。 同时,它将把用户重定向到 "/members"。 现在,让我们来看看下一个处理授权会话的servlet。 AsyncServlet privateServlet* - 管理授权会话。

@Provides@Named("private")AsyncServlet privateServlet(StaticLoader staticLoader) {  return RoutingServlet.create()      //[START REGION_6]      .map("/", request -> HttpResponse.redirect302("/members"))      //[END REGION_6]      //[START REGION_7]      .map("/members/*", RoutingServlet.create()          .map(GET, "/", StaticServlet.create(staticLoader, "index.html"))          //[START REGION_8]          .map(GET, "/cookie", request ->              HttpResponse.ok200().withBody(wrapUtf8(request.getAttachment(String.class))))          //[END REGION_8]          .map(POST, "/logout", request ->              HttpResponse.redirect302("/")                  .withCookie(HttpCookie.of(SESSION_ID).withPath("/").withMaxAge(Duration.ZERO))));  //[END REGION_7]}

首先,它将请求从主页重定向到 "/members"

.map("/", request -> HttpResponse.redirect302("/members"))

接下来,它负责处理所有在 "/members" 路径之后的请求。

.map("/members/*", RoutingServlet.create()    .map(GET, "/", StaticServlet.create(staticLoader, "index.html"))    //[START REGION_8]    .map(GET, "/cookie", request ->        HttpResponse.ok200().withBody(wrapUtf8(request.getAttachment(String.class))))    //[END REGION_8]    .map(POST, "/logout", request ->        HttpResponse.redirect302("/")            .withCookie(HttpCookie.of(SESSION_ID).withPath("/").withMaxAge(Duration.ZERO))));

请注意路径 "/members/*"* 是一个变量,用于路径的下一部分。 它指出,这个Servlet将处理任何在 "/members/"之后的路径段。 例如,这条路线。

.map(GET, "/cookie", request ->    HttpResponse.ok200().withBody(wrapUtf8(request.getAttachment(String.class))))

是对 "/members/cookie" 路径的GET请求。 该请求显示所有存储在会话中的cookies。

caution

一个请求可以有一个附件图,任何内容都可以被映射到某种类型,即 字符串。 默认情况下,请求没有附件。 在这种情况下,请求包含'cookies'作为附件,被映射到 String 类型。 ::: "/members/logout" 将用户注销,删除与此会话相关的所有cookies,并将用户重定向到主页。 在 publicprivate servlets设置完毕后,我们定义 main() 方法,它将启动我们的启动器。

public static void main(String[] args) throws Exception {  AuthLauncher launcher = new AuthLauncher();  launcher.launch(args);}

运行应用程序

如果您想运行该示例, 克隆ActiveJ ,并将其导入 作为Maven项目。 查阅分支机构 v5.4.3。 在运行这个例子之前,构建项目(Ctrl F9 for IntelliJ IDEA)。

打开 AuthLauncher 类并运行其 main() 方法。 然后打开你喜欢的浏览器,进入 localhost:8080。 尝试注册,然后登录。 当 登录时,查看你保存的cookie的会话。 你会看到以下内容: 我在会话中保存的对象。 最后,尝试注销。 你也可以尝试用无效的登录名或密码登录。