spring security+jwt 회원가입, 로그인 #2
2022.01.12 - [SOLUX-완숙이] - spring security+jwt 회원가입, 로그인 #1
jwt 사용하기 전에 spring security를 통한 로그인을 구현해보자.
1. build.gradle에 dependency를 추가
testImplementation('org.springframework.security:spring-security-test')
implementation('org.springframework.boot:spring-boot-starter-security')
2. WebSecurityConfig 클래스 생성
package solux.wansuki.OurNeighbor_BE.Security.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/**").permitAll();
//.antMatchers("/**").hasAnyRole("ADMIN","USER");
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
- @EnableWebSecurity : Spring Security Filter Chain 을 사용한다는 것을 의미
- antMatchers.hasRole을 통해 특정 URI로 들어오는 요청에 대해 권한 인증을 요청할 수 있다
3. LoginDto 생성
@Getter
@NoArgsConstructor
public class LoginDto {
private String loginId;
private String password;
@Builder
public LoginDto(String loginId, String password) {
this.loginId = loginId;
this.password = password;
}
public UsernamePasswordAuthenticationToken toAuthentication() {
return new UsernamePasswordAuthenticationToken(loginId, password);
}
}
LoginDto 클래스에서 toAuthentication 메소드를 통해 loginId와 password를 사용하여 AuthenticationToken을 생성한다.
4. MemberService
@Transactional
public Long login(LoginDto loginDto) {
if (memberRepository.findByLoginId(loginDto.getLoginId()).orElse(null) == null) {
return 0l;
}
UsernamePasswordAuthenticationToken authenticationToken = loginDto.toAuthentication();
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
return memberRepository.findByLoginId(loginDto.getLoginId()).get().getId();
}
loginDto의 toAuthentication에서 만든 authenticationToken을 가지고 authenticationManagerBuilder.getObject().authenticate(authenticationToken) 을 통해 아이디와 비번이 일치하는지 검증한다.
5. CustomUserDetailsService 클래스
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return memberRepository.findByLoginId(username)
.map(this::createUserDetails)
.orElseThrow(() -> new UsernameNotFoundException("해당 유저가 없습니다"));
}
private UserDetails createUserDetails(Member member) {
return new User(member.getUsername(), member.getPassword(), member.getAuthorities());
}
}
Member클래스(UserDetails를 구현한 클래스)
public class Member implements UserDetails {
...
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.roles.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
@Override
public String getUsername() {
return loginId;
}
authenticationManagerBuilder.getObject().authenticate(authenticationToken) 코드를 실행하면 authenticate 안에서
retrieveUser 메소드가 실행된다. 이때 retrieveUser 안에서 loadUserByUserName을 실행하게 된다. 따라서 이 메소드를 구현해줘야 한다. 이번에는 getUserName을 실행하면 loginId가 반환되도록 설정했다.
참고
spring security + JWT 로그인 기능 파헤치기 - 1 (tistory.com)
SPRING SECURITY + JWT 회원가입, 로그인 기능 구현 (tistory.com)