이 녀석도 아주 유용한 애노테이션입니다. @PreAuthorize랑 비슷하게 권한을 확인하지만, 차이점은 메서드를 일단 실행한 뒤에 권한을 확인한다는 것입니다. 처음에는.. 이런 생각을 했었습니다.

이게 뭐야.. @_@.. 이미 실행 한 뒤에 권한을 체크하면... 무슨 소용이지??

그런데 막상 보안관련 코드를 작성하다 보면 그럴 일이 생기더군요. 예를 들어, 봄싹 프로젝트에서 개인 정보 수정 기능이 있는데, 이 때, springsprout.org/member/update/{id}.do 이런 URL 구조를 사용합니다. 이 기능은 컨트롤러에서 GET, POST 2단으로 나눠서 처리합니다. 아주 일반적인 경우죠.

    @RequestMapping(value = "/member/update/{id}.do", method = RequestMethod.GET)
    public String updateForm(@PathVariable int id, Model model)
            throws ServletRequestBindingException {
        model.addAttribute(service.getMemberById(id));
        return "member/update";
    }

    @RequestMapping(value = "/member/update/{id}.do", method = RequestMethod.POST)
    public String updateForm(@PathVariable int id, Member member, BindingResult result,
            SessionStatus status, HttpSession session) throws ServletRequestBindingException {
        validator.validate(member, result);
        if (result.hasErrors()) {
            return "member/update";
        } else {
            service.update(member);
            status.isComplete();
            session.setAttribute("SESSION_FLASH_MSG", "회원정보가 수정되었습니다.");
            return "mypage/index";
        }
    }

시큐리티 보안 기능 중 하나인 메서드 보안 말고, URL 보안으로 MEMBER나 ADMIN 권한이 있는지 정도는 확인할 수 있습니다.

그러나.. GET 요청일 때, 만약 다른 회원 정보를 보기 위해 URL에서 자신의 id 가 아닌 다른 id를 입력한다면 어떻게 될까요? URL 보안은 못 막습니다. 아마도 getMember() 앞쪽에 이런 코드가 들어갈 겁니다.

    public boolean isCurrentUserOrAdmin(int id) {
        if(!isCurrentMembersInfo(id) && !isAdmin())
            throw new AccessDeniedException("다른 회원의 정보에 접근을 시도할 경우 계정이 차단 됩니다.");
        return true;
    }
   
    private boolean isCurrentMembersInfo(int id) {
        return getCurrentMemberId() == id;
    }

isCurrentUserOrAdmin 같은 걸 호출해서 확인을 거친 다음에 getMember()를 호출할 수 있게 해야합니다.

POST 요청은 어떤가요? 누군가 POST 요청을 임의로 만들어서 접근을 시도한다면?? 그 경우도 막기 위해 위와 같은 코드를 update 하기 전에 실행해야 합니다. 이 경우는 이전 글에서 살펴보았던, @PreAuthorize가 적당하기 때문에 쉽게 바꿀 수 있습니다.

    @PreAuthorize("hasRole('ROLE_ADMIN') or (#member.email == principal.Username)")
    public void update(Member member) {
        member.loadAvatar();
        repository.update(member);
    }

이렇게 말이죠. 굳이 컨트롤러에 시큐리티 관련 코드를 삽입할 필요가 없습니다. 그럼 GET 요청 처리는 어떻게 할까요??

    @PostAuthorize("(returnObject.email == principal.Username) or hasRole('ROLE_ADMIN')")
    public Member getMemberById(int id) {
        return repository.getMemberById(id);
    }

이렇게 getMember() 위에 @PostAuthrize를 붙여서 해결할 수 있습니다. 현재 상요자가 화면에 보여주려는 객체에 대한 권한이 있는지 혹은 관리자인지 확인해 보는 것이죠.

결론은.. @PostAuthorize도 매우 유용하답니다.

그런데 참고할만한 자료가 너무 없네요. 애노테이션 API에서 EL에서 자주 쓰일만한 기본 내장 객체 이름 (principal 이나 returnObject) 들을 알려줬으면 하는데 그런 내용이 없습니다.

거의 유일한 참고 자료는 예전에 번역/편역/요역해서 올렸었던 글 하나 뿐..

http://blog.springsource.com/2009/06/03/spring-security-300m1-released/
http://whiteship.me/2257