Question

I'm designing a db model where I want to have:

  • User(id, role_id) (UserDetails)
  • Permission(role_id, permission) (GrantedAuthority)
  • Role(id, description)

I'm using Hibernate and Spring security. I want every role to have list of permissions (GrantedAuthorities) using role_id rather than specific user having that. I'm kinda lost designing that. This is what I've came up with so far:

public class User implements UserDetails, Serializable {

    @Id
    @GeneratedValue
    @Column(name = "id")
    private int id;
    @Column(name = "role_id", insertable = true, updatable = false)
    private int roleId;
    @OneToMany(fetch = FetchType.EAGER, mappedBy = "user", cascade = CascadeType.ALL)
    private List<Permission> permissions;

}

public class Permission implements GrantedAuthority, Serializable {

    @Id
    @GeneratedValue
    @Column(name = "id")
    private int id;
    @Column(name = "role_id", insertable = false, updatable = false)
    private int roleId;
    @Column(name = "permission")
    private String permission;
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "role_id", nullable = false, insertable = false, updatable = false)
    private User user;

}

I've omitted the not so important code. The Role class isn't important for Spring security.

I know I'm making a huge mistake somewhere but I just can't seem to figure out how to fix it. The issue is that it joins those two objects using user.id instead of user.roleId. I've tried different annotations but I just can't hook it up correctly.

So the raw issue probably is that I'm trying to join 2 objects using a property from one and PK from another which might be a mistake.

Edit: I've also tried to specify referencedColumnName on Permission class but it didn't work either. User can't log in.

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "role_id", nullable = false, insertable = false, updatable = false, referencedColumnName = "role_id")
private User user;
Was it helpful?

Solution

Why would you want to have a design that can backfire you in the future? You really have to think ahead and apply good design practices on your project. This topic pops up here almost every day.

In Spring context, a role actually is an authority. I have no idea why this is made so complex by design by them. You either can have a 1) very simple approach where you assign a role which in fact is authority to an user or 2) more complex solution which includes user, role and permission. The idea would be to assign permissions to roles and assign roles to user. In this solution role entity only serves a purpose of grouping the granted permissions together as one bundle, but through your authentication manager you assign permissions through roles.

Note: I am using common base @MappedSuperclass for my entities.

First, have an User entity:

@Entity
@Table(name = "user_t")
public class User extends BaseEntity {

    @Column(name = "username", nullable = false, unique = true)
    private String userName;

    @Column(name = "password", nullable = false)
    private String password;

    @ManyToMany
    @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
    private Set<Role> role = new HashSet<Role>();

    // builder/getters/setters
}

Role entity

@Entity(name = "role_t")

    @Column(name = "role_name", nullable = false)
    private String roleName;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "role_permission", joinColumns = @JoinColumn(name = "role_id"), inverseJoinColumns = @JoinColumn(name = "permission_id"))
    private Set<Permission> permissions;

    // getters/setters
}

Permission entity

@Entity(name = "permission_t")
public class Permission extends BaseEntity implements GrantedAuthority {

    @Column (name = "permission_name", nullable = false)
    private String permissionName;

    public String getPermissionName() {
        return permissionName;
    }

    public void setPermissionName(String permissionName) {
        this.permissionName = permissionName;
    }   

    @Override
    public String getAuthority() {
        return permissionName;
    }

    @Override
    public int hashCode() {
        return permissionName.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if(obj == null) return false;
        if(!(obj instanceof Permission)) return false;
        return ((Permission) obj).getAuthority().equals(permissionName);
}

Now in your AuthenticationManager or whatever you decide to use, you loop throug the roles and assign the permissions that are assigned to the roles to the user, if that makes sense.

CustomAuthenticationProvider

public class AppAuthProvider implements AuthenticationProvider {

    private static final String PERMISSION_PREFIX = "ROLE_PERMISSION_";
    // get the logging user info

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {


        Collection<GrantedAuthority> permissions = new HashSet<GrantedAuthority>();
        for (Role role : user.getRole()) {
            for (Permission perm : role.getPermissions()) {
                GrantedAuthority permission = new SimpleGrantedAuthority(PERMISSION_PREFIX + perm.getPermissionName());
                permissions.add(permission);
            }
        }

        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(user, null, permissions); // user object you get from service/repository

        return authToken;
    }
}

OTHER TIPS

I think you have to re-consider your schema, see the image below. work from here. You need a many-many mapping table for mapping permissions to role.

public class User implements UserDetails, Serializable {

    @Id
    @GeneratedValue
    @Column(name = "id")
    private int id;
    @JoinColumn(name = "role_id", mappedBy= 'user", insertable = true, updatable = false)
    private Role role;      

}

public class Permission implements Serializable {

    @Id
    @GeneratedValue
    @Column(name = "id")
    private int id;
    @Column(name = "permission")
    private String permission;   
    @ManyToMany(fetch=FetchType.LAZY,cascade=CascadeType.ALL)
    @JoinTable(name = "role_permissions", catalog = "schema", joinColumns = {
        @JoinColumn(name = "id", nullable = false, updatable = false) }, inversJoinColumns =
            { @JoinColumn(name = "id", nullable = false, updatable = false)}
    private Set<Role> roles;

  }


public class Role implements Serializable {

    @Id
    @GeneratedValue
    @Column(name = "id")
    private int id;
    @Column(name = "permission")
    private String permission;   
    @ManyToMany(fetch=FetchType.LAZY, mappedBy = "permission")
    public Set<Permissions> permisions;
  }

Update: see the new schema diagram.

Updated schema diagram

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top