▸Spring Security

스프링 Security_로그인_Principal 객체 [7/9]

코데방 2020. 3. 30.
728x90
- Develop OS : Windows10 Ent, 64bit
- WEB/WAS Server : Tomcat v9.0
- DBMS : MySQL 5.7.29 for Linux (Docker)
- Language : JAVA 1.8 (JDK 1.8)
- Framwork : Spring 3.1.1 Release
- Build Tool : Maven 3.6.3
- ORM : Mybatis 3.2.8

 

커스터마이징 순서대로 총 9개의 포스팅으로 나눠져 있습니다. 순서대로 보면 쉽게 적용할 수 있습니다.

 

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_기본 컨텍스트 설정 [1/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_DB 연동 로직 작성 [2/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_로그인 실패 대응 로직 [3/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_로그인 성공 대응 로직 [4/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_DB 패스워드 암호화 [5/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_암호화된 DB 패스워드로 인증 [6/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_Principal 객체 [7/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_자동 로그인(Remember-me) [8/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_security 태그 라이브러리 [9/9]

 

 

security 태그에서도 사용하고 컨트롤러에서도 자동으로 전달해주는 Principal 객체에 대한 내용입니다. UserDetails 객체부터 시작해 다 엮여있는 구조라 정리가 한 번 필요할 것 같습니다.

 

이제까지 커스터마이징하며 살펴본 스프링 Security 인증 과정은 아래와 같습니다. 이번 글에서는 로직 구성보다는, 전달되는 인증 데이터(사용자 데이터)에 대해 중점을 둬보겠습니다.

 

 


 

 

[ UsernamePasswordAuthenticationToken의 상속 구조 ]

 

UsernamePasswordAuthenticationToken 클래스는 Authentication 인터페이스를 구현한 클래스로, 결국 위 구조에서 우리가 인증이 완료된 후 최종적으로 스프링 Security에게 전달해주는 구현체입니다. 이 클래스의 상속 구조는 아래와 같습니다.

 

보시면 알겠지만 이 실제 구현 클래스의 최상위 인터페이스가 바로 Principal 입니다. 이 말은 즉, 결국 컨트롤러나 JSP의 태그라이브러리 등 하나의 세션에서 가져다 쓸 수 있는 Principal이나 Athentication 객체는 서로 다른게 아니라 우리가 Provider를 커스터마이징하면서 리턴시킨 객체와 동일하다는 것입니다.

 

다만 자동 로그인을 사용할 때는 RememberMeAuthenticationToken 타입의 객체가 반환됩니다. 하지만 이 클래스 또한 Authentication을 구현하였으므로 기본적인 사용법이나 구조는 거의 비슷합니다. 이 부분은 커스터마이징을 하게 되면 다시 다루도록 하겠습니다.

 

 

 

 


 

 

[ UsernamePasswordAuthenticationToken 생성자 ]

 

따라서 로그인 인증 후 사용자 정보를 세션 등에 따로 보관해두지 않았다면, 우리가 꺼내쓸 수 있는 객체는 결국 이 구현 클래스밖에 없다는 의미가 됩니다.

 

여러 글들을 봤는데 대부분 UserID의 생성자로 String 형태의 ID 정보를 넣는 것 같습니다. 하지만 아래 코드에서 보다시피 생성자는 Object 타입으로 되어 있습니다. ID만 받아서 사용하는 구조가 아니라는 것입니다.

 

가장 기본적인 구조로 ID와 권한만 필요하다면 충분히 아래와 같은 코드로 작성해도 상관이 없겠으나, 만약 다른 정보들을 UserDetails에 넣어 전달했다면 사용할 수가 없게 되는 것입니다. 물론 따로 세션에 저장해서 사용한다면 가능하지만, 컨트롤러에서 자동으로 전달을 받는다거나 하는 지원을 받을 수 없는데다가 설계 구조를 이해하고 쓰는 것과 이해하지 못하고 쓰는 것은 차이가 있을 것 같습니다.

    public UsernamePasswordAuthenticationToken
    (Object principal, Object credentials, 
    Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true); // must use super, as we override
    }
	// String userId를 넣어준 경우
	Authentication newAuth = new UsernamePasswordAuthenticationToken(
				userId, null, userDetails.getAuthorities());

 

 

 

따라서 UserDetails 객체를 생성자로 제공해주는게 좋습니다. Object 타입이기 때문에 별도의 VO 객체를 사용해도 무방합니다만, 이왕 이미 잘 만들어져있는 인터페이스를 활용하는 것이 가장 안정적일 듯합니다. 남들이 보기도 쉽구요. 

 

Object Credentials 파라미터도 필요하면 객체를 넣을 수 있지만 저는 아직 쓸일이 없어서 null 처리했습니다. 로직 상 인증 관련 객체가 하나 더 담길 일이 있으면 사용해주면 될 듯합니다. 

 

	// userDetails 객체를 넣어줌
	Authentication newAuth = new UsernamePasswordAuthenticationToken(
				userDetails, null, userDetails.getAuthorities());

 

 


 

 

[ Principal 객체 ]

 

컨트롤러의 처리기 메소드에서 자동 파라미터로 주입받을 수 있는 타입 중 하나입니다. 다만 가장 구현체의 최상위 인터페이스이기 때문에 이 타입으로 받으면 사용할만한 메소드가 getName() 정도밖에 없습니다. 그냥 ID 정보만 가져다 사용할 수 있다고 보면 됩니다.

 

	/* 메인 페이지 */
	@RequestMapping("/")
	public String main(Principal principal) {

		if (principal != null) {
			System.out.println("타입정보 : " + principal.getClass());
			System.out.println("ID정보 : " + principal.getName());
		}
		return "main";
	}
   

 

 


 

 

[ Authentication 객체 ]

 

UsernamePasswordAuthenticationToken 구현체 및 세션 정보를 보관하는 객체에서 필요한 정보를 뽑아내는 메소드를 가지고 있습니다. 따라서 실제로 인증 정보를 사용하기 위해 사용되는 객체 타입이 바로 Authentication 이라고 보면 됩니다.

 

* Object getPrincipal() : 첫 번째 생성자로 주입한 객체 반환

* Object getCredentials() : 두 번째 생성자로 주입한 객체 반환 

* Collection<? extends GrantedAuthority> getAuthorities() : 세 번째 생성자인 권한 리스트 객체 반환

* Object getDetails() : 세션정보를 가진 WebAuthenticationDetails 객체 반환

 

getDetails()의 경우도 타입이 Object로 되어 있는 것으로 보아 커스터마이징 할 경우를 고려한 것 같습니다.

 

	/* 메인 페이지 */
	@RequestMapping("/")
	public String main(Authentication authentication) {

		if (authentication != null) {
			System.out.println("타입정보 : " + authentication.getClass());
			
			// 세션 정보 객체 반환
			WebAuthenticationDetails web = (WebAuthenticationDetails)authentication.getDetails();
			System.out.println("세션ID : " + web.getSessionId());
			System.out.println("접속IP : " + web.getRemoteAddress());

			// UsernamePasswordAuthenticationToken에 넣었던 UserDetails 객체 반환
			UserDetails userVO = (UserDetails) authentication.getPrincipal();
			System.out.println("ID정보 : " + userVO.getUsername());
		}
		
		return "main";
	}

 

728x90

댓글

💲 추천 글