Learn Spring Security
Contents
Define Permissions
Let's define the permissions for each API in an Enum in the format ACTION_RESOURCENAME as mentioned in the last chapter.
public enum PermissionEnum {
CREATE_COURSE,
UPDATE_COURSE,
PLAY_COURSE,
LIST_STUDENTS,
LIST_INSTRUCTORS,
VIEW_PROFILE
}
Secure the APIs with Permissions
We will add the remaining API urls in the SecurityConstants. We will then apply permission-based access restrictions to the UpdateCourse, PlayCourse and ViewProfile APIs in HttpSecurity configuration. Let's not do any changes in the existing role-based restrictions for now.
public static final String API_UPDATE_COURSES = "/api/v1/courses/*";
public static final String API_PLAY_COURSE = "/api/v1/courses/play/*";
public static final String API_VIEW_PROFILE = "/api/v1/users/*";
@Bean
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests(auth -> auth
.antMatchers(GET, PUBLIC_API_LIST).permitAll()
// Role based access restrictions
.antMatchers(API_LIST_STUDENTS, API_LIST_INSTRUCTORS).hasRole(ADMIN.name())
.antMatchers(POST, API_CREATE_COURSES).hasRole(INSTRUCTOR.name())
// Permission based access restrictions
.antMatchers(API_UPDATE_COURSES).hasAuthority(UPDATE_COURSE.name())
.antMatchers(API_PLAY_COURSE).hasAuthority(PLAY_COURSE.name())
.antMatchers(API_VIEW_PROFILE).hasAuthority(VIEW_PROFILE.name())
.anyRequest().authenticated()
)
.httpBasic();
return http.build();
}
Before applying permission-based restrictions to the above three APIs, we were able to access them once authenticated successfully. But now we will get 403 Forbidden error for any user, as we have neither granted these Permissions to any Roles in the database nor updated the authorities in UserDetails.
AppPermission Entity
In order to assign permissions dynamically without changing the code, we have to model them as a database entity and establish the relationship with AppRole entity. As we can assign any number of permissions to any roles, we have to model this as many-to-many relationship with AppRole entity.
Let's create an Entity class for Permission and define a standard JPARepository interface for the entity.
@Entity(name = "app_permission")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
public class AppPermission {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Enumerated(value = EnumType.STRING)
private PermissionEnum name;
@ManyToMany(fetch = FetchType.LAZY, mappedBy = "permissions")
@JsonIgnore
private Set<AppRole> assignedRoles;
}
@Repository
public interface AppPermissionRepository extends JpaRepository<AppPermission, Long> {
}
Update AppRole Entity
Now let's update the other side of the many-to-many relationship in AppRole entity, where AppRole entity will be the owner of the relation.
// Other details omitted for brevity
public class AppRole {
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "app_role_to_permission",
joinColumns = @JoinColumn(name = "app_role_id"),
inverseJoinColumns = @JoinColumn(name = "app_permission_id")
)
private Set<AppPermission> permissions;
}
Grant Permissions
The next step after modeling the entity is to create the permission records corresponding to each PermissionEnum instances. And then assign them to the roles as mentioned in the below table to align with the Security objectives defined in the first chapter. We can do this in AppDataInitialiser class in order to make these changes in the database while bootstraping the application.
Role | Permissions |
---|---|
Student | PLAY_COURSE, VIEW_PROFILE |
Instructor | CREATE_COURSE, UPDATE_COURSE, PLAY_COURSE, VIEW_PROFILE |
Admin | LIST_STUDENTS, LIST_INSTRUCTORS, VIEW_PROFILE |