전 글에서 인증과 권한에 대해 다뤘고, 같은 권한을 가진 User는 다른 User의 리소스에 접근할 수 있었습니다. Interceptor를 통해서 인가 문제를 해결해보겠습니다.


1. Interceptor란?

인터셉터(Interceptor)는 스프링의 Spring Context(ApplicationContext) 기능입니다.

임의의 URI를 호출 시 DispatcherServlet에서 해당 Controller가 처리되기 전과 후에 발생하는 것을 처리할 수 있습니다.

즉 DispatcherServlet -> Interceptor -> Controller -> Interceptor -> ~~ 로 표현할 수 있습니다.

 


 

2. UserController 수정

@PostMapping("/character")
public String create(@RequestParam(name = "name") String name,
                     HttpServletRequest request){
    HttpSession session = request.getSession(false);
    String email = (String) session.getAttribute("email");
    Character character = userService.createCharacter(name, email);
    return "ok";
}
  • 더 이상 로그인을 한 User에게서는 RequestParam으로 User 정보를 받지 않고, 인증을 성공한 User에 한해서 CustomLoginSuccessHandler에서 session을 만들고 정보를 넣습니다. 이제 Controller에서는 HttpServlerRequest에서 session을 꺼내 어떠한 User인지 알 수 있습니다. 

3. CustomLoginSuccessHandler 수정

public class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {


    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        SecurityContextHolder.getContext().setAuthentication(authentication);
        String email = authentication.getName();
        HttpSession session = request.getSession();
        session.setAttribute("email",email);
        response.sendRedirect("/success");
    }
}
  • Authentication.getName()을 통해서 User의 email을 꺼내고 session을 성성하고 정보를 넣습니다.

session과 코드의 중복을 줄이는 ArgumentResolver를 알아보고 싶으시면 아래 링크들에서 확인 부탁드립니다:)

 

https://dingdingmin-back-end-developer.tistory.com/entry/SpringBoot-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B06-MemberController-%EB%A7%8C%EB%93%A4%EA%B8%B0?category=910740 

 

SpringBoot [스프링부트] 시작하기(6) MemberController 만들기

UserController를 만들었던 거 처럼 MemberController를 만들겠습니다. 1. MemberController Controller위에 @RequestMapping("member")을 주면 모든 메소드에 적용됩니다. /member/~~이런식으로 식별됩니다. @Pos..

dingdingmin-back-end-developer.tistory.com

 

https://dingdingmin-back-end-developer.tistory.com/entry/SpringBoot-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B06-ArgumentResolver-%EB%A7%8C%EB%93%A4%EA%B8%B0?category=910740 

 

SpringBoot [스프링부트] 시작하기(7) ArgumentResolver 만들기

전 시간에 Controller에서 Session을 가져오는 데 있어서 중복 코드가 있었습니다. 해당 코드를 ArgumentResolver로 해결해보겠습니다. ArgumentResolver란? 컨트롤러의 메소드의 인자로 사용자가 임의의 값을

dingdingmin-back-end-developer.tistory.com


4. CharacterRepository 수정 (fetch join을 추가해줍니다.)

public interface CharacterRepository extends JpaRepository<Character, Long> {


    @Query("select c from Character c join fetch c.user where c.id= :id")
    Optional<Character> findFetchById(@Param("id") Long id);
}

5. CharacterInterceptor추가 (주석을 통한 설명)

@RequiredArgsConstructor
@Component
public class CharacterInterceptor implements HandlerInterceptor {

    private final CharacterRepository characterRepository;

    //Filter 에서 이미 권한에 관한 것을 처리했으므로 인가에 관해서만 처리합니다.
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // URI 즉 localhost:8080의 그 뒷부분이 해당합니다.
        String requestURI = request.getRequestURI();

        //session 을 통해서 어느 User 가 요청했는지 알 수 있습니다.
        String email = (String) request.getSession().getAttribute("email");


        //Post 에 관해서는 자신의 캐릭터를 만드는 것이므로 모두 접근 가능하지만 Patch 에 관해서는 자신이 소유한 것만 가능하므로 체크해줍니다.
        if(requestURI.contains("/character") && request.getMethod().equals("PATCH")){
            //Patch 를 하는 캐릭터의 id 입니다.
            long id = Long.parseLong(request.getParameter("id"));
            Character character = characterRepository.findFetchById(id).get();
            if(!character.getUser().getEmail().equals(email)){
            	
                // 인가 되지 않으므로 /notAccess 로 redirect합니다.
                response.sendRedirect("/notAccess");
                
                // false 일 경우 원래 접근하고자 하는 uri에 요청을 보내지 않습니다.
                // return false를 해주지 않으면 밑에 로직들이 실행되므로 반드시 해줘야합니다.
                return false;
            }
        }
        else{
            return true;
        }
        // return true 이므로 자원에 접근할 수 있습니다. 
        return true;
    }
}

6. WebConfig 추가

@EnableWebMvc
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {

    private final CharacterRepository characterRepository;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CharacterInterceptor(characterRepository))
                .addPathPatterns("/character") // "/character"에 대해서만 Interceptor를 적용합니다.
                .excludePathPatterns("/signup","/login","/notAccess","/user","/guest","/"); // 적어준 uri에 대해선 Interceptor를 적용하지 않습니다.
    }
}

7. Postman test

  • asdf User와 qwer User가 가입을 합니다.

 

  • asdf User가 로그인을 한 후 Character를 만듭니다. 

  • asdf User는 Character의 이름을 업데이트가 가능합니다.

 

  • qwer User가 로그인을 한 후 asdf User가 만든 Character에 대해서 인가가 되어 수정이 가능한지 보겠습니다.

  • 수정이 불가능한 것을 볼 수 있습니다.

지금까지 Security의 Filter를 직접 만들어 권한과 인증처리를 완료했고, Interceptor를 통해서 인가에 대한 처리를 완료했습니다. Security에 전반적인 부분을 살펴보았습니다. 감사합니다.

 

모든 코드는 아래 링크에서 확인 가능합니다.

https://github.com/rlaehdals/secuitybasic

 

GitHub - rlaehdals/secuitybasic

Contribute to rlaehdals/secuitybasic development by creating an account on GitHub.

github.com