1. 概要

このチュートリアルでは、Springセキュリティシリーズへの登録を継続し、役割と特権を適切に実装する方法を説明します。

2. ユーザー役割および特権

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

  • ユーザー
  • Role は、システムにおけるユーザーの高レベルの役割を表します。 各役割には、一連の低レベルの特権があります。
  • 特権は、システム内の低レベルの詳細な特権/権限を表します。

ユーザーは次のとおりです。

@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<Role> roles;
}

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

次に、役割は次のとおりです。

@Entity
public class Role {
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

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

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

最後に、特権を見てみましょう。

@Entity
public class Privilege {
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

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

ご覧のとおり、ユーザー<->ロールとロール<->特権の関係の両方を考慮しています。 多対多の双方向。

3. 特権と役割の設定

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

これをアプリケーションの起動に結び付け、ContextRefreshedEventApplicationListenerを使用して、サーバーの起動時に初期データをロードします。

@Component
public class SetupDataLoader implements
  ApplicationListener<ContextRefreshedEvent> {

    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<Privilege> 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
    Privilege createPrivilegeIfNotFound(String name) {
 
        Privilege privilege = privilegeRepository.findByName(name);
        if (privilege == null) {
            privilege = new Privilege(name);
            privilegeRepository.save(privilege);
        }
        return privilege;
    }

    @Transactional
    Role createRoleIfNotFound(
      String name, Collection<Privilege> privileges) {
 
        Role role = roleRepository.findByName(name);
        if (role == null) {
            role = new Role(name);
            role.setPrivileges(privileges);
            roleRepository.save(role);
        }
        return role;
    }
}

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

  • 特権を作成しています。
  • 次に、役割を作成し、それらに特権を割り当てます。
  • 最後に、ユーザーを作成し、それに役割を割り当てます。

alreadySetup フラグを使用して、セットアップを実行する必要があるかどうかをで判断する方法に注意してください。これは、ContextRefreshedEventが複数回発生する可能性があるためです。アプリケーションで構成したコンテキストの数。 そして、セットアップを1回だけ実行したいと思います。

ここに2つの簡単なメモ。 最初にの用語を見ていきます。ここでは特権–役割の用語を使用しています。 しかし、Springでは、これらはわずかに異なります。 Springでは、私たちの特権は役割と呼ばれ、(付与された)権限とも呼ばれますが、少し混乱します。

もちろん、これは実装にとって問題ではありませんが、注目に値することは間違いありません。

次に、これらのSpringロール(特権)にはプレフィックスが必要です。デフォルトでは、そのプレフィックスは「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<? extends GrantedAuthority> getAuthorities(
      Collection<Role> roles) {
 
        return getGrantedAuthorities(getPrivileges(roles));
    }

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

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

ここで従うべき興味深いことは、特権(および役割)がGrantedAuthorityエンティティにどのようにマップされるかです。

このマッピングにより、セキュリティ構成全体が非常に柔軟で強力になります。必要に応じて役割と特権を組み合わせて組み合わせることができ、最終的に、それらは当局に正しくマッピングされ、フレームワークに戻されます。 。

5. 役割の階層

さらに、役割を階層に編成しましょう。

特権をロールにマッピングすることにより、ロールベースのアクセス制御を実装する方法を見てきました。 これにより、すべての個別の特権を割り当てるのではなく、ユーザーに単一の役割を割り当てることができます。

ただし、役割の数が増えると、ユーザーは複数の役割を必要とし、役割が急増する可能性があります。

これを克服するために、Springセキュリティの役割階層を使用できます。

ロールADMINを割り当てると、STAFFロールとUSERロールの両方の権限がユーザーに自動的に付与されます。

ただし、ロール STAFF を持つユーザーは、STAFFおよびUSERロールアクションのみを実行できます。

タイプRoleHierarchyのbeanを公開するだけで、Springセキュリティでこの階層を作成しましょう。

@Bean
public RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
    String hierarchy = "ROLE_ADMIN > ROLE_STAFF \n ROLE_STAFF > ROLE_USER";
    roleHierarchy.setHierarchy(hierarchy);
    return roleHierarchy;
}

式で>記号を使用して、役割の階層を定義します。 ここでは、役割 ADMIN を構成して、役割 STAFF を含め、役割USERを含めています。

最後に、この役割階層を Spring Web Expressions に含めるために、roleHierarchyインスタンスをWebSecurityExpressionHandlerに追加します。

@Bean
public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler() {
    DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
    expressionHandler.setRoleHierarchy(roleHierarchy());
    return expressionHandler;
}

ご覧のとおり、ロール階層は、ユーザーに追加する必要のあるロールと権限の数を減らすための優れた方法です。

6. ユーザー登録

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

ユーザーを作成し、それにロール(および特権)を割り当てるセットアップがどのように行われるかを見てきました。

次に、新しいユーザーの登録時にこれを行う必要がある方法を見てみましょう。

@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ロールを割り当てています。

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

7. 結論

この記事では、Springセキュリティに裏打ちされたシステムに対して、JPAを使用してロールと特権を実装する方法を説明しました。

また、アクセス制御の構成を簡素化するために役割階層を構成しました。

このRegistrationwithSpring Securityチュートリアルの完全な実装は、GitHubにあります。