ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Security + JWT 회원가입, 로그인 (1)
    Spring 2022. 10. 23. 15:55
    반응형

    해당 포스팅은 Spring Secuiry + JWT + JPA 를 이용해 회원가입, 로그인 관련 예제 입니다.

     

    환경 : Spring boot(2.5.5) + Mysql

    Spring Security 영역
    인증(Authentication)과 권한(Authorization)


    인증(Authentication) : 보호된 리소스에 접근하는 사용자에게 적절한 접근 권한이 있는지 확인하는 일련의 과정
    접근 주체(Principal) : 보호된 리소스에 접근하는 대상(사용자)
    권한(Authorization) : 인증절차가 끝난 접근 주체가 보호된 리소스에 접근 가능한지를 결정
    인가(Authorize) : 권한을 부여하는 작업

    즉, 인증은 아이디와 비밀번호를 입력 받아 로그인 하는 과정 자체를 의미하는 것이고 권한이 필요한 리소스에 접근하기 위해서는 당연히 이러한 인증 과정을 거쳐야 합니다. 스프링 시큐리는 이런 인증 매커니즘을 간단하게 만들 수 있도록 다양한 옵션들을 제공하고 있습니다.

    Spring Security 구조
    스프링 시큐리티는 주로 서블릿 필터와 이로 구성된 필터체인을 사용하고 있습니다.

     


    시작하기

     

    📌  Spring Security, Jwt Dependency 추가

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
    	<groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>

     

    📌 Entity, Repository 추가

    Roles.java

    @Entity @Table(name = "MEMBER_ROLES")
    @Getter @Setter
    public class Roles implements Serializable {
    
        @Id
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "MEMBER_ID")
        private Member member;
    
        @Enumerated(EnumType.STRING)
        @Column
        private UserRolesType roles;  // ADMIN, MEMBER
    }
    • 데이터베이스에 저장하기 위해서 직렬화 구현한 권한 클래스
    • Member Entity와 양방향 관계 설정 위해 ManyToOne 설정

     

    Member.java

    @Entity @Table(name = "MEMBER")
    @Getter @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class Member implements UserDetails {
    
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long memberId;
      
      @Column
      private String phoneNum;
      
      @Column
      private String password;
      
      @Column
      private String name;
    
      @Column
      private String birthYmd;
    
      @Enumerated(EnumType.STRING)
      @Column
      private GenderType gender;   // MAN,WOMEN
      
      @ElementCollection(fetch = FetchType.EAGER)
      @CollectionTable(
              name = "MEMBER_ROLES",
              joinColumns=@JoinColumn(name = "MEMBER_ID")
      )
      @Builder.Default
      private List<String> roles = new ArrayList<>();
      
      @Override
      public Collection<? extends GrantedAuthority> getAuthorities() {
          return this.roles.stream()
                  .map(SimpleGrantedAuthority::new)
                  .collect(Collectors.toList());
      }
      
      @Override
      public String getUsername() {
          return phoneNum;
      }
      
      @Override
      public boolean isAccountNonExpired() {
          return true;
      }
      
      @Override
      public boolean isAccountNonLocked() {
          return true;
      }
      
      @Override
      public boolean isCredentialsNonExpired() {
          return true;
      }
      
      @Override
      public boolean isEnabled() {
          return true;
      }
    }
    • 회원 정보를 담는 UserDetails를 구현한 Entity
    • 28 - 34 Line : Member Entity와 Roles Entity 매핑 컬렉션
    • 36 - 41 Line : 계정이 갖고 있는 권한을 리턴 받기 위한 Override Method
    • UserDetails : Spring Security에서 사용자의 정보는 담는 인터페이스

     

    → Spring Security에서는 해당 인터페이스 구현한 클래스를 사용자 정보로 인식하고 인증 작업을 합니다. UserDetails를 구현하게 되면 아래와 같은 메소드들이 Override 됩니다.

     

    메소드명 리턴타입 설명
    getAuthorities() Collection<? extends GrantedAuthority> 계정이 갖고있는 권한 목록을 리턴한다.
    getUsername() String 계정의 이름을 리턴한다.
    (ex : 로그인 아이디, 즉 phoneNum)
    isAccountNonExpired() boolean 계정이 만료되지 않았는지 리턴한다.
    (true: 만료 안됨)
    isAccountNonLocked() boolean 계정이 잠겨있지 않았는 지 리턴한다.
    (true: 잠기지 않음)
    isCredentialNonExpired() boolean 비밀번호가 만료되지 않았는 지 리턴한다.
    (true: 만료 안됨)
    isEnabled() boolean 계정이 활성화(사용가능)인 지 리턴한다.
    (true: 활성화)

     

    MemberRepository.java

    @Repository
    public interface MemberRepository extends JpaRepository<Member, Long> {}

     

    📌 WebSecurityConfig 추가

    Spring Security를 사용하기 위해서는 Spring Security Filter Chain을 사용해야 합니다.
    이를 위해서 WebSecurityConfigurerAdapter를 상속 받은 클래스에 @EnableWebSecurity 어노테이션을 추가해야 합니다.

     

    WebSecurityConfig.java

    import com.company.project.fo.exception.CustomAccessDeniedHandler;
    import com.company.project.fo.security.CustomAuthenticationEntryPoint;
    import com.company.project.fo.security.JwtAuthenticationFilter;
    import com.company.project.fo.security.JwtTokenProvider;
    import lombok.RequiredArgsConstructor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.builders.WebSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.config.http.SessionCreationPolicy;
    import org.springframework.security.crypto.factory.PasswordEncoderFactories;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    
    @RequiredArgsConstructor
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        private final JwtTokenProvider jwtTokenProvider;
        private final CustomAccessDeniedHandler customAccessDeniedHandler;
        private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
        
        // 암호화에 필요한 PasswordEncoder Bean 등록
        @Bean
        public PasswordEncoder passwordEncoder() {
            return PasswordEncoderFactories.createDelegatingPasswordEncoder();
        }
    
        // authenticationManager Bean 등록
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
        
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.
                    httpBasic().disable()
                        .csrf().disable()
                        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            	.and()
                        .exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint)
                        .accessDeniedHandler(customAccessDeniedHandler)        
       		.and()
                        .authorizeRequests()
                        .antMatchers("/admin/**").hasRole("ADMIN")
                        .antMatchers("/front/**").hasRole("MEMBER")
                        .antMatchers("/**").permitAll()
                    .and()
                    .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
                            UsernamePasswordAuthenticationFilter.class);
        }
        
        @Override
        public void configure(WebSecurity web) throws Exception {
            web
                    .ignoring()
                    .antMatchers("/front/auth/**")
                    .antMatchers("/favicon.ico")
    		.antMatchers("/swagger-ui.html")
                    .antMatchers("/css/**")
                    .antMatchers("/fonts/**")
                    .antMatchers("/images/**")
                    .antMatchers("/js/**");
        }
    }

     

     

    Annotation
    Configuration 해당 Class를 Configuration으로 등록
    EnableWebSecurity Spring Security 활성화

     

    JwtTokenProvider : JWT 토큰 관련 Class. 자세한 설명은 여기를 참조하세요.
    CustomAccessDeniedHandler : 접근 권한 Exception을 처리하기 위한 Custom Class. 자세한 설명은 여기를 참조하세요.
    CustomAuthenticationEntryPoint : Servlet Exception을 처리하기 위한 Custom Class. 자세한 설명은 여기를 참조하세요.  

     

     

    configure(HttpSecurity http)

    httpBasic() : Http basic Auth 기반으로 로그인 인증창. 기본 인증 로그인을 이용하지 않으면 disable
    csrf() : html tag 를 통한 공격 - api 서버 이용시 disable()
    sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) :
    시큐리티에서 세션을 생성 하지도 않고, 기존것을 사용하지 않기 위한 전략
    → JWT 토큰 방식 사용하기 위함
    addFilterBefore() : 인증을 처리하는 기본 필터 UsernamePasswordAuthenticationFilter대신 JwtAuthenticationFilter 라는 별도의 인증 로직을 가진 커스텀 필터 추가
    → jwtTokenProvider를 생성자 파라미터로 넣은 이유는 JwtAuthenticationFilter에 종속성을 추가하기 위함
    /admin/** 에 해당하는 url들은 ADMIN 권한을 가진 사용자만 접근 가능
    /front/** 에 해당하는 url들은 MEMBER 권한을 가진 사용자만 접근 가능
    그 외 나머지 url들은 권한 상관 없이 접근 가능 (permitAll)

     

    configure(WebSecurity web)
    위의 HttpSecurity Config 부분은 권한 체크할 URL 설정을 하는 Configure라면,
    WebSecurity Config에서는 ignoring() 메서드로 Spring Security를 안타게끔 설정 할 수 있다.
    로그인, 회원가입 등 인증 관련 API 또는 static이나 resource들을 설정 한다. 

     

    내용이 길어져서 다음 포스터에 이어서 설명하겠습니다.

    다음 포스터 읽으러 가기

     

    Spring Security + JWT 회원가입, 로그인 (2)

    🎈 지난 게시글에선 Spring Security의 구조와 필요한 Settings을 다뤘습니다. 이어서 Security에 필요한 Class들을 다뤄보겠습니다. 📌 인증 구조 순서 1️⃣ (Login → Jwt Token 발급된 상태) API 요청 2️⃣..

    developer-been.tistory.com



    Reference:

    https://to-dy.tistory.com/86

    반응형

    댓글

Designed by Tistory.