[Spring Security] WebExpressionVoter 및 PIAAVoter 테스트 실습

WOOK JONG KIM·2022년 12월 4일
0

패캠_java&Spring

목록 보기
85/103
post-thumbnail

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;
    }

}
profile
Journey for Backend Developer

0개의 댓글