Spring Security - 役割と特権

Spring Security –ロールと特権

1. 概要

この記事では、Roles and Privilegesを適切に実装する方法を見て、SpringSecurityシリーズへの登録を続けます。

参考文献:

Spring Security Expressionsの概要

Spring Security Expressionsのシンプルで実用的なガイド。

Spring Method Securityの概要

Spring Securityフレームワークを使用したメソッドレベルセキュリティのガイド。

Spring Security –ログイン後に以前のURLにリダイレクトする

Spring Securityでのログイン後のリダイレクトの簡単な例

2. UserRoleおよびPrivilege

まず、エンティティから始めましょう。 3つの主要なエンティティがあります。

  • User

  • Role –これはシステム内のユーザーの高レベルの役割を表します。各役割には、一連の低レベルの特権があります

  • Privilege –システム内の低レベルの詳細な特権/権限を表します

the userは次のとおりです。

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String firstName;
    private String lastName;
    private String email;
    private String password;
    private boolean enabled;
    private boolean tokenExpired;

    @ManyToMany
    @JoinTable(
        name = "users_roles",
        joinColumns = @JoinColumn(
          name = "user_id", referencedColumnName = "id"),
        inverseJoinColumns = @JoinColumn(
          name = "role_id", referencedColumnName = "id"))
    private Collection roles;
}

ご覧のとおり、ユーザーにはロールが含まれていますが、適切な登録メカニズムに必要な追加の詳細も含まれています。

次へ–ここにthe roleがあります:

@Entity
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;
    @ManyToMany(mappedBy = "roles")
    private Collection users;

    @ManyToMany
    @JoinTable(
        name = "roles_privileges",
        joinColumns = @JoinColumn(
          name = "role_id", referencedColumnName = "id"),
        inverseJoinColumns = @JoinColumn(
          name = "privilege_id", referencedColumnName = "id"))
    private Collection privileges;
}

そして最後にthe privilege

@Entity
public class Privilege {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "privileges")
    private Collection roles;
}

ご覧のとおり、ユーザー<→役割と役割<→特権の関係many-to-many bidirectionalの両方を検討しています。

3. 特権と役割の設定

次へ–システムでの特権と役割の初期設定に焦点を当てましょう。

これをアプリケーションの起動に関連付け、ContextRefreshedEventApplicationListenerを使用して、サーバーの起動時に初期データを読み込みます。

@Component
public class InitialDataLoader implements
  ApplicationListener {

    boolean alreadySetup = false;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private RoleRepository roleRepository;

    @Autowired
    private PrivilegeRepository privilegeRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    @Transactional
    public void onApplicationEvent(ContextRefreshedEvent event) {

        if (alreadySetup)
            return;
        Privilege readPrivilege
          = createPrivilegeIfNotFound("READ_PRIVILEGE");
        Privilege writePrivilege
          = createPrivilegeIfNotFound("WRITE_PRIVILEGE");

        List adminPrivileges = Arrays.asList(
          readPrivilege, writePrivilege);
        createRoleIfNotFound("ROLE_ADMIN", adminPrivileges);
        createRoleIfNotFound("ROLE_USER", Arrays.asList(readPrivilege));

        Role adminRole = roleRepository.findByName("ROLE_ADMIN");
        User user = new User();
        user.setFirstName("Test");
        user.setLastName("Test");
        user.setPassword(passwordEncoder.encode("test"));
        user.setEmail("[email protected]");
        user.setRoles(Arrays.asList(adminRole));
        user.setEnabled(true);
        userRepository.save(user);

        alreadySetup = true;
    }

    @Transactional
    private Privilege createPrivilegeIfNotFound(String name) {

        Privilege privilege = privilegeRepository.findByName(name);
        if (privilege == null) {
            privilege = new Privilege(name);
            privilegeRepository.save(privilege);
        }
        return privilege;
    }

    @Transactional
    private Role createRoleIfNotFound(
      String name, Collection privileges) {

        Role role = roleRepository.findByName(name);
        if (role == null) {
            role = new Role(name);
            role.setPrivileges(privileges);
            roleRepository.save(role);
        }
        return role;
    }
}

では、この単純なセットアップコードの間に何が起こっているのでしょうか。 複雑なことはありません:

  • 特権を作成しています

  • 役割を作成し、それらに特権を割り当てています

  • ユーザーを作成し、それに役割を割り当てています

alreadySetupフラグをdetermine if the setup needs to run or notに使用していることに注意してください。 これは、アプリケーションで構成したコンテキストの数によっては、ContextRefreshedEventが複数回起動される可能性があるためです。 そして、セットアップを一度だけ実行したいのです。

ここに2つの簡単なメモ–first, about terminology。 ここではPrivilege – Roleの用語を使用していますが、Springではこれらは少し異なります。 Springでは、特権はロールと呼ばれ、(付与された)権限とも呼ばれます。 もちろん実装に問題はありませんが、間違いなく注目に値します。

Second – these Spring Roles (our Privileges) need a prefix;デフォルトでは、そのプレフィックスは「ROLE」ですが、変更することができます。 ここでは、簡単にするためにそのプレフィックスを使用していませんが、明示的に変更しない場合は必須になることに注意してください。

4. カスタムUserDetailsService

それでは、認証プロセスを確認しましょう。

カスタムUserDetailsService内でユーザーを取得する方法と、ユーザーが割り当てた役割と特権から適切な権限のセットをマップする方法を見ていきます。

@Service("userDetailsService")
@Transactional
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private IUserService service;

    @Autowired
    private MessageSource messages;

    @Autowired
    private RoleRepository roleRepository;

    @Override
    public UserDetails loadUserByUsername(String email)
      throws UsernameNotFoundException {

        User user = userRepository.findByEmail(email);
        if (user == null) {
            return new org.springframework.security.core.userdetails.User(
              " ", " ", true, true, true, true,
              getAuthorities(Arrays.asList(
                roleRepository.findByName("ROLE_USER"))));
        }

        return new org.springframework.security.core.userdetails.User(
          user.getEmail(), user.getPassword(), user.isEnabled(), true, true,
          true, getAuthorities(user.getRoles()));
    }

    private Collection getAuthorities(
      Collection roles) {

        return getGrantedAuthorities(getPrivileges(roles));
    }

    private List getPrivileges(Collection roles) {

        List privileges = new ArrayList<>();
        List collection = new ArrayList<>();
        for (Role role : roles) {
            collection.addAll(role.getPrivileges());
        }
        for (Privilege item : collection) {
            privileges.add(item.getName());
        }
        return privileges;
    }

    private List getGrantedAuthorities(List privileges) {
        List authorities = new ArrayList<>();
        for (String privilege : privileges) {
            authorities.add(new SimpleGrantedAuthority(privilege));
        }
        return authorities;
    }
}

ここで興味深いのは、権限(およびロール)がどのようにGrantedAuthorityエンティティにマップされるかです。

このマッピングにより、セキュリティ構成全体がhighly flexible and powerfulになります。役割と特権を必要に応じて細かく組み合わせて一致させることができ、最終的に、それらは権限に正しくマッピングされ、フレームワークに戻されます。

5. User登録

最後に、新しいユーザーの登録を見てみましょう。

セットアップでユーザーを作成し、それにロール(および特権)を割り当てる方法を確認しました。次に、新しいユーザーの登録時にこれを行う必要がある方法を見てみましょう。

@Override
public User registerNewUserAccount(UserDto accountDto) throws EmailExistsException {

    if (emailExist(accountDto.getEmail())) {
        throw new EmailExistsException
          ("There is an account with that email adress: " + accountDto.getEmail());
    }
    User user = new User();

    user.setFirstName(accountDto.getFirstName());
    user.setLastName(accountDto.getLastName());
    user.setPassword(passwordEncoder.encode(accountDto.getPassword()));
    user.setEmail(accountDto.getEmail());

    user.setRoles(Arrays.asList(roleRepository.findByName("ROLE_USER")));
    return repository.save(user);
}

この単純な実装では、標準ユーザーが登録されていると想定しているため、ROLE_USERロールが割り当てられています。

もちろん、より複雑なロジックは、同じ方法で簡単に実装できます。ハードコードされた複数の登録方法を使用するか、クライアントが登録されているユーザーのタイプを送信できるようにします。

6. 結論

このチュートリアルでは、Spring Securityがサポートするシステムに対して、JPAでロールと特権を実装する方法を説明しました。

このSpringSecurityへの登録チュートリアルのfull implementationは、the GitHub projectにあります。これはMavenベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。