Apache Shiroの紹介

1概要

この記事では、多目的なJavaセキュリティー・フレームワークであるhttps://shiro.apache.org/[Apache Shiro]を調べます。

フレームワークは、認証、承認、暗号化、およびセッション管理を提供するため、高度にカスタマイズ可能でモジュール式です。

2依存

Apache Shiroにはhttps://shiro.apache.org/download.html[モジュール]がたくさんあります。

ただし、このチュートリアルでは、 shiro-core アーティファクトのみを使用します。

それを pom.xml に追加しましょう。

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.4.0</version>
</dependency>

Apache Shiroモジュールの最新版はhttps://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22org.apache.shiro%22[on Maven Centralにあります。

3 Security Managerの設定

SecurityManager は、Apache Shiroのフレームワークの中心的な部分です。

アプリケーションは通常、その単一インスタンスを実行しています。

このチュートリアルでは、デスクトップ環境におけるフレームワークを探ります。フレームワークを構成するには、リソースフォルダーに shiro.ini ファイルを次の内容で作成する必要があります。

----[users]user = password, admin
user2 = password2, editor
user3 = password3, author
[roles]admin = **
editor = articles:**
author = articles:compose,articles:save
----

shiro.ini 設定ファイルの [users] セクションは、 SecurityManager によって認識されるユーザー認証情報を定義します。形式は次のとおりです。

principal(username)=パスワード、ロール1、ロール2、…​、ロール

ロールとそれに関連付けられた許可は [roles] セクションで宣言されています。 admin ロールには、アプリケーションのあらゆる部分に対する許可とアクセス権が付与されています。これはワイルドカード (** ) 記号で示されます。

editor ロールには articles に関連付けられたすべての権限がありますが、 author ロールには記事の作成および保存のみできます。

SecurityManager SecurityUtils クラスを設定するために使用されます。

SecurityUtils から、システムと対話している現在のユーザーを取得し、認証および承認操作を実行できます。

IniRealm を使用して shiro.ini ファイルからユーザー定義とロール定義をロードし、それを使用して DefaultSecurityManager オブジェクトを構成します。

IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
SecurityManager securityManager = new DefaultSecurityManager(iniRealm);

SecurityUtils.setSecurityManager(securityManager);
Subject currentUser = SecurityUtils.getSubject();

shiro.ini ファイルに定義されているユーザーの資格情報と役割を認識した SecurityManager ができたので、次にユーザーの認証と承認に進みましょう。

4認証

Apache Shiroの用語では、 Subject はシステムと相互作用するエンティティです。人間、スクリプト、またはRESTクライアントのいずれかです。

SecurityUtils.getSubject() を呼び出すと、現在の Subject のインスタンス、つまり currentUser が返されます。

これで currentUser オブジェクトができたので、提供された資格情報で認証を実行できます。

if (!currentUser.isAuthenticated()) {
  UsernamePasswordToken token
    = new UsernamePasswordToken("user", "password");
  token.setRememberMe(true);
  try {
      currentUser.login(token);
  } catch (UnknownAccountException uae) {
      log.error("Username Not Found!", uae);
  } catch (IncorrectCredentialsException ice) {
      log.error("Invalid Credentials!", ice);
  } catch (LockedAccountException lae) {
      log.error("Your Account is Locked!", lae);
  } catch (AuthenticationException ae) {
      log.error("Unexpected Error!", ae);
  }
}

まず、現在のユーザーがまだ認証されていないかどうかを確認します。

次に、ユーザーのプリンシパル (ユーザー名) と資格情報__(パスワード)を使用して認証トークンを作成します。

次に、トークンでログインしようとします。提供された資格情報が正しい場合は、すべてうまくいくはずです。

異なる場合には異なる例外があります。アプリケーションの要件により適したカスタム例外をスローすることも可能です。これは、 AccountException クラスをサブクラス化することによって実行できます。

5承認

認証はシステム内の特定のリソースへのアクセスを制御しようとしている間、認証はユーザーのIDを検証しようとしています。

shiro.ini ファイルで作成した各ユーザーに1つ以上の役割を割り当てます。さらに、ロールセクションでは、ロールごとに異なる権限またはアクセスレベルを定義します。

それでは、アプリケーションでこれを使用してユーザーアクセス制御を強化する方法を見てみましょう。

shiro.ini ファイルでは、システムのすべての部分へのアクセス権を管理者に与えています。

編集者は articles に関するすべてのリソース/操作に完全にアクセスでき、作者は articles のみを作成して保存することに制限されます。

役割に基づいて現在のユーザーを歓迎しましょう。

if (currentUser.hasRole("admin")) {
    log.info("Welcome Admin");
} else if(currentUser.hasRole("editor")) {
    log.info("Welcome, Editor!");
} else if(currentUser.hasRole("author")) {
    log.info("Welcome, Author");
} else {
    log.info("Welcome, Guest");
}

それでは、現在のユーザーがシステム内でできることを確認しましょう。

if(currentUser.isPermitted("articles:compose")) {
    log.info("You can compose an article");
} else {
    log.info("You are not permitted to compose an article!");
}

if(currentUser.isPermitted("articles:save")) {
    log.info("You can save articles");
} else {
    log.info("You can not save articles");
}

if(currentUser.isPermitted("articles:publish")) {
    log.info("You can publish articles");
} else {
    log.info("You can not publish articles");
}

6. レルム構成

実際のアプリケーションでは、 shiro.ini ファイルからではなくデータベースからユーザーの資格情報を取得する方法が必要です。これが、Realmの概念が効力を発揮するところです。

Apache Shiroの用語では、https://shiro.apache.org/realm.html[Realm]は、認証と承認に必要なユーザー資格情報のストアを指すDAOです。

レルムを作成するには、 Realm インターフェースを実装するだけです。それは面倒です。しかし、フレームワークにはサブクラス化できるデフォルトの実装が付属しています。これらの実装の1つが JdbcRealm です。

JdbcRealm クラスを拡張し、次のメソッドをオーバーライドするカスタムレルム実装を作成します。

JdbcRealm クラスをサブクラス化してレルムを作成しましょう。

public class MyCustomRealm extends JdbcRealm {
   //...
}

簡単にするために、 java.util.Map を使用してデータベースをシミュレートします。

private Map<String, String> credentials = new HashMap<>();
private Map<String, Set<String>> roles = new HashMap<>();
private Map<String, Set<String>> perm = new HashMap<>();

{
    credentials.put("user", "password");
    credentials.put("user2", "password2");
    credentials.put("user3", "password3");

    roles.put("user", new HashSet<>(Arrays.asList("admin")));
    roles.put("user2", new HashSet<>(Arrays.asList("editor")));
    roles.put("user3", new HashSet<>(Arrays.asList("author")));

    perm.put("admin", new HashSet<>(Arrays.asList("** ")));
    perm.put("editor", new HashSet<>(Arrays.asList("articles:** ")));
    perm.put("author",
      new HashSet<>(Arrays.asList("articles:compose",
      "articles:save")));
}

続けて、 doGetAuthenticationInfo() をオーバーライドします。

protected AuthenticationInfo
  doGetAuthenticationInfo(AuthenticationToken token)
  throws AuthenticationException {

    UsernamePasswordToken uToken = (UsernamePasswordToken) token;

    if(uToken.getUsername() == null
      || uToken.getUsername().isEmpty()
      || !credentials.containsKey(uToken.getUsername())) {
          throw new UnknownAccountException("username not found!");
    }

    return new SimpleAuthenticationInfo(
      uToken.getUsername(),
      credentials.get(uToken.getUsername()),
      getName());
}

最初に提供された AuthenticationToken UsernamePasswordToken にキャストします。 uToken から、ユーザー名( uToken.getUsername() )を抽出し、それを使用してデータベースからユーザー資格情報(password)を取得します。

レコードが見つからない場合 - 私たちは UnknownAccountException をスローします。それ以外の場合は、資格情報とユーザー名を使用してメソッドから返される SimpleAuthenticatioInfo オブジェクトを作成します。

ユーザー資格情報がsaltでハッシュされている場合は、関連付けられたsaltと共に SimpleAuthenticationInfo を返す必要があります。

return new SimpleAuthenticationInfo(
  uToken.getUsername(),
  credentials.get(uToken.getUsername()),
  ByteSource.Util.bytes("salt"),
  getName()
);

また、 getRoleNamesForUser() および getPermissions() と同様に、 doGetAuthorizationInfo() もオーバーライドする必要があります。

最後に、カスタムレルムを securityManager に接続しましょう。あとは、上記の IniRealm をカスタムレルムに置き換えて、それを DefaultSecurityManager のコンストラクタに渡すだけです。

Realm realm = new MyCustomRealm();
SecurityManager securityManager = new DefaultSecurityManager(realm);

コードの他の部分はすべて以前と同じです。これで、 securityManager をカスタムレルムで正しく構成するために必要なすべてができました。

問題は、フレームワークが信任状とどのように一致するのかということです。

デフォルトでは、 JdbcRealm SimpleCredentialsMatcher を使用します。これは単に AuthenticationToken AuthenticationInfo の資格を比較することによって同等性をチェックするだけです。

パスワードをハッシュする場合は、代わりに HashedCredentialsMatcher を使用するようフレームワークに通知する必要があります。ハッシュされたパスワードを持つレルムのINI設定はhttps://shiro.apache.org/realm.html#Realm-HashingCredentials[ここ]にあります。

7. ログアウト

ユーザーを認証したので、ログアウトを実装する時が来ました。

これは、単一のメソッドを呼び出すことで簡単に実行できます。これにより、ユーザーセッションが無効になり、ユーザーがログアウトされます。

currentUser.logout();

8セッション管理

フレームワークには当然セッション管理システムが付属しています。 Web環境で使用されている場合、デフォルトの HttpSession 実装になります。

スタンドアロンアプリケーションの場合は、エンタープライズセッション管理システムを使用します。利点は、デスクトップ環境でも、一般的なWeb環境と同じようにセッションオブジェクトを使用できることです。

簡単な例を見て、現在のユーザーのセッションと対話しましょう。

Session session = currentUser.getSession();
session.setAttribute("key", "value");
String value = (String) session.getAttribute("key");
if (value.equals("value")) {
    log.info("Retrieved the correct value![" + value + "]");
}

9 Spring を使用したWebアプリケーションのShiro

これまでのところ、Apache Shiroの基本構造の概要を説明し、それをデスクトップ環境に実装しました。フレームワークをSpring Bootアプリケーションに統合しましょう。

ここでの主な焦点は、Shiroであり、Springアプリケーションではないことに注意してください。単純なサンプルアプリケーションを動かすためにそれを使用するだけです。

9.1. 依存関係

まず、Spring Bootの親の依存関係を pom.xml に追加する必要があります。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.2.RELEASE</version>
</parent>

次に、同じ pom.xml ファイルに次の依存関係を追加する必要があります。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-web-starter</artifactId>
    <version>${apache-shiro-core-version}</version>
</dependency>

9.2. 構成

pom.xml shiro-spring-boot-web-starter 依存関係を追加すると、デフォルトで SecurityManager などのApache Shiroアプリケーションの一部の機能が設定されます。

ただし、 Realm およびShiroセキュリティフィルタを設定する必要があります。上で定義したものと同じカスタムレルムを使用します。

そのため、Spring Bootアプリケーションが実行されるメインクラスに、次の Bean 定義を追加しましょう。

@Bean
public Realm realm() {
    return new MyCustomRealm();
}

@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
    DefaultShiroFilterChainDefinition filter
      = new DefaultShiroFilterChainDefinition();

    filter.addPathDefinition("/secure", "authc");
    filter.addPathDefinition("/** ** ", "anon");

    return filter;
}

ShiroFilterChainDefinition では、 authc フィルタを /secure pathに適用し、Antパターンを使用して他のパスに anon フィルタを適用しました。

authc anon の​​両方のフィルタは、デフォルトではWebアプリケーションに付属しています。他のデフォルトフィルタはhttps://shiro.apache.org/web.html#Web-DefaultFilters[ここ]にあります。

Realm Beanを定義しなかった場合、 ShiroAutoConfiguration は、デフォルトで、 src/main/resources または src/main/resources/META-INFで shiro.ini ファイルを見つけることを想定した IniRealm__実装を提供します。

ShiroFilterChainDefinition Beanを定義しないと、フレームワークはすべてのパスを保護し、ログインURLを login.jsp に設定します。

application.properties に次のエントリを追加して、このデフォルトのログインURLとその他のデフォルトを変更できます。

shiro.loginUrl =/login
shiro.successUrl =/secure
shiro.unauthorizedUrl =/login

authc フィルタが /secure に適用されたので、そのルートへのすべてのリクエストはフォーム認証を必要とします。

9.3. 認証と承認

次のパスマッピングを使用して ShiroSpringController を作成しましょう。

/index /login、/logout 、および /secure.

login() メソッドは、上記のように実際のユーザー認証を実装する場所です。認証が成功すると、ユーザーはセキュリティで保護されたページにリダイレクトされます。

Subject subject = SecurityUtils.getSubject();

if(!subject.isAuthenticated()) {
    UsernamePasswordToken token = new UsernamePasswordToken(
      cred.getUsername(), cred.getPassword(), cred.isRememberMe());
    try {
        subject.login(token);
    } catch (AuthenticationException ae) {
        ae.printStackTrace();
        attr.addFlashAttribute("error", "Invalid Credentials");
        return "redirect:/login";
    }
}

return "redirect:/secure";

そして secure() 実装では、 SecurityUtils.getSubject()を呼び出すことによって currentUser__が取得されました。

Subject currentUser = SecurityUtils.getSubject();
String role = "", permission = "";

if(currentUser.hasRole("admin")) {
    role = role  + "You are an Admin";
} else if(currentUser.hasRole("editor")) {
    role = role + "You are an Editor";
} else if(currentUser.hasRole("author")) {
    role = role + "You are an Author";
}

if(currentUser.isPermitted("articles:compose")) {
    permission = permission + "You can compose an article, ";
} else {
    permission = permission + "You are not permitted to compose an article!, ";
}

if(currentUser.isPermitted("articles:save")) {
    permission = permission + "You can save articles, ";
} else {
    permission = permission + "\nYou can not save articles, ";
}

if(currentUser.isPermitted("articles:publish")) {
    permission = permission  + "\nYou can publish articles";
} else {
    permission = permission + "\nYou can not publish articles";
}

modelMap.addAttribute("username", currentUser.getPrincipal());
modelMap.addAttribute("permission", permission);
modelMap.addAttribute("role", role);

return "secure";

これで、Apache ShiroをSpring Bootアプリケーションに統合することができます。

また、フレームワークには追加のhttps://shiro.apache.org/spring.html[注釈]があり、これを使用してアプリケーションを保護することができます。

10. JEEとの統合

Apache ShiroをJEEアプリケーションに統合することは、 web.xml ファイルを設定することだけの問題です。いつものように、設定は shiro.ini がクラスパスにあることを想定しています。詳細な設定例はhttps://shiro.apache.org/web.html#Web-%7B%7Bweb.xml%7D%7D[here]にあります。また、JSPタグはhttps://shiro.apache.org/web.html#Web-JSP%2FGSPTagLibrary[ここ]にあります。

11結論

このチュートリアルでは、Apache Shiroの認証および承認メカニズムを調べました。また、カスタムレルムを定義して SecurityManager にプラグインする方法にも注目しました。

いつものように、完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/apache-shiro[over on GitHub]から入手可能です。

前の投稿:JAXBガイド
次の投稿:文字列から文字列への変換