본문 바로가기
PROGRAMING📚/Spring📑

Spring Security - USER/MANAGER/ADMIN 에 따른 권한 부여

Ta이니 2024. 9. 25.
728x90
반응형

권한은 user,MANAGER,ADMIN 으로 나누어지지만 이 모두는 Member 엔티티에 들어감 *USER*, *MANAGER*, *ADMIN*}

@Configuration
@EnableWebSecurity
public class SecurityConfig {
  private static final String[] AUTH_WHITElIST = {
      "/", "/sample/all"
  };
@Bean
  protected SecurityFilterChain config(HttpSecurity httpSecurity)
      throws Exception {

    // authorizeHttpRequests :: 선별적으로 접속을 제한하는 메서드
    // 모든 페이지가 인증을 받도록 되어 있는 상태
    // httpSecurity.authorizeHttpRequests(auth -> auth.anyRequest().authenticated());
    httpSecurity.authorizeHttpRequests(
        auth -> auth.requestMatchers(AUTH_WHITElIST).permitAll()
            .requestMatchers("/sample/admin/**").hasRole("ADMIN")
            .requestMatchers("/sample/member/**").access(
                new WebExpressionAuthorizationManager(
                    "hasRole('ADMIN') or hasRole('MEMBER')")
            )
            .anyRequest().authenticated());
    // formLogin()를 정의해야만 해도 자동생성된 로그인페이지로 이동가능.
    httpSecurity.formLogin(new Customizer<FormLoginConfigurer<HttpSecurity>>() {
      @Override
      public void customize(FormLoginConfigurer<HttpSecurity> httpSecurityFormLoginConfigurer) {
        httpSecurityFormLoginConfigurer
            .loginPage("/sample/login")
            .loginProcessingUrl("/sample/login")
            .successHandler(new AuthenticationSuccessHandler() {
              @Override
              public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
              }
            })
            .defaultSuccessUrl("/");
      }
    });
    // logout() 정의 안해도 로그아웃 페이지 사용 가능. 사용자 로그아웃 페이지 지정할 때사용
    httpSecurity.logout(new Customizer<LogoutConfigurer<HttpSecurity>>() {
      @Override
      public void customize(LogoutConfigurer<HttpSecurity> httpSecurityLogoutConfigurer) {
        httpSecurityLogoutConfigurer
            // logoutUrl() 설정할 경우 html action 주소 또한 같이 적용해야 함.
            // logoutUrl()으로 인해 기존 logout 주소 이동은 가능하나 기능은 사용 안됨.
            .logoutUrl("/sample/logout")
            .logoutSuccessUrl("/") // 로그아웃 후에 돌아갈 페이지 설정
            .logoutSuccessHandler((request, response, authentication) -> {
              // logout 후에 개별적으로 여러가지 상황에 대하여 적용 가능한 설정
            })
            .invalidateHttpSession(true); // 서버 세션을 무효화, false도 클라이언트측 무효화
      }
    });

    return httpSecurity.build();
  }

/* SecurityFilterChain Bean 역활 : 세션 인증 기반 방식으로 대부분의 Spring Security에 대한 설정으로 다룰수 있다. */

 // InMemory 방식으로 계정의 권한 관리
  @Bean
  public UserDetailsService userDetailsService() {
    UserDetails user1 = User.builder()
        .username("user1")
        .password("$2a$10$XGw3jOo9mQSoij4/so.6H.BtSRWpgPze6ZWMuc7ntyFFWqVNbcmBe")
        .roles("USER")
        .build();
    UserDetails member = User.builder()
        .username("member")
        .password("$2a$10$AEHcuzENZx7OLeA.s8e.t.CvhE/a/GZf.ZKTPEBIKLv8g03zChnD2")
        .roles("MEMBER")
        .build();
    UserDetails admin = User.builder()
        .username("admin")
        .password(passwordEncoder().encode("1"))
        .roles("ADMIN","MEMBER")
        .build();
    List<UserDetails> list = new ArrayList<>();
    list.add(user1);list.add(member);list.add(admin);
    return new InMemoryUserDetailsManager(list);
  }

로그인 기능

    // formLogin()를 정의만 해도 자동생성된 로그인페이지로 이동가능.
    httpSecurity.formLogin(new Customizer<FormLoginConfigurer<HttpSecurity>>() {
      @Override
      public void customize(FormLoginConfigurer<HttpSecurity> httpSecurityFormLoginConfigurer) {

      }
    });

로그아웃 기능

//logout()은 정의 안해도 로그아웃 페이지 설정 가능. 사용자 로그아웃 페이지 지정할 때 사용
    httpSecurity.logout(new Customizer<LogoutConfigurer<HttpSecurity>>() {
      @Override
      public void customize(LogoutConfigurer<HttpSecurity> httpSecurityLogoutConfigurer) {
        httpSecurityLogoutConfigurer.logoutUrl("/sample/logout")
            .logoutSuccessUrl("/index")
            .invalidateHttpSession(true); 
      }
    });
@SpringBootTest
class ClubMemberRepositoryTests {
  @Autowired
  private ClubMemberRepository clubMemberRepository;
  @Autowired
  private PasswordEncoder passwordEncoder;
  @Test
  public void insertDummies(){
    IntStream.rangeClosed(1,100).forEach(i -> {
      ClubMember clubMember = ClubMember.builder()
          .email("user"+i+"@a.a")
          .name("사용자"+i)
          .fromSocial(false) //구글 로그인 처리
          .password(passwordEncoder.encode("1")) //비밀번호 1로 통일
          .build();
      clubMember.addMemberRole(ClubMemberRole.USER);
      if(i>80){clubMember.addMemberRole(ClubMemberRole.MANAGER);}
      if(i>90){clubMember.addMemberRole(ClubMemberRole.ADMIN);}
      clubMemberRepository.save(clubMember);
    });
  }
}
public interface ClubMemberRepository extends JpaRepository<ClubMember, String> {

  //attributePaths 에 정의된 속성은 eager(즉각적)로 패치하고, 나머지는 lazy 패치
  @EntityGraph(attributePaths = {"roleSet"}, type = EntityGraph.EntityGraphType.LOAD)
  @Query("select m from ClubMember m where m.email=:email and m.fromSocial=:social")
  Optional<ClubMember> findByEmail(String email , boolean social);

}
  @Test
  public  void testRead(){
    Optional<ClubMember>result = clubMemberRepository.findByEmail("user100@a.a",false);
    if(result.isPresent()) System.out.println(result.get());
  }

로그인 정보에 대한 정보를 전달하기 위한 DTO

@Log4j2
@Getter
@Setter
public class ClubMemberAuthDTO extends User {
  private Long cno;
  private String email;
  private boolean fromSocial;

  public ClubMemberAuthDTO(Long cno, String username, String password,
                           String email, boolean fromSocial,
                           Collection<? extends GrantedAuthority> authorities)
  {
    super(username, password, authorities);
    this.cno = cno;
    this.email = email;
    this.fromSocial = fromSocial;
  }
}

@Log4j2
@Service
public class ClubUserDetailsService implements UserDetailsService {
  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    log.info("ClubMemberUser......",username);
    return null;
  }
}

@Log4j2
@Service
@RequiredArgsConstructor
public class ClubUserDetailsService implements UserDetailsService {
  private final ClubMemberRepository clubMemberRepository;

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    log.info("ClubMemberUser.........", username);
    Optional<ClubMember> result = clubMemberRepository.findByEmail(username, false);
    if (!result.isPresent()) throw new UsernameNotFoundException("Check Email or Social");
    ClubMember clubMember = result.get();

    //String username, String password,
    //                           Long cno, boolean fromSocial,
    //                           Collection<? extends GrantedAuthority> authorities
    ClubMemberAuthDTO clubMemberAuthDTO = new ClubMemberAuthDTO(
        clubMember.getEmail(), clubMember.getPassword(), clubMember.getCno(),
        clubMember.isFromSocial(),
        clubMember.getRoleSet().stream().map(
            clubMemberRole -> new SimpleGrantedAuthority(
                "ROLE_" + clubMemberRole.name())).collect(Collectors.toList())
    );
    clubMemberAuthDTO.setName(clubMember.getName());
    clubMemberAuthDTO.setFromSocial(clubMember.isFromSocial());
    log.info("clubMemberAuthDTO >> ", clubMemberAuthDTO.getCno());
    return clubMemberAuthDTO;
  }
}

Spring Security 가 자동적으로 login 페이지를 생성해주고 관리해줌

 

Spring Security : 사용자의 UserName 이 중요하다

이름을 가지고 SpringSecurity 에서 내부적으로 처리를 함

 

Security Context = Login SuccesHandler

//불편해서 사용하지 않음

*UserDetailsSevrvice ⇒ InMemory방식으로 계저의 권한을 관리함

→loadUserName UserDetails 안에 불러온 데이터의 값을 저장해줌

 

 @Bean
  public UserDetailsService userDetailsService() {
    UserDetails user1 = User.builder()
        .username("user1")
        .password("$2a$10$XGw3jOo9mQSoij4/so.6H.BtSRWpgPze6ZWMuc7ntyFFWqVNbcmBe")
        .roles("USER")
        .build();
    UserDetails member = User.builder()
        .username("member")
        .password("$2a$10$AEHcuzENZx7OLeA.s8e.t.CvhE/a/GZf.ZKTPEBIKLv8g03zChnD2")
        .roles("MEMBER")
        .build();
    UserDetails admin = User.builder()
        .username("admin")
        .password(passwordEncoder().encode("1"))
        .roles("ADMIN", "MEMBER")
        .build();
    List<UserDetails> list = new ArrayList<>();
    list.add(user1);
    list.add(member);
    list.add(admin);
    return new InMemoryUserDetailsManager(list);
  }

 

userDetailsService 의 역할: DB 접근 방식으로UserDetailsService(인증 관리 객체) 사용

Authentication(in Memory 방식)

@Service @RequireArgsConstructor

clubMemberRepository.findByEmail(username); 에서 이름을 찾음
이름이 있으면 DB에서 해당하는 user의 이름을 가져와서 pw에 작성된 암호화된 비번과 비교를 해서 해당하는 정보를 가져옴

 

  @Bean
  PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }
@Log4j2
@Service
@RequiredArgsConstructor //DB접근방식으로 UserDetailsService(인증 관리 객체) 사용
public class ClubUserDetailsService implements UserDetailsService {
  private final ClubMemberRepository clubMemberRepository;

  @Override
  // DB에 있는 것 확인 된후,User를 상속받은 ClubMemberAuthDTO에 로그인정보를 담음=>세션
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    log.info("ClubMemberUser.........", username);
    Optional<ClubMember> result = clubMemberRepository.findByEmail(username);
    if (!result.isPresent()) throw new UsernameNotFoundException("Check Email or Social");
        if (!result.isPresent()) throw new UsernameNotFoundException("Check Email or Social");
    ClubMember clubMember = result.get(); //DB로 부터 검색한, Entity
    // 엔티티를 세션으로 담기위해 만든 ClubMemberAuthDTO
    ClubMemberAuthDTO clubMemberAuthDTO = new ClubMemberAuthDTO(
        clubMember.getEmail(), clubMember.getPassword(), clubMember.getCno(),
        clubMember.isFromSocial(),
        clubMember.getRoleSet().stream().map(
            clubMemberRole -> new SimpleGrantedAuthority(
                "ROLE_" + clubMemberRole.name())).collect(Collectors.toList())
    );
    clubMemberAuthDTO.setName(clubMember.getName());
    clubMemberAuthDTO.setFromSocial(clubMember.isFromSocial());
    log.info("clubMemberAuthDTO >> ", clubMemberAuthDTO.getCno());
    return clubMemberAuthDTO;
  }
}

 

public UserDetails loadUserByUsername(String username) 이부분은 Spring Security 에서 제공해줌

 

비밀번호를 따로 비교하지 않아도 됨, 여기서 알아서 해줌

 

 

전송 될 때 name으로 값을 넘겨줌
Optional<ClubMember> result = clubMemberRepository.findByEmail(username, false);

ClubMember에 Email 이 존재하면 ClubMember clubMember = result.get(); 에 결과를 담아줌

DB에 있는 것이 확인 되고 , User를 상속 받은 ClubMemberAuthDTO에 로그인 정보를 담음 ⇒ 세션

@Log4j2
@Getter
@Setter
@ToString
public class ClubMemberAuthDTO extends User {
  private Long cno;
  private String email;
  private String name;
  private boolean fromSocial;

  public ClubMemberAuthDTO(String username, String password,
                           Long cno, boolean fromSocial,
                           Collection<? extends GrantedAuthority> authorities) {
    super(username, password, authorities);
    this.cno = cno;
    this.email = username;  //★UserDetails에서 username은 email로 기준하기 때문.
    this.fromSocial = fromSocial;
  }
}

 

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.tymeleaf.orf/extras/spring-security">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<h1>For Manager</h1>
<div sec:authrize ="hasRole('USER')" >Has USER_ROLE</div>
<div sec:authrize ="hasRole('MANAGER')" >Has USER_MANAGER</div>
<div sec:authrize ="hasRole('ADMIN')" >Has USER_ADMIN</div><br>
<div sec:authrize ="isAuthenicated('ADMIN')" >
<!--  로그인했을 때만 보여짐-->
  Authenticated memer can just see this page.
</div>
<div>
  아래의 태그들은 로그인 되었을 땜나 사용가능.<br>
  Authenticated username :<span sec:authentication ="name"></span><br>
  Authenticated user role :<span sec:authentication ="principal.authorities"></span><br>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.w3.org/1999/xhtml">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<h1>For ALL</h1>
<div sec:authrize ="hasRole('USER')" >Has USER_ROLE</div>
<div sec:authrize ="hasRole('MANAGER')" >Has USER_MANAGER</div>
<div sec:authrize ="hasRole('ADMIN')" >Has USER_ADMIN</div><br>
<fieldset sec:authrize ="isAuthenicated()" >
  <!--  로그인했을 때만 보여짐-->
  <legend>
    Authenticated memer can just see this page.
  </legend>
  <div>
    아래의 태그들은 로그인 되었을 때만 사용가능.<br>
    만약 fieldset 태그의 isAuthenticated 가 없이 사용한다면 에러발생.<br>
    에러와 함께 로그인하는 페이지로 연결됨 <br>
    Authenticated username :<span sec:authentication ="name"></span><br>
    Authenticated user role :<span sec:authentication ="principal.authorities"></span><br>
  </div>
</fieldset>
Authenticated username :<span sec:authentication ="name"></span><br>
Authenticated user role :<span sec:authentication ="principal.authorities"></span><br>

</body>
</html>

 

728x90
반응형

'PROGRAMING📚 > Spring📑' 카테고리의 다른 글

JSP(JavaServer Pages)  (1) 2024.09.25
Spring : 구글 로그인 세션 만들기  (1) 2024.09.25
SecuritConfig -코드 해석  (0) 2024.09.25
[Spring]::Startbootstrap - Simple Sidebar 사용하기  (0) 2024.07.11
[Spring]::Fragments  (0) 2024.07.11

댓글