SpEL(Spring Expression Language)란?
Spring expression language는 보통 SpEL로 표기하며 구두로는 스프링 EL이라고 지칭한다
인증은 각각 서비스에서 정해지는 방법이 있을것이고 filter나 interceptor에서 거를 거라고 생각하고
api호출시의 권한체크, @PreAuhorize의 여러 방법들에 대해서 알아보려고 한다
0. 인증이 필요하지 않을 경우
@PreAuthorize("isAnonymous()")
익명이라는 뜻이다. 이 경우 인증을 거치지 않더라도 동작한다
@PreAuthorize("isAnonymous()")
@GetMapping("/test0")
public String test0() {
return "test0";
}
1. 인증이 필요할 경우
@PreAuthorize("isAuthenticated()")
Authentication(인증)이 필요하다.
@PreAuthorize("isAuthenticated()")
@GetMapping("/test1")
public String test1() {
return "test1";
}
2. 인가 - 특정한 롤 중 하나라도 있는 경우(보통은 hasRole보다는 확장성을 위해서 hasAnyRole을 선호한다)
이 밑으로는 인가가 필요한데, 인증은 로그인 인가는 권한 확인이라고 보면 된다. 인가가 되는지 여부를 체크한다는 뜻은 이미 인증은 거쳤다는 뜻으로 받아들여진다. 그래서 3번부터는 인가 - 를 빼겠다
@PreAuthorize("hasAnyRole('ROLE_USER', 'ROLE_ADMIN')")
@PreAuthorize("hasAnyRole('ROLE_USER', 'ROLE_ADMIN')")
@GetMapping("/test2")
public String test2() {
return "test2";
}
이따 코드로 보여주겠지만, ROLE !== Authority 정도이다. 시큐리티에서 의미하는 바는 같은데, 코드의 동작방식이 아주 살짝 다르다
3. 특정한 롤 중 하나라도 있는 경우 2
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
@GetMapping("/test3")
public String test3() {
return "test3";
}
이렇게 보면 내부적으로getRoleWithDefaultPrefix에서 ROLE_을 떼오고
Set Container의 contains로 판단하기때문에 ROLE_을 굳이 붙이지 않아도 동작한다
4. 특정 권한들 중 하나라도 있는 경우(이것도 마찬가지로 hasAnyAuthority를 썼다)
@PreAuthorize("hasAnyAuthority('ROLE_USER', 'ROLE_ADMIN')")
@PreAuthorize("hasAnyAuthority('ROLE_USER', 'ROLE_ADMIN')")
@GetMapping("/test4")
public String test4() {
return "test4";
}
5. 특정 권한들 중 하나라도 있는 경우 2
@PreAuthorize("hasAnyAuthority('USER', 'ADMIN')")
@PreAuthorize("hasAnyAuthority('USER', 'ADMIN')")
@GetMapping("/test5")
public String test5() {
return "test5";
}
어? 왜안될까...?ㅎㅎ
자 코드를 살펴보면, Role과는 다르게, prefix가 null이면 뒤의 String 전체매칭으로 판단하기때문에 이렇게 실패를 한다
결론적으로 hasAnyRole로 처리를 하고 ROLE_ 부분을 빼고 작성하면 깔끔할 것 같다
6. Auhentication의 Principal로 확인하는 경우
@PreAuthorize("principal == #email")
@PreAuthorize("principal == #email")
@GetMapping("/test6/{email}")
public String test6(@PathVariable String email) {
return "test6";
}
정보는 넣는 사람 마음이긴 한데 보통은 토큰값은 프론트에서도 전역적으로(리코일,리덕스,몹엑스 등등)으로 관리되기때문에
나같은 경우는 key Object느낌으로, Principal에는 pk key값을 넣고, Credentials에는 Token Object를 암호화해서 담아놓는다(파싱해도 정보가 보여지지도 않고, 단순한 email, phone번호 같이 최후의 순간에 복호화가 되더라도 critical한 정보가 아닌 데이터들만 - 어차피 대칭키값은 알아내기 힘들지롱)
Authentication은 Principal, Credentails, Authorities로 만들어지는데, Principal을 PathVariable, RequestParams, ResponseBody 등과 비교를 하는것이다
7. PostAuthorize returnObject로 한번 더 비교하는 경우
@PreAuthorize("principal == #email")
@PostAuthorize("returnObject == principal")
@PreAuthorize("principal == #email")
@PostAuthorize("returnObject == principal")
@GetMapping("/test7/{email}")
public String test7(@PathVariable String email) {
return "test7";
}
반환된 객체가 자격증명과 일치하는지 체크한다. 반환객체의 depth(참조)랑도 비교를 할 수 있다. 여기서는 단순 비교만 했다
보통 음... /me같이 자기 자신이 맞는지 확인하는데 메서드를 거치면서 변조 여부까지 확인을 한번 더 하는 그런 로직이 필요할때 사용할 것 같다
화면에서 보이다시피 50 / AccessDeniedException 예외를 뱉는다
이제부터는 DTO 바디와 함께 간다
@Getter
static class UserDTO {
private String email;
private Integer age;
}
8. 이번에는 DTO의 객체참조로 비교할 때
@PreAuthorize("principal == #user.email")
@PreAuthorize("principal == #user.email")
@GetMapping("/test8")
public String test8(@RequestBody UserDTO user) {
return "test8";
}
PathVariable과 마찬가지로 @RequestBody로도 사용 가능하다(잭슨 바인딩 되기때문에, DTO 쪽에도 Getter가 있어야 작동하는건 기본)
9. PostAuthorize principal로 비교하는 경우
@PreAuthorize("principal == #email")
@PostAuthorize("principal == #email")
@PreAuthorize("principal == #user.email")
@PostAuthorize("principal == #user.email")
@GetMapping("/test9")
public String test9(@RequestBody UserDTO user) {
Authentication authentication = new UsernamePasswordAuthenticationToken("change", null,
Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")));
SecurityContextHolder.getContext().setAuthentication(authentication);
return "changed";
}
7번과 비슷하지만 returnObject가 아닌 principal들끼리만 비교하는 경우이다. 일부러 실패하는 경우를 재현하기 위해 새로운 인증 객체를 시큐리티 컨텍스트 홀더에 담아줬다
그랬더니 당연히 실ㅋ패ㅋ
10. and 나 or 조건도 이용하는 경우
@PreAuthorize("principal == #email" || hasRole('ADMIN')")
@PreAuthorize("principal == #user.email || hasRole('ADMIN')")
@GetMapping("/test10")
public String test10(@RequestBody UserDTO user) {
return "test10";
}
보통 수정이나 삭제 요청에 쓰인다. 로그인한 사용자는 프론트에서 단순 정보(유저번호(pk), 이메일(pk) 등등...)들을 가지고 있을거고
DLETE user/{userId}/post/{postNo}
PATCH user/{userId}/post/{postNo}
이런 요청이 있다면, #userId == principal || hasRole('ADMIN')으로 하면 된다
아니면 body값에 넣어서 넘겨주면 된다 ㅎㅎ
11. @RequestParam으로도 테스트
@PreAuthorize("principal == #email")
@PreAuthorize("principal == #email")
@GetMapping("/test11")
public String test11(@RequestParam String email) {
return "test11";
}
당연히 되겠지만 안해본거같아서 해봤다
아니면 SecurityConfig 에서 이렇게 경로 설정과 필요한 권한을 전역적으로 설정해줘도 상관이 없다
또는 컨트롤러 맨 위에 걸어줘도 된다
모두 공부가 되셨길 바라며 뿅
'Backend > Java - Spring' 카테고리의 다른 글
DTO instantiate (feat. Lombok, GET/POST) (4) | 2022.03.10 |
---|---|
Springboot local 환경에서 https 적용하기 (0) | 2022.03.03 |
AOP/(Rest)ControllerAdvice Log를 꾸며보자(feat. NPE 관리) (0) | 2022.02.26 |
댓글