Paper.java
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Paper {
private Long paperId;
private String title;
private String tutorId; // 강사
private List<String> studentIds;
private State state;
public static enum State {
PREPARE, // 출제 중
READY, // 시험 시작
END // 시험 종료
}
}
PaperController.java
@RestController
@RequestMapping("/paper")
public class PaperController {
@Autowired
private PaperService paperService;
@PreAuthorize("isStudent()")
@GetMapping("/mypapers")
public List<Paper> myPaper(@AuthenticationPrincipal User user){
return paperService.getMyPapers(user.getUsername());
}
@PreAuthorize("hasPermission(#paperId, 'paper', 'read')")
@GetMapping("/get/{paperId}")
public Paper getPaper(@AuthenticationPrincipal User user, @PathVariable Long paperId){
return paperService.getPaper(paperId);
}
}
PaperService.java
@Service
public class PaperService implements InitializingBean {
//실제론 Repository에서 DB 이용
private HashMap<Long, Paper> paperDB = new HashMap<>();
@Override
public void afterPropertiesSet() throws Exception {
}
public void setPaper(Paper paper){
paperDB.put(paper.getPaperId(), paper);
}
//학생으로 시험지 가져오기
public List<Paper> getMyPapers(String username) {
return paperDB.values().stream().filter(
paper -> paper.getStudentIds().contains(username)
).collect(Collectors.toList());
}
public Paper getPaper(Long paperId) {
return paperDB.get(paperId);
}
}
MethodSecurityConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
@Autowired
private CustomPermissionEvaluator permissionEvaluator;
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler() {
@Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
// return new CustomMethodSecurityExpressionRoot(authentication, invocation);
CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication, invocation);
root.setPermissionEvaluator(getPermissionEvaluator());
return root;
}
};
handler.setPermissionEvaluator(permissionEvaluator); // 이렇게 전달한다고 해서 ExpressionRoot에 실제로 들어갈 수 있는 것 X
// 따라서 위에 코드 처럼 루트에 전달해줘야 함
return handler;
}
@Override
protected AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice();
expressionAdvice.setExpressionHandler(getExpressionHandler());
decisionVoters.add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice));
decisionVoters.add(new RoleVoter());
decisionVoters.add(new AuthenticatedVoter());
return new AffirmativeBased(decisionVoters); // 한명이라도 동의할 시 통과(긍정 위원회)
}
}
CustomMethodSecurityExpressionRoot
// MethodSecurityExpressionRoot는 public이 아님
// 이렇게 커스텀화 된 루트를 교체 하기 위해선 Handler에서 교체해야 함
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot
implements MethodSecurityExpressionOperations {
MethodInvocation invocation;
public CustomMethodSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
super(authentication);
this.invocation = invocation;
}
private Object filterObject;
private Object returnObject;
public boolean isStudent(){
// 이것은 Context에 루트로 등록되는 객체이기 때문에 루트에서 isStudent()에 바로 접근 가능
return getAuthentication().getAuthorities().stream()
.filter(a -> a.getAuthority().equals("ROLE_STUDENT"))
.findAny().isPresent();
}
public boolean isTutor(){
return getAuthentication().getAuthorities().stream()
.filter(a -> a.getAuthority().equals("ROLE_TUTOR"))
.findAny().isPresent();
}
@Override
public Object getThis() {
return this;
}
}
CustomPermissionEvaluator.java
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Autowired
private PaperService paperService;
// 시험지의 사용자로 등록된 자만 시험지를 가져올수 있게
@Override
public boolean hasPermission(Authentication authentication,
Serializable targetId,
String targetType, Object permission) {
Paper paper = paperService.getPaper((long) targetId);
if (paper == null) throw new AccessDeniedException("시험지가 존재하지 않음.");
// 준비 상태 시험지 접근 금지
if(paper.getState() == Paper.State.PREPARE) return false;
boolean canUse = paper.getStudentIds().stream().filter(userId -> userId.equals(authentication.getName()))
.findAny().isPresent();
return canUse;
}
@Override
public boolean hasPermission(Authentication authentication,
Object targetDomainObject, Object permission) {
return false;
}
}