-
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/**"); } }
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들을 설정 한다.내용이 길어져서 다음 포스터에 이어서 설명하겠습니다.
Reference:반응형'Spring' 카테고리의 다른 글
[Spring] Spring에서 CORS 처리(설정)하는 방법 (0) 2022.10.27 [Spring] Spring에서 Scheduler 처리하기 (0) 2022.10.26 Spring Boot 공통 Global Exception Handler (0) 2022.10.23 Spring Security + JWT 회원가입, 로그인 (3) (0) 2022.10.23 Spring Security + JWT 회원가입, 로그인 (2) (0) 2022.10.23