Redditアプリにロールと特権を追加する

Redditアプリへのロールと特権の追加

1. 概要

今回の記事では、our Reddit appに簡単な役割と権限を導入して、次のような興味深いことを実行できるようにします。通常のユーザーがRedditに毎日スケジュールできる投稿の数を制限します。

また、管理者の役割(および暗黙的に管理者ユーザー)を持つため、管理者管理領域も追加します。

2. UserRole、およびPrivilegeエンティティ

まず、Userエンティティを変更します(Reddit App seriesを介して使用します)。ロールを追加します。

@Entity
public class User {
    ...

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

    ...
}

ユーザーとロールの関係が柔軟な多対多であることに注意してください。

次に、RoleエンティティとPrivilegeエンティティを定義します。 その実装の詳細については、this article on exampleを確認してください。

3. セットアップ

次に、プロジェクトのブートストラップでいくつかの基本的なセットアップを実行して、次の役割と特権を作成します。

private void createRoles() {
    Privilege adminReadPrivilege = createPrivilegeIfNotFound("ADMIN_READ_PRIVILEGE");
    Privilege adminWritePrivilege = createPrivilegeIfNotFound("ADMIN_WRITE_PRIVILEGE");
    Privilege postLimitedPrivilege = createPrivilegeIfNotFound("POST_LIMITED_PRIVILEGE");
    Privilege postUnlimitedPrivilege = createPrivilegeIfNotFound("POST_UNLIMITED_PRIVILEGE");

    createRoleIfNotFound("ROLE_ADMIN", Arrays.asList(adminReadPrivilege, adminWritePrivilege));
    createRoleIfNotFound("ROLE_SUPER_USER", Arrays.asList(postUnlimitedPrivilege));
    createRoleIfNotFound("ROLE_USER", Arrays.asList(postLimitedPrivilege));
}

そして、テストユーザーを管理者にします。

private void createTestUser() {
    Role adminRole = roleRepository.findByName("ROLE_ADMIN");
    Role superUserRole = roleRepository.findByName("ROLE_SUPER_USER");
    ...
    userJohn.setRoles(Arrays.asList(adminRole, superUserRole));
}

4. 標準ユーザーの登録

また、registerNewUser()実装を介して標準ユーザーを登録していることを確認する必要があります。

@Override
public void registerNewUser(String username, String email, String password) {
    ...
    Role role = roleRepository.findByName("ROLE_USER");
    user.setRoles(Arrays.asList(role));
}

システム内の役割は次のとおりです。

  1. ROLE_USER:通常のユーザーの場合(デフォルトの役割) - これらには1日にスケジュールできる投稿の数に制限があります。

  2. ROLE_SUPER_USER:スケジューリング制限なし

  3. ROLE_ADMIN:追加の管理オプション

5. 校長

次に、これらの新しい特権を主要な実装に統合しましょう。

public class UserPrincipal implements UserDetails {
    ...

    @Override
    public Collection getAuthorities() {
        List authorities = new ArrayList();
        for (Role role : user.getRoles()) {
            for (Privilege privilege : role.getPrivileges()) {
                authorities.add(new SimpleGrantedAuthority(privilege.getName()));
            }
        }
        return authorities;
    }
}

6. 標準ユーザーによるスケジュールされた投稿の制限

Redditのスパムを回避するために、新しい役割と特権、およびrestrict standard users from scheduling more than – say – 3 new articles a dayを利用してみましょう。

6.1. 投稿リポジトリ

まず、PostRepositoryの実装に新しい操作を追加します。これにより、特定の期間に特定のユーザーがスケジュールした投稿をカウントします。

public interface PostRepository extends JpaRepository {
    ...

    Long countByUserAndSubmissionDateBetween(User user, Date start, Date end);

}

5.2. スケジュールされたポストコントローラー

次に、schedule()メソッドとupdatePost()メソッドの両方に簡単なチェックを追加します。

public class ScheduledPostRestController {
    private static final int LIMIT_SCHEDULED_POSTS_PER_DAY = 3;

    public Post schedule(HttpServletRequest request,...) throws ParseException {
        ...
        if (!checkIfCanSchedule(submissionDate, request)) {
            throw new InvalidDateException("Scheduling Date exceeds daily limit");
        }
        ...
    }

    private boolean checkIfCanSchedule(Date date, HttpServletRequest request) {
        if (request.isUserInRole("POST_UNLIMITED_PRIVILEGE")) {
            return true;
        }
        Date start = DateUtils.truncate(date, Calendar.DATE);
        Date end = DateUtils.addDays(start, 1);
        long count = postReopsitory.
          countByUserAndSubmissionDateBetween(getCurrentUser(), start, end);
        return count < LIMIT_SCHEDULED_POSTS_PER_DAY;
    }
}

ここでいくつか興味深いことがあります。 まず、Spring Securityと手動でやり取りし、現在ログインしているユーザーに権限があるかどうかを確認していることに注目してください。 これは毎日行うことではありませんが、行う必要がある場合は、APIが非常に役立ちます。

現在のロジックでは、ユーザーがPOST_UNLIMITED_PRIVILEGEを持っている場合、ユーザーは選択した量だけスケジュールを立てることができます。

ただし、その権限がない場合は、1日あたり最大3件の投稿をキューに入れることができます。

7. 管理者ユーザーページ

次に、ユーザーの役割に基づいてユーザーを明確に分離したので、小さなRedditアプリの管理者向けに非常に簡単なユーザー管理を実装しましょう。

7.1. すべてのユーザーを表示

まず、システム内のすべてのユーザーを一覧表示する基本的なページを作成しましょう。

ここにすべてのユーザーをリストするためのAPI:

@PreAuthorize("hasRole('ADMIN_READ_PRIVILEGE')")
@RequestMapping(value="/admin/users", method = RequestMethod.GET)
@ResponseBody
public List getUsersList() {
    return service.getUsersList();
}

そして、サービス層の実装:

@Transactional
public List getUsersList() {
    return userRepository.findAll();
}

次に、シンプルなフロントエンド:

Username Roles Actions

7.2. ユーザーの役割を変更する

次に、これらのユーザーの役割を管理するためのいくつかの単純なロジック。コントローラから始めましょう:

@PreAuthorize("hasRole('USER_WRITE_PRIVILEGE')")
@RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.OK)
public void modifyUserRoles(
  @PathVariable("id") Long id,
  @RequestParam(value = "roleIds") String roleIds) {
    service.modifyUserRoles(id, roleIds);
}

@PreAuthorize("hasRole('USER_READ_PRIVILEGE')")
@RequestMapping(value = "/admin/roles", method = RequestMethod.GET)
@ResponseBody
public List getRolesList() {
    return service.getRolesList();
}

そしてサービス層:

@Transactional
public List getRolesList() {
    return roleRepository.findAll();
}
@Transactional
public void modifyUserRoles(Long userId, String ids) {
    List roleIds = new ArrayList();
    String[] arr = ids.split(",");
    for (String str : arr) {
        roleIds.add(Long.parseLong(str));
    }
    List roles = roleRepository.findAll(roleIds);
    User user = userRepository.findOne(userId);
    user.setRoles(roles);
    userRepository.save(user);
}

最後に-シンプルなフロントエンド:

8. セキュリティ構成

最後に、セキュリティ構成を変更して、管理ユーザーをシステム内のこの新しい個別のページにリダイレクトする必要があります。

@Autowired
private AuthenticationSuccessHandler successHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.
    ...
    .authorizeRequests()
    .antMatchers("/adminHome","/users").hasAuthority("ADMIN_READ_PRIVILEGE")
    ...
    .formLogin().successHandler(successHandler)
}

decide where the user lands after loginに対してカスタム認証成功ハンドラーを使用しています。

@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(
      HttpServletRequest request, HttpServletResponse response, Authentication auth)
      throws IOException, ServletException {
        Set privieleges = AuthorityUtils.authorityListToSet(auth.getAuthorities());
        if (privieleges.contains("ADMIN_READ_PRIVILEGE")) {
            response.sendRedirect("adminHome");
        } else {
            response.sendRedirect("home");
        }
    }
}

そして、非常にシンプルな管理者ホームページadminHome.html



    

Welcome, Bob


Display Users List

9. 結論

ケーススタディのこの新しい部分では、いくつかの簡単なセキュリティアーティファクトをアプリに追加しました–ロールと特権。 このサポートにより、we built two simple features –標準ユーザーのスケジューリング制限と管理ユーザーの最低限の管理者。