안녕하세요
이번시간에는 Spring Boot가 제공하는 Spring Security를 활용하여 회원가입 및 로그인 서비스를 구현해보도록 하겠습니다.
Spring Security에 대해서는 추후 다른 게시글로 자세하게 다뤄보도록 하겠습니다.
그래도 간략하게 Spring Security에 대해서 알아보고 넘어가겠습니다.
Spring Security란?
인증, 권한관리, 데이터 보호 기능을 포함하여 웹 개발 과정에서 필수적인 사용자 관리 기능을 구현하는데 도움을 주는 Spring의 프레임워크입니다.
📄 Spring Security 적용하기
Spring Security를 사용하기위해 build.gradle에 의존성을 추가해줍니다.
implementation 'org.springframework.boot:spring-boot-starter-security' // Spring Security 사용하기 위한 스타터
testImplementation 'org.springframework.security:spring-security-test' // Spring Security 테스트를 위한 의존성 추가
📄 Spring Security Config
Spirng Security를 설정하기 위한 config 파일을 만들어줍니다.
Spring Security 5.7.0-M2 버전부터는 component-based security configuration 을 권장하기 위해, 기존에 사용했던 WebSecurityConfigurerAdapter 라는 추상 클래스가 *deprecated 상태로 되었으며 대신 SecurityFilterChain 을 등록하도록 업데이트되었습니다.
이에 따라, WebSecurityConfigurerAdapter를 상속받아 메소드를 재정의하는 방식에서 SecurityFilterChain 클래스를 반환하는 메소드와 비밀번호 암호화에 필요한 BCryptPasswordEncoder 클래스를 Bean으로 등록하는 방식으로 진행하였습니다.
SecurityConfig.java
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf((csrfConfig) ->
csrfConfig.disable()
)
.authorizeHttpRequests((authorizeRequests ->
authorizeRequests
.requestMatchers("/", "/member/**").permitAll()
.requestMatchers("/").permitAll()
));
http
.formLogin(login -> login
.loginPage("/") // 로그인 default 페이지
.loginProcessingUrl("/member/login")
.usernameParameter("email")
.passwordParameter("password")
.defaultSuccessUrl("/member/successLogin") // 로그인 성공시
.failureForwardUrl("/member/failureLogin") // 로그인 실패시
.permitAll()
);
return http.build();
}
// 패스워드 암호화로 사용할 bean
@Bean
public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
- crsf : Cross-site request forgery 사이트간 요청 위조를 막아주는 기능입니다. (프로젝트 테스트를 위해 임시로 disable 설정하였습니다.
- authorizeRequest() : 인증, 인가가 필요한 URL을 지정합니다.
- antMatchers(URL)
- authenticated() : 해당 URL에 진입하기 위해서 Authentication(인증, 로그인)이 필요합니다.
- hasAuthority() : 해당 URL에 진입하기 위해서 Authorization(인가, ex)권한이 ADMIN인 유저만 진입 가능)이 필요합니다.
- URL에 ** 사용 : ** 위치에 어떤 값이 들어와도 적용시킵니다.
- antMatchers(HttpMethod.POST, URL) : 이런 식으로 특정 HttpMethod만 검사 할 수도 있습니다.
- anyRequest() : 그 외의 모든 URL
- permitAll() : Authentication, Authorization 필요 없이 통과
- formLogin() : Form Login 방식 적용
- usernameParameter() : 로그인할 때 사용되는 ID를 적어줌 (해당 프로젝트에서는 email을 id값으로 받을거기 때문에 설정해주었습니다)
- passwordParameter() : 로그인할 때 사용되는 password를 적어줍니다.
- loginPage() : 로그인 페이지 URL
- defaultSuccessURL() : 로그인 성공 시 이동할 URL
- failureURL() : 로그인 실패 시 이동할 URL
- logout() : 로그아웃에 대한 정보
📄 DTO 생성
MemberDto.java
@Data
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
public class MemberDto {
private String email;
private String password;
private String auth; // 권한 Role
private int enable;
}
📄 Controller
MemberController.java
@Slf4j
@Controller
public class MemberController {
private final MemberService memberService;
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
// register화면 이동
@GetMapping("/member/register")
public String register() {
return "member/register";
}
// 로그인 성공 화면 이동
@PostMapping("/member/successLogin")
public String successLogin() {
return "member/successLogin";
}
// 로그인 실패 화면 이동
@PostMapping("/member/failureLogin")
public String failureLogin() {
return "error/loginError";
}
// 회원가입
@PostMapping("/member/insertMember")
public String insertMember(MemberDto memberDto) {
log.info("insertMember ::: {}", memberDto);
int res = memberService.insertMember(memberDto);
log.info("res ::: {}", res);
if (res == 1) {
return "redirect:/index";
} else {
return "redirect:/error/registerError";
}
}
config에서 설정했던 로그인 성공/실패 시 화면을 성공, 실패 화면으로 이동시켜주는 기능을 추가해주었습니다.
📄 Service
MemberService.java
@Service
public class MemberService {
private final MemberMapper memberMapper;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
public MemberService(MemberMapper memberMapper) {
this.memberMapper = memberMapper;
}
public int insertMember(MemberDto memberDto) {
// 패스워드 암호화
memberDto.setPassword(passwordEncoder.encode(memberDto.getPassword()));
return memberMapper.insertMember(memberDto);
}
Spring Security는 회원가입시 반드시 패스워드가 암호화되어야 하기 때문에,
config에서 암호화를 사용하기 위해 등록했던 encoder를 활용해 패스워드를 암호화합니다.
📄 Mapper
MemberMapper.java
@Mapper
public interface MemberMapper {
int insertMember(MemberDto memberDto);
CustomMemberDetails selectMemberByEamail(String email);
}
MemberMapper.xml
<select id="selectMemberByEamail" parameterType="String" resultType="wt.worktiger.security.CustomMemberDetails">
SELECT *
FROM MEMBER
WHERE EMAIL = #{email}
</select>
📄 UserDetails, UserDetailsService
로그인 기능을 구현하기 위해 Spring Secrutiy에 기본적으로 탑재된 UserDetails와 UserDetailsService를 커스텀하여 구현합니다.
CustomMemeberDetails.java
@RequiredArgsConstructor
public class CustomMemberDetails implements UserDetails {
private String email;
@Setter
private String password;
private String auth;
private int enabled;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
ArrayList<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
authList.add(new SimpleGrantedAuthority(auth));
return authList;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return email;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled == 1;
}
}
CustomMemberDetailsService.java
@RequiredArgsConstructor
@Service
public class CustomMemberDetailsService implements UserDetailsService {
private final MemberMapper memberMapper;
private BCryptPasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
CustomMemberDetails members = memberMapper.selectMemberByEamail(email);
if(members == null) {
throw new UsernameNotFoundException("email " + email + " not found");
}
System.out.println("**************Found user***************");
System.out.println(" email : " + members.getUsername());
System.out.println("***************************************");
return members;
}
}
로그인 프로세스 진행 시 Spring Security의 loadUserByUsername 이라는 메소드가 프로세스를 가로채 입력받은 id 값으로 해당 유저의 정보를 조회하고 로그인을 진행합니다.
해당 프로젝트를 기존 MyBatis를 사용하는것에서 JPA를 사용하는것으로 변경되어 변경 전 구현했던 내용들을 위주로 간략하게 작성하였습니다.
추후 JPA를 사용하는 버전으로 수정한 뒤 업데이트 예정입니다.
추가로 이렇게 구현했을때 로그인이 계속 실패만 뜨고있어서 해당 부분도 수정할 예정입니다.
출처
Spring Security 로 회원가입, 로그인 기능 구현하기
Spring Security 로 회원가입, 로그인 기능 구현하기
velog.io
https://chb2005.tistory.com/176
[Spring Boot] Spring Security를 사용한 로그인 구현 (Form Login)
Spring Security 란? Spring에서 제공하는 애플리케이션의 보안 (권한, 인증, 인가 등)을 담당하는 프레임워크 Spring Security는 인증과 권한에 대한 부분을 Filter의 흐름에 따라 처리 [Spring Boot] Session을 사
chb2005.tistory.com
'Spring Boot' 카테고리의 다른 글
[Spring Boot] Spring Security와 JPA로 로그인 구현하기 (0) | 2024.07.21 |
---|---|
[Spring Boot] Spring Security로 회원가입 구현하기(with. JPA) (0) | 2024.07.21 |
[Spring Boot] Sprig Boot와 MySQL + MyBatis 연동하기 (0) | 2024.07.02 |
SpringBoot를 활용한 Rest api 만들기(9) - Spring Starter Unit Test (0) | 2023.03.30 |
SpringBoot를 활용한 Rest api 만들기(8) - SpringSecurity를 이용한 인증 및 권한부여 (0) | 2023.03.21 |