안녕하세요, 코린이의 코딩 학습기 채니 입니다.
개인 포스팅용으로 내용에 오류 및 잘못된 정보가 있을 수 있습니다.
Security - 인증된 사용자 정보 가져오기
아이디를 클릭하면 해당 회원의 정보를 랜더링 해주도록 하겠습니다.
security 처리 순서 파악하기
사용자 인증 요청이 들어오면, 위와 같은 순서로 처리가 됩니다.
① AuthenticationFilter에서 사용자가 입력한 username과 password를 통해 UsernamePasswordAuthenticationToken 생성
② 해당 객체를 AuthenticationManager에게 보내줌 (구현체는 providerManager)
<authentication-manager>
<authentication-provider user-service-ref="memberSecurityService">
<password-encoder ref="bcryptPasswordEncoder"/>
</authentication-provider>
</authentication-manager>
③ 이를 관리하는 AuthenticationProvider가 여러개 있고, 그 중 security가 현재 username, password를 가지고 인증을 처리할 수 있는 provider를 찾음 (UserDetailsService를 가지고 있는 Provider가 처리할 수 있다고 나섬!!)
④ UserDetailsService를 통해 실제 하나의 User를 가져옴
⑤ 이를 DB에서 조회하여 데이터를 가져오게 되면, 이를 AuthenticationFilter에서 세션에 저장
⑥ security가 관리하는 SecurityContextHolder가 있고, SecurityContext가 따로 있으며, 그 안에 Authentication에서 사용자 정보 관리
- Authentication 내부적으로 principal, credentials, authorities 를 구분하여 관리해줍니다.
즉, 인증된 사용자 정보는 SecurityContextHolder가 가지고 있습니다.
Security - 사용자 정보 상세 조회
이러한 처리 과정을 이해한 후 인증된 사용자 정보를 Controller에서 가져와 랜더링 처리해주겠습니다.
인증된 사용자 정보 가져오기
방법 ① - SecurityContextHolder로부터 직접 가져오는 방법 (비추)
Controller
MemberSecurityController
/**
* security가 관리하는 인증된 사용자 정보
*/
@GetMapping("/memberDetail.do")
public ModelAndView memberDetail(ModelAndView mav) {
// SecurityContextHolder로부터 직접 가져오는 방법
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
Object principal = authentication.getPrincipal();
Object credentials = authentication.getCredentials();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
log.debug("principal = {}", principal);
log.debug("credentials = {}", credentials);
log.debug("authorities = {}", authorities);
mav.setViewName("member/memberDetail");
return mav;
}
@콘솔출력값
DEBUG: com.ce.spring2.member.controller.MemberSecurityController - principal = Member(super=MemberEntity(memberId=honggd, password=$2a$10$G7F3EfcXE8wJWUAsCznaoOqttim4MwJBNlWey5yuL5ZRMwRSdPvEi, name=홍길동그랑땡, gender=M, birthday=1999-09-09, email=honggd12@abc.com, phone=01045674567, address=경기 성남시 분당구 대왕판교로 688, hobby=[등산, 게임], createdAt=2022-08-22T12:23:43, updatedAt=2022-08-23T12:47:25, enabled=true), authorities=[ROLE_USER])
DEBUG: com.ce.spring2.member.controller.MemberSecurityController - credentials = null // 실제 열람 불가!
DEBUG: com.ce.spring2.member.controller.MemberSecurityController - authorities = [ROLE_USER]
※ intercetor에서 발생하는 403 오류
지금까지 포스팅 과정을 거치면 servlet-context에 loginInterceptor를 통한 login여부를 검사했을 것입니다.
이제는 security가 처리해주기 때문에 해당 interceptor는 주석 처리해주겠습니다.
servlet-context.xml
<!--
<interceptor>
<mapping path="/member/memberDetail.do"/>
<mapping path="/member/memberUpdate.do"/>
<mapping path="/board/**"/>
<exclude-mapping path="/board/boardList.do"/>
<exclude-mapping path="/board/boardDetail.do"/>
<exclude-mapping path="/board/fileDownload.do"/>
<beans:bean id="loginInterceptor" class="com.ce.spring2.common.interceptor.LoginInterceptor"></beans:bean>
</interceptor>
-->
public class LoginInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 로그인 여부 확인
HttpSession session = request.getSession();
Member loginMember = (Member)session.getAttribute("loginMember");
if(loginMember == null) {
// Redirect Attribute 대신 사용할 FlashMap
FlashMap flashMap = new FlashMap();
flashMap.put("msg", "로그인 후 이용할 수 있습니다.");
FlashMapManager manager = RequestContextUtils.getFlashMapManager(request);
manager.saveOutputFlashMap(flashMap, request, response);
response.sendRedirect(request.getContextPath() + "/member/memberLogin.do");
return false;
}
return super.preHandle(request, response, handler);
}
}
해당 intercetpor를 주석하지 않는다면, session에 "loginMember"를 저장하지 않고 security로 관리하므로 loginMember는 null이므로 해당 if문을 타게 됩니다.
그 때, redirect가 "/member/memberLogin.do"로 되는데 이 때 /member/memberLogin.do는 isAnonymous() 즉! 인증이 안되었을 때만 접근할 수 있으므로 403 오류가 발생하게 됩니다.
다만!!! 위와 같은 방법으로 인증된 사용자 정보를 가져오는 것은 비추입니다.
(하나하나 다 꺼내온 것이기 때문)
인증된 사용자 정보 가져오기
방법 ② - Authentication 주입 받기
Controller
MemberSecurityController
@GetMapping("/memberDetail.do")
public ModelAndView memberDetail(Authentication authentication, ModelAndView mav) {
Object principal = authentication.getPrincipal();
Object credentials = authentication.getCredentials();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
log.debug("principal = {}", principal);
log.debug("credentials = {}", credentials);
log.debug("authorities = {}", authorities);
mav.setViewName("member/memberDetail");
return mav;
}
@콘솔출력값
DEBUG: com.ce.spring2.member.controller.MemberSecurityController - principal = Member(super=MemberEntity(memberId=honggd, password=$2a$10$G7F3EfcXE8wJWUAsCznaoOqttim4MwJBNlWey5yuL5ZRMwRSdPvEi, name=홍길동그랑땡, gender=M, birthday=1999-09-09, email=honggd12@abc.com, phone=01045674567, address=경기 성남시 분당구 대왕판교로 688, hobby=[등산, 게임], createdAt=2022-08-22T12:23:43, updatedAt=2022-08-23T12:47:25, enabled=true), authorities=[ROLE_USER])
DEBUG: com.ce.spring2.member.controller.MemberSecurityController - credentials = null
DEBUG: com.ce.spring2.member.controller.MemberSecurityController - authorities = [ROLE_USER]
잘 가져오는 것을 확인할 수 있습니다.
memberDetail.jsp
<input type="text" class="form-control" placeholder="아이디 (4글자이상)" name="memberId" id="memberId" value='<sec:authentication property="principal.memberId"/>' readonly required/>
<input type="text" class="form-control" placeholder="이름" name="name" id="name" value="<sec:authentication property="principal.name"/>" required/>
<input type="date" class="form-control" placeholder="생일" name="birthday" id="birthday" value="<sec:authentication property="principal.birthday"/>" />
<input type="email" class="form-control" placeholder="이메일" name="email" id="email" value="<sec:authentication property="principal.email"/>"/>
<input type="tel" class="form-control" placeholder="전화번호 (예:01012345678)" name="phone" id="phone" maxlength="11" value="<sec:authentication property="principal.phone"/>" required/>
<input type="text" class="form-control" placeholder="주소" name="address" id="address" value="<sec:authentication property="principal.address"/>"/>
<sec:authentication> 태그의 property를 이용하여 principal를 통해 인증된 사용자 정보에 대해 접근하여 값을 가져와주었습니다.
select 혹은 checkbox 등 값을 비교해야하는 상황에서는 principal를 변수로 저장하여 사용하는 것이 편리합니다.
<sec:authentication property="principal" var="loginMember" scope="page"/>
<select class="form-control" name="gender" required>
<option value="" disabled selected>성별</option>
<option value="M" ${loginMember.gender eq 'M' ? 'selected' : ''}>남</option>
<option value="F" ${loginMember.gender eq 'F' ? 'selected' : ''}>여</option>
</select>
<%
//Member loginMember = (Member) pageContext.getAttribute("loginMember");
Member loginMember = (Member) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String[] hobbies = loginMember.getHobby();
List<String> hobbyList = hobbies != null ? Arrays.asList(hobbies) : null;
pageContext.setAttribute("hobbyList", hobbyList);
%>
<div class="form-check-inline form-check">
취미 :
<input type="checkbox" class="form-check-input" name="hobby" id="hobby0" value="운동" ${hobbyList.contains('운동') ? 'checked' : ''}>
<label for="hobby0" class="form-check-label" >운동</label>
<input type="checkbox" class="form-check-input" name="hobby" id="hobby1" value="등산" ${hobbyList.contains('등산') ? 'checked' : ''}>
<label for="hobby1" class="form-check-label" >등산</label>
<input type="checkbox" class="form-check-input" name="hobby" id="hobby2" value="독서" ${hobbyList.contains('독서') ? 'checked' : ''}>
<label for="hobby2" class="form-check-label" >독서</label>
<input type="checkbox" class="form-check-input" name="hobby" id="hobby3" value="게임" ${hobbyList.contains('게임') ? 'checked' : ''}>
<label for="hobby3" class="form-check-label" >게임</label>
<input type="checkbox" class="form-check-input" name="hobby" id="hobby4" value="여행" ${hobbyList.contains('여행') ? 'checked' : ''}>
<label for="hobby4" class="form-check-label" >여행</label>
<sec:authentication property="principal" var="loginMember" scope="page"/>
principal를 loginMember라는 변수에 저장해주며, 기본적으로 page scope에 저장됩니다.
이를 이용하여 값을 비교할 수 있습니다.
'Java > Spring' 카테고리의 다른 글
Spring) Spring-WebSocket + SockJS (0) | 2022.09.14 |
---|---|
Spring) Spring-WebSocket (관련 설정) (2) | 2022.09.14 |
Spring) Security - DB에 있는 사용자 조회(로그인 처리), UserDetails/UserDetailsService (2) | 2022.09.11 |
Spring) Security - 로그인 후처리 (0) | 2022.09.11 |
Spring) Security - 커스텀 로그인/로그아웃 페이지, CSRF 공격대비 설정(403에러), error처리 (0) | 2022.09.11 |