アクセス セキュリティはかなり長い間 Java に実装されており、このセキュリティを提供するためのアーキテクチャは JAAS (Java Authentication and Authorization Service) と呼ばれています。このレビューでは、認証、認可とは何か、そして JAAS がそれとどのような関係にあるのかという謎を解明しようとします。JAAS はサーブレット API とどのような関係にあるのか、またその関係においてどこに問題があるのか。
つまり、プラグインは、Web アプリケーションの WAR アーカイブを構築するときに、この場所のファイルを考慮します。さらに、Gradle War Plugin のドキュメントには、このディレクトリが「アーカイブのルート」になると記載されています。そして、その中にすでに WEB-INF ディレクトリを作成し、そこに web.xml ファイルを追加できます。これはどのようなファイルですか?
サーブレット コンテナが Tomcat によって選択されていることを確認し (#1 を参照)、Web アプリケーションがどのアドレスで利用可能であるかを確認する (#2 を参照) ことが重要です。
つまり、JAAS Login Config 設定を Web アプリケーションのリソースに配置することができ、Tomcat Context のおかげでそれを使用できるようになります。このファイルは ClassLoader のリソースとして使用できる必要があるため、そのパスは次のようになります。
導入
このレビューでは、Web アプリケーションのセキュリティなどのトピックについて説明したいと思います。Java には、セキュリティを提供するいくつかのテクノロジがあります。-
「Java SE プラットフォーム セキュリティ アーキテクチャ」。詳細については、Oracle のガイド「JavaTM SE プラットフォーム セキュリティ アーキテクチャ」を参照してください。このアーキテクチャは、Java SE ランタイム環境で Java アプリケーションを保護する方法を説明します。しかし、これは今日の会話のテーマではありません。
-
「Java Cryptography Architecture」は、データ暗号化を記述する Java 拡張機能です。この拡張機能の詳細については、JavaRush のレビュー「Java 暗号化アーキテクチャ: 最初の知人」または Oracle のガイド「Java 暗号化アーキテクチャ (JCA) リファレンス ガイド」を参照してください。
JAAS
JAASは Java SE の拡張機能であり、「Java Authentication and Authorization Service (JAAS) Reference Guide」で説明されています。テクノロジーの名前が示すように、JAAS では、認証と認可がどのように実行されるべきかを説明しています。-
「Authentication」: ギリシャ語から翻訳された「authentikos」は「本物の、本物の」を意味します。つまり、認証は信頼性のテストです。認証されている人が本当に本人であること。
-
「Authorization」:英語から翻訳すると「許可」を意味します。つまり、認可とは、認証が成功した後に実行されるアクセス制御です。
- 道路利用者としての個人を表すものとしての彼の運転免許証
- 彼の国の国民としての代表としての彼のパスポート
- 国際関係の参加者としての個人の代表としての彼の外国パスポート
- 図書館にある彼の図書館カード、図書館に付属する読者としての人物の表現として
ウェブアプリケーション
したがって、Web アプリケーションが必要です。Gradle 自動プロジェクト ビルド システムは、プロジェクトの作成に役立ちます。Gradle を使用することで、小さなコマンドを実行することで、必要な形式で Java プロジェクトを組み立てたり、必要なディレクトリ構造を自動的に作成したりすることができます。Gradle の詳細については、短い概要「Gradle の簡単な紹介」または公式ドキュメント「Gradle Getting Started」を参照してください。プロジェクトを初期化する必要があります (Initialization)。この目的のために、Gradle には特別なプラグイン「Gradle Init Plugin」があります (Init は Initialization の略で、覚えやすいです)。このプラグインを使用するには、コマンド ラインで次のコマンドを実行します。gradle init --type java-application
正常に完了すると、Java プロジェクトが作成されます。次に、プロジェクトのビルド スクリプトを開いて編集しましょう。ビルド スクリプトは と呼ばれるファイルでbuild.gradle
、アプリケーションのビルドの微妙な違いが記述されています。したがって、ビルド スクリプトという名前が付けられました。これはプロジェクトのビルド スクリプトであると言えます。Gradle は非常に多用途なツールであり、その基本機能はプラグインによって拡張されます。したがって、まず「プラグイン」ブロックに注目してみましょう。
plugins {
id 'java'
id 'application'
}
デフォルトでは、Gradle は、「 」で指定した内容に従って--type java-application
、いくつかのコア プラグイン、つまり Gradle 自体のディストリビューションに含まれるプラグインのセットをセットアップします。gradle.org Web サイトの「Docs」(つまり、ドキュメント)セクションに移動すると、「Reference」セクションのトピックのリストの左側に「 Core Plugins」セクションが表示されます。これらの非常に基本的なプラグインについて説明するセクション。Gradle が生成したプラグインではなく、必要なプラグインを正確に選択しましょう。ドキュメントによると、「Gradle Java Plugin」は、ソース コードのコンパイルなど、Java コードの基本的な操作を提供します。また、ドキュメントによると、「Gradle アプリケーション プラグイン」は、「実行可能な JVM アプリケーション」を操作するためのツールを提供します。スタンドアロン アプリケーション (コンソール アプリケーションや独自の UI を持つアプリケーションなど) として起動できる Java アプリケーションを使用します。「アプリケーション」プラグインは必要ないことがわかりました。なぜなら... スタンドアロン アプリは必要ありません。Web アプリが必要です。削除しましょう。「mainClassName」設定と同様に、このプラグインのみが認識します。さらに、アプリケーション プラグインのドキュメントへのリンクが提供されている同じ「パッケージ化と配布」セクションには、Gradle War プラグインへのリンクがあります。 ドキュメントに記載されているように、Gradle War Plugin は、war 形式で Java Web アプリケーションを作成するためのサポートを提供します。WAR 形式とは、JAR アーカイブの代わりに WAR アーカイブが作成されることを意味します。これが私たちに必要なもののようです。また、ドキュメントに記載されているように、「War プラグインは Java プラグインを拡張します」。つまり、Java プラグインを war プラグインに置き換えることができます。したがって、プラグイン ブロックは最終的に次のようになります。
plugins {
id 'war'
}
また、「Gradle War プラグイン」のドキュメントには、プラグインが追加の「プロジェクト レイアウト」を使用すると記載されています。レイアウトは英語から location と訳されます。つまり、war プラグインはデフォルトで、タスクに使用するファイルの特定の場所が存在することを期待します。Web アプリケーション ファイルを保存するために次のディレクトリを使用します。src/main/webapp
プラグインの動作は次のように説明されます。
web.xml
- これは「デプロイメント記述子」または「デプロイメント記述子」です。これは、Web アプリケーションが動作するように構成する方法を説明するファイルです。このファイルは、アプリケーションが処理するリクエスト、セキュリティ設定などを指定します。その核心部分は、JAR ファイルからのマニフェスト ファイルに似ています (「マニフェスト ファイルの操作: 基本」を参照)。マニフェスト ファイルは Java アプリケーション (つまり、JAR アーカイブ) の操作方法を示し、web.xml は Java Web アプリケーション (つまり、WAR アーカイブ) の操作方法を示します。「デプロイメント記述子」という概念自体はそれ自体で生まれたものではなく、「サーブレット API 仕様」という文書で説明されています。"。すべての Java Web アプリケーションは、この「サーブレット API」に依存します。これは API であること、つまり、何らかの対話コントラクトの記述であることを理解することが重要です。Web アプリケーションは独立したアプリケーションではありません。Web サーバー上で実行されます。 、ユーザーとのネットワーク通信を提供します。つまり、Web サーバーは Web アプリケーションの一種の「コンテナ」です。Web アプリケーションのロジック、つまりユーザーにどのページがどのように表示されるかを書きたいので、これは論理的です。ユーザーのアクションに反応する必要があります。そして、メッセージがユーザーにどのように送信されるか、情報のバイト数がどのように転送されるか、その他の低レベルで非常に品質が要求されるものについてコードを書きたくありません。 Web アプリケーションはすべて異なることがわかりましたが、データ転送は同じです。つまり、100 万人のプログラマーが同じ目的でコードを何度も何度も書かなければならないことになります。そのため、Web サーバーはユーザー インタラクションの一部を担当します。およびデータ交換が行われ、Web アプリケーションと開発者はそのデータの生成を担当します。そして、これら 2 つの部分を接続するには、つまり Web サーバーと Web アプリケーションの場合、それらの対話のためのコントラクトが必要です。彼らはこれを行うためにどのようなルールに従うのでしょうか?Web アプリケーションと Web サーバー間の対話がどのようなものであるべきかというコントラクトを何らかの形で記述するために、サーブレット API が発明されました。興味深いことに、Spring のようなフレームワークを使用している場合でも、内部では依然としてサーブレット API が実行されています。つまり、Spring を使用すると、Spring はサーブレット API と連携して動作します。Web アプリケーション プロジェクトはサーブレット API に依存する必要があることがわかりました。この場合、サーブレット API は依存関係になります。ご存知のとおり、Gradle では、宣言的な方法でプロジェクトの依存関係を記述することもできます。プラグインは依存関係を管理する方法を記述します。たとえば、Java Gradle プラグインは、「testImplementation」依存関係管理メソッドを導入しています。これは、そのような依存関係がテストにのみ必要であることを示します。しかし、Gradle War プラグインは依存関係管理メソッド「providedCompile」を追加します。これは、そのような依存関係が Web アプリケーションの WAR アーカイブに含まれないことを示します。WAR アーカイブにサーブレット API を含めてみてはいかがでしょうか? サーブレット API は Web サーバー自体によって Web アプリケーションに提供されるためです。Web サーバーがサーブレット API を提供する場合、そのサーバーはサーブレット コンテナーと呼ばれます。したがって、サーブレット API を提供するのは Web サーバーの責任であり、コードのコンパイル時にのみサーブレット API を提供するのは私たちの責任です。それが理由ですprovidedCompile
。したがって、依存関係ブロックは次のようになります。
dependencies {
providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
testImplementation 'junit:junit:4.12'
}
それでは、web.xml ファイルに戻りましょう。デフォルトでは、Gradle はデプロイメント記述子を作成しないため、これを自分で行う必要があります。ディレクトリ を作成しsrc/main/webapp/WEB-INF
、その中に という XML ファイルを作成しますweb.xml
。ここで、「Java Servlet 仕様」自体と「第 14 章 デプロイメント記述子」の章を開いてみましょう。「14.3 Deployment Descriptor」で述べたように、Deployment DescriptorのXML文書はスキーマhttp://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsdで記述されます。XML スキーマは、ドキュメントがどのような要素で構成され、どのような順序で表示されるべきかを記述します。どれが必須でどれがそうではないのか。一般に、これはドキュメントの構造を記述し、XML ドキュメントが正しく構成されているかどうかを確認できるようにします。ここで、「 14.5 例」の章の例を使用してみましょう。ただし、スキームはバージョン 3.1 用に指定する必要があります。
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd
空のものはweb.xml
次のようになります。
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>JAAS Example</display-name>
</web-app>
次に、JAAS を使用して保護するサーブレットについて説明しましょう。以前は、Gradle が App クラスを生成していました。これをサーブレットにしてみましょう。「第 2 章 サーブレット インターフェイス」の仕様に記載されているように、「ほとんどの目的で、開発者は HttpServlet を拡張してサーブレットを実装します」。つまり、クラスをサーブレットにするには、このクラスを次から継承する必要がありますHttpServlet
。
public class App extends HttpServlet {
public String getGreeting() {
return "Secret!";
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().print(getGreeting());
}
}
前述したように、サーブレット API はサーバーと Web アプリケーション間の契約です。この規約により、ユーザーがサーバーに接続すると、サーバーはユーザーからのリクエストをオブジェクトの形式で生成しHttpServletRequest
、それをサーブレットに渡すことを記述することができます。また、サーブレットにオブジェクトを提供して、HttpServletResponse
サーブレットがユーザーに応答を書き込めるようにします。サーブレットの実行が完了すると、サーバーはそれに基づいた応答をユーザーに提供できるようになりますHttpServletResponse
。つまり、サーブレットはユーザーと直接通信せず、サーバーとのみ通信します。サーブレットがあることと、それをどのようなリクエストに使用する必要があるかをサーバーに認識させるには、デプロイメント記述子でこれについてサーバーに伝える必要があります。
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>jaas.App</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/secret</url-pattern>
</servlet-mapping>
この場合、すべてのリクエストは、 class に対応する/secret
name によって 1 つのサーブレットにアドレス指定されるわけではありません。前に述べたように、Web アプリケーションは Web サーバー上にのみデプロイできます。Web サーバーは個別に (スタンドアロンで) インストールできます。ただし、このレビューの目的には、埋め込みサーバーで実行する代替オプションが適しています。これは、サーバーがプログラムによって作成および起動され (プラグインがこれを実行します)、同時に Web アプリケーションがサーバー上にデプロイされることを意味します。Gradle ビルド システムでは、次の目的で 「 Gradle Gretty Plugin 」プラグインを使用できます。app
jaas.App
plugins {
id 'war'
id 'org.gretty' version '2.2.0'
}
さらに、Gretty プラグインには優れたドキュメントがあります。まず、Gretty プラグインを使用すると、異なる Web サーバー間を切り替えることができるという事実から始めましょう。これについては、ドキュメント「サーブレット コンテナ間の切り替え」で詳しく説明されています。Tomcat に切り替えましょう。なぜなら... これは最もよく使用されているものの 1 つであり、優れたドキュメントと多くの例と分析された問題も備えています。
gretty {
// Переключаемся с дефолтного Jetty на Tomcat
servletContainer = 'tomcat8'
// Укажем Context Path, он же Context Root
contextPath = '/jaas'
}
これで「gradle appRun」を実行できるようになり、Web アプリケーションが http://localhost:8080/jaas/secret で利用できるようになります。
認証
認証設定は多くの場合、サーバー側の設定と、このサーバー上で実行される Web アプリケーション側の設定の 2 つの部分で構成されます。Web アプリケーションのセキュリティ設定は、Web アプリケーションが Web サーバーと対話せざるをえないという理由以外の理由がない限り、Web サーバーのセキュリティ設定と対話せざるを得ません。Tomcat に切り替えたのは無駄ではありませんでした。Tomcat には明確に説明されたアーキテクチャがあります (「Apache Tomcat 8 アーキテクチャ」を参照)。このアーキテクチャの説明から、Web サーバーとしての Tomcat が Web アプリケーションを「Tomcat コンテキスト」と呼ばれる特定のコンテキストとして表すことは明らかです。このコンテキストにより、各 Web アプリケーションは他の Web アプリケーションから分離された独自の設定を持つことができます。さらに、Web アプリケーションはこのコンテキストの設定に影響を与える可能性があります。柔軟で便利です。さらに深く理解するには、記事「Tomcat コンテキスト コンテナについて」および Tomcat ドキュメントのセクション「コンテキスト コンテナ」を読むことをお勧めします。上で述べたように、Web アプリケーションは、 を使用してアプリケーションの Tomcat コンテキストに影響を与えることができます/META-INF/context.xml
。私たちが影響を与えることができる非常に重要な設定の 1 つは、セキュリティ レルムです。 Security Realms は、一種の「セキュリティ エリア」です。特定のセキュリティ設定が指定されている領域。したがって、セキュリティ レルムを使用する場合、このレルムに定義されたセキュリティ設定が適用されます。セキュリティ レルムはコンテナによって管理されます。Web アプリケーションではなく、Web サーバーです。どのセキュリティ スコープをアプリケーションに拡張する必要があるかをサーバーに伝えることしかできません。Tomcat ドキュメントの「レルム コンポーネント」セクションでは、認証を実行するためのユーザーとその役割に関するデータのコレクションとしてレルムについて説明しています。Tomcat は、さまざまなセキュリティ レルム実装のセットを提供しており、その 1 つが「Jaas Realm」です。用語を少し理解したら、ファイル内の Tomcat コンテキストを説明しましょう/META-INF/context.xml
。
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Realm className="org.apache.catalina.realm.JAASRealm"
appName="JaasLogin"
userClassNames="jaas.login.UserPrincipal"
roleClassNames="jaas.login.RolePrincipal"
configFile="jaas.config" />
</Context>
appName
- アプリケーション名。Tomcat は、この名前を で指定された名前と照合しようとしますconfigFile
。 configFile
- これは「ログイン設定ファイル」です。この例は、JAAS ドキュメント「付録 B: ログイン構成の例」で参照できます。さらに、このファイルがリソース内で最初に検索されることが重要です。したがって、Web アプリケーション自体がこのファイルを提供できます。属性userClassNames
には、roleClassNames
ユーザーのプリンシパルを表すクラスの指示が含まれます。JAAS では、「ユーザー」と「ロール」の概念を 2 つの異なる概念として分離しますjava.security.Principal
。上記のクラスについて説明しましょう。ユーザー プリンシパルの最も単純な実装を作成してみましょう。
public class UserPrincipal implements Principal {
private String name;
public UserPrincipal(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
}
についてもまったく同じ実装を繰り返しますRolePrincipal
。インターフェイスからわかるように、プリンシパルの主なことは、プリンシパルを表す名前 (または ID) を保存して返すことです。これで、セキュリティ レルムとプリンシパル クラスができました。ファイルを " configFile
" 属性 (別名 )から埋める作業が残りますlogin configuration file
。その説明は、Tomcat ドキュメントの「 The Realm Component 」にあります。
\src\main\resources\jaas.config
このファイルの内容を設定しましょう。
JaasLogin {
jaas.login.JaasLoginModule required debug=true;
};
context.xml
こことで同じ名前が使用されている ことに注意してください。これにより、セキュリティ レルムが LoginModule にマップされます。したがって、Tomcat Context は、どのクラスがプリンシパルを表すか、またどの LoginModule を使用するかを教えてくれました。私たちがしなければならないのは、この LoginModule を実装することだけです。 LoginModule は、おそらく JAAS で最も興味深いものの 1 つです。LoginModule の開発には、公式ドキュメント「 Java Authentication and Authorization Service (JAAS): LoginModule Developer's Guide 」が役立ちます。ログインモジュールを実装してみましょう。インターフェースを実装するクラスを作成しましょうLoginModule
。
public class JaasLoginModule implements LoginModule {
}
まず初期化メソッドについて説明しますLoginModule
。
private CallbackHandler handler;
private Subject subject;
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, <String, ?> sharedState, Map<String, ?> options) {
handler = callbackHandler;
this.subject = subject;
}
このメソッドは を保存しSubject
、さらに認証してプリンシパルに関する情報を入力します。また、私たちに与えられたものを将来の使用のために保存しますCallbackHandler
。助けを借りて、CallbackHandler
少し後で認証対象に関するさまざまな情報を要求できます。詳細についてはCallbackHandler
、ドキュメントの対応するセクション「JAAS リファレンス ガイド: CallbackHandler」を参照してください。login
次に、認証のためのメソッドが実行されますSubject
。これは認証の最初のフェーズです。
@Override
public boolean login() throws LoginException {
// Добавляем колбэки
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("login");
callbacks[1] = new PasswordCallback("password", true);
// При помощи колбэков получаем через CallbackHandler логин и пароль
try {
handler.handle(callbacks);
String name = ((NameCallback) callbacks[0]).getName();
String password = String.valueOf(((PasswordCallback) callbacks[1]).getPassword());
// Далее выполняем валидацию.
// Тут просто для примера проверяем определённые значения
if (name != null && name.equals("user123") && password != null && password.equals("pass123")) {
// Сохраняем информацию, которая будет использована в методе commit
// Не "пачкаем" Subject, т.к. не факт, что commit выполнится
// Для примера проставим группы вручную, "хардcodeно".
login = name;
userGroups = new ArrayList<String>();
userGroups.add("admin");
return true;
} else {
throw new LoginException("Authentication failed");
}
} catch (IOException | UnsupportedCallbackException e) {
throw new LoginException(e.getMessage());
}
}
login
を変更しないこと が重要ですSubject
。このような変更は、確認方法でのみ発生する必要がありますcommit
。次に、認証が成功したことを確認する方法を説明する必要があります。
@Override
public boolean commit() throws LoginException {
userPrincipal = new UserPrincipal(login);
subject.getPrincipals().add(userPrincipal);
if (userGroups != null && userGroups.size() > 0) {
for (String groupName : userGroups) {
rolePrincipal = new RolePrincipal(groupName);
subject.getPrincipals().add(rolePrincipal);
}
}
return true;
}
メソッドlogin
と を分離するのは奇妙に思えるかもしれませんcommit
。ただし、重要なのは、ログイン モジュールを組み合わせることができるということです。また、認証を成功させるには、いくつかのログイン モジュールが正常に動作する必要がある場合があります。必要なモジュールがすべて機能した場合にのみ、変更を保存します。これは認証の第 2 フェーズです。abort
およびメソッドで終了しましょうlogout
。
@Override
public boolean abort() throws LoginException {
return false;
}
@Override
public boolean logout() throws LoginException {
subject.getPrincipals().remove(userPrincipal);
subject.getPrincipals().remove(rolePrincipal);
return true;
}
このメソッドは、abort
認証の最初のフェーズが失敗したときに呼び出されます。このメソッドは、logout
システムがログアウトするときに呼び出されます。Login Module
私たちのものを実装して構成したら、次は特定のものを使用することをSecurity Realm
示す必要があります。 web.xml
Login Config
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>JaasLogin</realm-name>
</login-config>
セキュリティ レルムの名前を指定し、認証方法として BASIC を指定しました。これは、「 13.6 認証」セクションのサーブレット API で説明されている認証のタイプの 1 つです。残りn
GO TO FULL VERSION