본문 바로가기

Server Programming/Spring Boot Backend Programming

[Spring 부트 - 운동 클럽 프로젝트] 1. 스프링 시큐리티 연동 (2) CSRF 와 접근 제한 설정

728x90
반응형

진행 순서

  1. CSRF란 무엇인가?
  2. REST방식의 보안 설정을 이용하기 위해, CSRF 토큰 비활성화 설정
  3. 스프링 시큐리티의 logout 설정
  4. 프로젝트에 스프링 시큐리티 연동을 위한 JPA 처리
  5. ClubMemberRepository에서 일반 사용자와 소셜 로그인 사용자 구분 메소드 처리
  6. ClubMemberRepository를 이용해 회원 처리 하는 로직 작성
    일반적인 로그인 처리) 회원 정보로, DB를 조회하고, 올바른 데이터가 있으면 세션이나 쿠키로 처리
    스프링 시큐리티의 경우) User라는 현재 로그인한 사용자 객체를 생성해 사용
    1. 스프링 시큐리티의 User클래스를 이용한 UserDetails 인터페이스 작성
    2. User클래스가 필요한 정보를 제공하기 위한, ClubAuthMemberDTO클래스 작성
    3. ClubAuthMemberDTO를 사용하는 UserDetailsService 인터페이스 작성
    4. 임시로 작성한 스프링 시큐리티 설정을 제거 - InMemoryUserDetailsManager 메서드

1. CSRF

: CSRF는 크로스 사이트 요청 위조 공격으로, 사이트간 요청을 위조하는 공격이다. 

CSRF를 방지하기 위해 임의 값을 만들어 GET방식을 제외한 모든 요청방식에 포함시켜야 정상적인 동작이 가능하다.

 

2. REST방식의 보안 설정을 이용하기 위해, CSRF 토큰 비활성화 설정

 

CSRF 설정

  1. 스프링 시큐리티는 크로스 사이트 요청 위조 공격 방어를 위한 설정을 필수적으로 요구한다.
  2. 서버에서 받아들이는 정보를 사전 조건을 검증하지 않는 단점을 이용한 공격
  3.  hidden값으로 숨겨진 CSRF 토큰값을 이용해 CSRF을 방지한다.

CSRF 토큰

  1. 세션당 하나씩 생성되며, <form>태그 방식에서는 권장되고 REST방식에서는 발행하지 않을 때가 많다.
  2. REST 방식을 사용할 때 토큰 발행하지 않도록 csrf().disable()을 이용해 설정한다.

 

CSRF 토큰 비활성화

: HttpSecurity의 csrf() 메서드로 CSRF 토큰을 발행하지 않도록 설정

package com.club.boot5.config;

import lombok.extern.log4j.Log4j2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@Log4j2
public class SecurityConfig {

  //중략
  
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.authorizeHttpRequests((auth) -> {
            auth.antMatchers("/sample/all").permitAll();
            auth.antMatchers("/sample/member").hasRole("USER");
        });

        //인가/인증 절차에서 문제 발생시 권한 획득을 유도하는 페이지 리턴
        http.formLogin();
        //csrf 토큰 비활성화
        http.csrf().disable();

        return http.build();
    }

}

 

3. 스프링 시큐리티의 logout 설정

package com.club.boot5.config;

import lombok.extern.log4j.Log4j2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@Log4j2
public class SecurityConfig {

  //중략
  
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.authorizeHttpRequests((auth) -> {
            auth.antMatchers("/sample/all").permitAll();
            auth.antMatchers("/sample/member").hasRole("USER");
        });

        //인가/인증 절차에서 문제 발생시 권한 획득을 유도하는 페이지 리턴
        http.formLogin();
        //csrf 토큰 비활성화
        http.csrf().disable();
        //스프링 시큐리티에서 제공하는 로그아웃 처리
        http.logout();
        return http.build();
    }

}

 

CSRF 토큰 사용시 POST방식으로만 로그아웃을 처리한다.

-> CSRF 토큰 사용시 /logout URL 호출한 것과 같이 구성된 화면을 보여준다.

 

CSRF 토큰 사용하지 않으면 GET방식으로 로그아웃을 처리할 수 있다.

-> 페이지 이동없이 로그아웃 처리

 

logout() 설정

  1. logoutURL()
  2. logoutSuccessUrl()
  3. 스프링 시큐리티가 사용하는 HttpSession의 invalidatedHttpSession()과 deleteCookies()로 쿠키나 세션 무효화 가능

 


4. 프로젝트에 스프링 시큐리티 연동을 위한 JPA 처리

: 소셜 로그인의 이메일을 아이디로 구성한 회원 처리

 

ClubMember 회원 정보 구성

  • 이메일(아이디 역할)
  • 패스워드
  • 이름(닉네임)
  • 소셜 가입 여부(소셜 로그인으로 회원 가입된 경우(다음 장에서 사용))
  • 기타 (등록일/수정일)

접근 제한을 위한 권한 처리

  • USER : 일반 회원
  • MANAGER : 중간 관리 회원
  • ADMIN : 총괄 관리자

ClubMemberRole의 역할이 부족하기 때문에, @ElementCollection을 이용해 PK 없이 구성

: ClubMember와 ClubMemberRole의 1:N 관계지만 부족한 ClubMemberRole의 역할 대체

-> 한 회원이 여러 개의 권한을 가질 수 있다.

 

ClubMember 클래스와 ClubMemberRole 열거형 작성

 

package com.club.boot5.entity;

import lombok.*;
import org.springframework.beans.factory.annotation.Autowired;

import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import java.util.HashSet;
import java.util.Set;

@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString
public class ClubMember extends BaseEntity{
    @Id
    private String email;

    private String password;

    private String name;

    //소셜 로그인 여부
    private boolean fromSocial;


    //회원 권한을 위한 타입값 처리 -> @ElementCollection을 이용해 별도의 키 없이 컬렉션 데이터 처리한다.
    //: 한 회원이 하나의 권한만을 가지는 것이 아니기 때문에 Set 자료구조로 설정하고, PK가 없는 테이블로 설정
     
    @ElementCollection(fetch = FetchType.LAZY)
    //객체 생성시 기본값으로 가지게 설정
    @Builder.Default
    private Set<ClubMemberRole> roleSet=new HashSet<>();

    public void addMemberRole(ClubMemberRole clubMemberRole){
        roleSet.add(clubMemberRole);
    }
}

 

package com.club.boot5.entity;

public enum ClubMemberRole {
    USER, MANAGER, ADMIN
}

 

데이터를 생성하는 테스트 코드 작성

package com.club.boot5.repository;

import com.club.boot5.entity.ClubMember;
import com.club.boot5.entity.ClubMemberRole;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.stream.IntStream;

@SpringBootTest
public class ClubMemberTests {
    
    @Autowired
    private ClubMemberRepository clubMemberRepository;
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Test
    public void insertDummies(){
        //1-80 USER
        //81-90 USER,MANAGER
        //91-100, USER,MANAGER,ADMIN
        IntStream.rangeClosed(1,100).forEach(i->{
            ClubMember clubMember=ClubMember.builder()
                    .email("user"+i+"@naver.com")
                    .name("사용자"+i)
                    .fromSocial(false)
                    .password(passwordEncoder.encode("1111"))
                    .build();
            //기본 권한
            clubMember.addMemberRole(ClubMemberRole.USER);
            if(i>80){
                clubMember.addMemberRole(ClubMemberRole.MANAGER);
            }

            if(i>90){
                clubMember.addMemberRole(ClubMemberRole.ADMIN);
            }
            clubMemberRepository.save(clubMember);
        });
    }
            
}

5. ClubMemberRepository에서 일반 사용자와 소셜 로그인 사용자 구분 메소드 처리

package com.club.boot5.repository;

import com.club.boot5.entity.ClubMember;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import javax.swing.text.html.Option;
import java.util.Optional;

public interface ClubMemberRepository extends JpaRepository<ClubMember, String > {

    //회원 데이터 조회 테스트
    //: 지연 로딩으로 조회할 경우, 객체 생성을 하고, 특정 데이터를 위해 새로운 객체를 생성해야 할때 
    @EntityGraph(attributePaths = {"roleSet"}, type = EntityGraph.EntityGraphType.LOAD)
    @Query("select m from ClubMember m where m.fromSocial=:social and m.email=:email")
    //사용자 이메일과 소셜로 추가된 회원 여부를 선택해서 동작하도록 설계 -> @EntityGraph를 이용해 외부조인으로 ClubMemberRole처리 되도록
    Optional<ClubMember> findByEmail(@Param("email") String email, @Param("social") boolean social);
    
}

 

@EntityGraph

: 평소에는 LAZY로 쓰지만, 특정 시나리오에서는 한번에 패치하는 방법으로, LAZY 패치타입으로 연관관계의 entity를 n+1 문제 없이 한번에 가져온다.

: 해당 데이터를 가져오는 타입을 지정할 수 있다.

더보기

2가지 타입이 존재하는데

EntityGraph.EntityGraphType.FETCH

EntityGraph.EntityGraphType.LOAD

  • FETCH: entity graph에 명시한 attribute는 EAGER로 패치하고, 나머지 attribute는 LAZY로 패치
  • LOAD: entity graph에 명시한 attribute는 EAGER로 패치하고, 나머지 attribute는 entity에 명시한 fetch type이나 디폴트 FetchType으로 패치
    (기본 값 : @OneToMany - LAZY, @ManyToOne - EAGER)

 

일반 사용자와 소셜 로그인 사용자 구분 메소드 테스트

//일반 로그인 사용자와 소셜 로그인 사용자 구분 메서드 테스트
@Test
public void testRead(){
    Optional<ClubMember> result = clubMemberRepository.findByEmail("user95@naver.com", false);

    ClubMember clubMember = result.get();
    System.out.println(clubMember);
}
ClubMember(email=user95@naver.com, password=$2a$10$p4M9UgV1PNn8y.DO1zjcz.5CHZRfE73E9q8hL5hlH4hwp4EKARWlu, name=사용자95, fromSocial=false, roleSet=[ADMIN, MANAGER, USER])

: 테스트 결과 회원정보 뿐만 아니라 권한도 함께 로딩하는 것을 알 수 있다.

 

 


6. ClubMemberRepository를 이용해 회원 처리 하는 로직 작성

  • 회원이나 계정에 스프링 시큐리티에서 사용하는 상수인 User를 이용해 접근
  • 회원을 구별하는 식별 데이터로 회원 아이디 대신 username을 사용
    : 동일하게 문자열로 처리하지만 회원의 이름이 아닌 id에 해당한다.
  • 인증에 username과 password를 동시에 사용하지 않는다.
    : UserDetailsService를 이용해 회원의 존재를 먼저 가져온 이후, password를 확인해 결과를 반환한다.
    -> password 오류일 경우 Bad Credential
  • 인증 단계를 통과할 경우, 원하는 자원에 접근할 수 있는 권한을 확인하는 인가 단계를 수행한다.
    -> Access Control에 걸릴 경우 Access Denied 결과 반환

 

 

UserDetailsService가 가지고 있는 loadUserByUsername() 메서드

: UserDetails라는 리턴타입으로 username이라는 회원 아이디와 같은 식별 값으로 회원 정보를 가져온다.

 

loadUserByUsername의 리턴타입 UserDetails의 구성

  1. getAuthorites() : 사용자가 가지는 권한 정보
  2. getPassword() :    인증을 마무리하기 위한 패스워드 정보
  3. getUsername()   : 인증에 필요한 아이디와 같은 정보
  4. 계정 만료 여부        :더이상 사용 불가능 계정 정보
  5. 현재 계정 잠김 여부 :현재 계정의 잠김 여부

ClubMember를 다루는 UserDetails를 처리하기 위한 방법

  1. 기존 DTO 클래스에 UserDetails 인터페이스 구현
  2. DTO와 같은 개념으로 별도의 클래스를 구성해 이를 활용

: 별도의 클래스를 구성하는 방법을 택하는데, 인터페이스를 구현한 별도의 클래스가 존재하므로 이를 사용하는 방법

 

인터페이스를 구현해둔 구현 클래스

  1. InetOrgPerson
  2. LdapUserDetailsImpl
  3. Person
  4. User

 

User 클래스 생성자

User(String username, String, password, Collection<? extends GrantedAuthority> authorities)

 

(1) 스프링 시큐리티의 userdetails패키지의 User 클래스를 상속하는 DTO

: ClubAuthMemberDTO

-> User클래스를 상속하므로, User클래스의 생성자를 호출해야 한다.

: 부모의 생성자 호출이 필요한 이유는 User클래스에 사용자 정의 생성자가 존재하기 때문에 반드시 호출해야한다.

 

 

User 클래스를 상속해 작성한 ClubAuthMemberDTO

:부모의 생성자를 호출 필수

package com.club.boot5.dto;

import lombok.Getter;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

import java.util.Collection;

@Log4j2
public class ClubAuthMemberDTO extends User {
    public ClubAuthMemberDTO(String username, String password, Collection<? extends GrantedAuthority> authorities){
        
        
        //부모의 클래스에 사용자 정의 생성자가 존재하기 때문에 반드시 별도로 호출해야한다.
        super(username, password, authorities);
    }
}

 

필요한 멤버변수를 선언한 ClubAuthMemberDTO

package com.club.boot5.dto;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

import java.util.Collection;

@Log4j2
@Getter
@Setter
@ToString
//DTO 역할 수행 + 스프링 시큐리티에서 인가/인증
public class ClubAuthMemberDTO extends User {
    private String email;
    private String name;
    private boolean fromSocial;


    //필요한 속성인 소셜로그인 체크 여부 속성을 추가한다.
    public ClubAuthMemberDTO(String username, String password, boolean fromSocial,Collection<? extends GrantedAuthority> authorities){

        //email -> username
        //name -> name
        //fromSocial -> fromSocial
        //password는 부모 클래스 사용하므로 변수로 선언하지 않는다.

        //따라서, email과 fromSocial은 별도로 setter 작성

        //부모의 클래스에 사용자 정의 생성자가 존재하기 때문에 반드시 별도로 호출해야한다.
        super(username, password, authorities);

        this.email=username;
        this.fromSocial=fromSocial;

    }
}

 

 

(2) UserDetailsService 인터페이스 작성

: AuthenticationManager가 UserDetailsService를 호출해서 사용자 정보를 가져온다.

 

-> JPA로 사용자 정보를 가져오도록 작성한다면, UserDetailsService가 이용하는 구조로 작성

 

ClubUserDetailsService를 @Service 어노테이션을 이용해 빈으로 등록

package com.club.boot5.security.service;

import lombok.extern.log4j.Log4j2;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Log4j2
//@Service 어노테이션으로 자동으로 스프링에서 빈으로 처리
@Service
public class ClubUserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {

    //UserDetailsService의 loadUserByUsername 메서드 오버라이딩
    //: 별도의 처리 없이 로그 기록
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
        log.info("ClubUserDetailsService loadUserByUsername "+username);

        return null;
    }
}

 

ClubUserDetailsService를 빈으로 등록해, 스프링 시큐리티에서 UserDetailsService로 인식하므로
임시로 직접 설정한 메서드를 사용하지 않도록 설정

    //인메모리를 통해 인증절차를 수행하는 메서드를 빈으로 등록
    // : ClubUserDetailsService를 빈으로 등록해, 스프링 시큐리티에서 UserDetailsService로 인식하므로
    // 임시로 직접 설정한 메서드를 사용하지 않도록 설정
//    @Bean
//    public InMemoryUserDetailsManager userDetailsService() {
//        UserDetails user = User.builder()
//                .username("user1")
//                //.password(passwordEncoder().encode("1111"))
//                .password("$2a$10$wO8xgibV2LVtJScXhtFLtOXkM4jppH9/gjPIsVjsHWFkQ6ukIzw4.")
//                .roles("USER")
//                .build();
//
//        log.info("userDetailsService............................");
//        log.info(user);
//
//        return new InMemoryUserDetailsManager(user);
//    }

:실제 계정 로그인 처리 완료

 

시큐리티에서 회원을 인증하는 방식

  1. 회원이나 계정을 나타내는 User 클래스를 상속한 ClubAuthMemberDTO로 객체를 생성한다.
  2. ClubAuthMemberDTO를 이용해 사용자 정보를 가져오는데, username에 해당하는 email을 전달한다.
  3. User클래스를 상속하는 UserDetailsService의 loadUserByUsername 메서드를 오버라이딩해 회원이 맞다면 회원 정보를 가져온다.
  4. 구현한 인증 단계를 통해 ClubUserDetailsService와 ClubMemberRepository를 연동한다.
    1. ClubUserDetailsService의 loadUserByUsername메서드를 오버라이딩해 인증 단계 수행
    2. clubMemberRepository의 findByEmail을 통해 회원정보를 가져와 Optional<ClubMember> result에 삽입
    3. throw new UsernameNotFoundException으로 존재하지 않는 이메일일 경우의 에러 처리
    4. 존재하는 회원정보를 ClubMember에 삽입해서, 이를 이용해 인증단계를 거치기 위해 만든 DTO 객체생성
    5. DTO상태의 ClubAuthMemberDTO를 리턴

 

(3) 구현한 인증 단계를 통해 ClubUserDetailsService와 ClubMemberRepository를 연동

 

ClubMember 멤버변수

  • private String email;
  • private String password;
  • private String name;
  • private boolean fromSocial;
  • private Set<ClubMemberRole> roleSet

ClubAuthMemberDTO 멤버변수

  • private String email;
  • private String name;
  • private boolean fromSocial;
  • User클래스 상속한 생성자 -> 로그인에 직접 사용하는 멤버들
    • super(username, password, authorities);
    • this.email=username;
    • this.fromSocial=fromSocial;

변경 전의 ClubUserDetailsService

package com.club.boot5.security.service;

import lombok.extern.log4j.Log4j2;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Log4j2
//@Service 어노테이션으로 자동으로 스프링에서 빈으로 처리
@Service
public class ClubUserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {

    //UserDetailsService의 loadUserByUsername 메서드 오버라이딩
    //: 별도의 처리 없이 로그 기록
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
        log.info("ClubUserDetailsService loadUserByUsername "+username);

        return null;
    }
}

 

변경 후의 ClubUserDetailsService

package com.club.boot5.security.service;

import com.club.boot5.entity.ClubMember;
import com.club.boot5.repository.ClubMemberRepository;
import com.club.boot5.security.dto.ClubAuthMemberDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.Optional;
import java.util.stream.Collectors;

@Log4j2
//@Service 어노테이션으로 자동으로 스프링에서 빈으로 처리
@Service
@RequiredArgsConstructor
public class ClubUserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {

    private final ClubMemberRepository clubMemberRepository;

    //UserDetailsService의 loadUserByUsername 메서드 오버라이딩
    //: 별도의 처리 없이 로그 기록
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("ClubUserDetailsService loadUserByUsername " + username);

        //이메일로 회원 찾을 경우 -> 이름과 소셜로그인 유무를 파라미터로 전달한다.
        Optional<ClubMember> result = clubMemberRepository.findByEmail(username, false);
        if (!result.isPresent()) {
            throw new UsernameNotFoundException("Check Email or Social");
        }
        ClubMember clubMember = result.get();

        log.info("----------------");
        log.info(clubMember);

        //빌더 패턴을 사용하지 않는 이유 : 모든 속성이 꼭 필요하므로
        ClubAuthMemberDTO clubAuthMember = new ClubAuthMemberDTO(
                clubMember.getEmail(),
                clubMember.getPassword(),
                clubMember.isFromSocial(),
                //권한의 경우 한 가지의 권한만 가지지 않을 수 있으므로 스트림 이용
                clubMember.getRoleSet().stream()
                        .map(role -> new SimpleGrantedAuthority(
                                "ROLE_" + role.name()
                        )).collect(Collectors.toSet()));
        //DTO에 필요한 name, fromSocial Setter
        clubAuthMember.setName(clubMember.getName());
        clubAuthMember.setFromSocial(clubMember.isFromSocial());

        return clubAuthMember;
    }

}

 

변경점

  • ClubMemberRepository를 주입받는 구조로 변경, final 객체 의존성 주입을 위해 @RequiredArgsConstructor 처리
  • username이 실제로 ClubMember의 email이므로, ClubMemberRepository의 findByEmail()를 비소셜로그인 처리로 호출
  • 사용자 존재하지 않으면 UsernameNotFoundException으로 예외처리
  • ClubMember를 UserDetails 타입으로 처리하기 위해 ClubAuthMemberDTO로 변환
  • ClubMemberRole을 스프링 시큐리티의 SimpleGrantedAuthority로 변환
    • 변환 이름 규칙 : 'ROLE_'
    • 권한 이름의 문자열 값을 SimpleGrantedAuthority의 생성자 파라미터에 넣어주는 것으로 권한 객체 생성하는 클래스

 

(4) 사용자 정보 출력

  • 스프링 시큐리티 의존성 추가
  • 로그인 정상적으로 처리되었을 때 사용자의 정보를 화면이나 컨트롤러에 출력
  • 스프링 시큐리티의 Authentication 타입을 이용해 사용자 정보 추출
  • Authentication가 ClubAuthMemberDTO 객체이므로 해당 DTO를 이용한 출력 수행

 

브라우저에서 사용자 정보 출력

 

member.html

<h1>For Member ....................</h1>
<!--sec을 이용해 시큐리티 부분 처리하는데 사용 -->
<!-- sec:authorize는 인가 관련 정보 추출 및 제어 -->
<!-- sec:principal는 ClubAuthMemberDTO 내용을 이용-->
<div sec:authorize="hasRole('USER')">Has USER ROLE</div>
<div sec:authorize="hasRole('MANAGER')">Has MANAGER ROLE</div>
<div sec:authorize="hasRole('ADMIN')">Has ADMIN ROLE</div>

<div sec:authorize="isAuthenticated()">
  Only Authenticated user can see this Text
</div>

Authenticated username:
<div sec:authentication="name"></div>
Authenticated user roles:
<div sec:authentication="principal.authorities">

</div>

 

컨트롤러에서 사용자 정보 출력 방법

  1. SecurityContextHolder 객체 사용
  2. 직접 파라미터와 어노테이션 사용

-> 직접 @AuthenticationPrincipal 어노테이션을 사용해 처리

 

package com.club.boot5.controller;

import com.club.boot5.security.dto.ClubAuthMemberDTO;
import lombok.Getter;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@Log4j2
@RequestMapping("/sample/")
public class SampleController {

    @GetMapping("/all")
    public void exAll(){
        log.info("exAll..........");
    }

    @GetMapping("/member")
    //@AuthenticationPrincipal 로그인한 사용자의 정보를 파라메터로 받고 싶을때 사용하는 어노테이션
    //UserDetailsService에서 인가된 사용자 정보를 리턴 받아서 컨트롤러에 출력
    public void exMember(@AuthenticationPrincipal ClubAuthMemberDTO clubAuthMember){

        log.info("exMember.........");

        log.info("--------------------");

        log.info(clubAuthMember);
    }

    @GetMapping("/admin")
    public void exAdmin(){
        log.info("exAdmin..........");
    }
}

 

 

@AuthenticationPrincipal 어노테이션

: getPrincipal()메서드를 이용해 Object 타입의 반환 타입이 존재하므로, 캐스팅없이 ClubAuthMemberDTO 사용가능

728x90
반응형