이번 포스팅에서는 스프링 시큐리티를 활용한 로그인을 하는 방법에 대해 포스팅 할 것이다.
로그인을 하는 과정에는 인가와 인증이라는 절차가 생기는데 이 둘의 차이부터 간단하게 알아보자
인증이란? (Authentication)
- 사용자가 누구인지 신원을 확인하는 절차를 말한다. 쉽게 비유하자면 내가 주민등록증과 같은 정부 문서를 받고 싶을 때 나의 신원을 확인하는 과정이라고 생각하면 쉬울 것 같다. 내가 진짜 나라는 것을 증명하는 것을 말한다. 웹에서는 ID와 Password를 활용하여 로그인 하는 것이 대표적인 인증이다.
인가란? (Authorization)
- 어떤 개체가 어떤 리소스에 접근할 수 있는지 어떤 동작을 수행할 수 있는 권한이 있는지를 확인하는 과정이다.
쉽게 비유하자면 내가 체육관에서 운동하려면 회원권이 필요하다. 체육관에 입장하는 과정에서 내가 학생인지 개발자인지 의사인지 나의 신원에 대해서는 중요하지 않다 하지만 내가 이 체육관을 사용할 수 있다는 증표인 회원권을 소유하고 있는지에 대한 여부는 중요하다. 회원권이 헬스장에 대한 접근 권한이고 인가라고 볼 수 있다. 이것을 웹에서 사용 할 때에는 토큰 이라는 방법이 인가를 받는 대표적인 방법이다.
로그인에 대한 최소한을 알았으니 이제 시작해보자.
코드 전에 스프링 시큐리티를 사용하기 위해서는 스프링 시큐리티 라이브러리를 추가해 줘야 한다. 이 두가지를 추가해 주자.
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'
1. HTML
먼저 페이지 소스 코드는 그냥 기본만 했다ㅎ CSS가 제일 어려워,,,
2. Controller.java
@GetMapping("/")
public String home(Authentication authentication, Model model, Principal principal) {
if(principal != null) {
model.addAttribute("isSignedIn", true);
}
else {
model.addAttribute("isSignedIn", false);
}
return "/home";
}
@GetMapping("/signin")
public String signin() {
return "/member/signin";
}
스프링 시큐리티로 인증을 받으면 Principal 객체게 값을 가져올 수 있다. 그래서 principal 객체가 null이 아니면 로그인이 됐다는 것이기 때문에 Home 페이지에 isSignedIn객체를 보내줘서 이름을 띄워준다.
이 부분은 옆에 있는 회원가입 편의 Home.html을 참고하자 [스프링]회원가입 구현
로그인 페이지를 불러올 때 사용하는 GetMapping은 있는데 PostMapping은 어디갔지 라는 생각이 들 것이다. 우리는 스프링 시큐리티를 사용해서 로그인 기능을 구현했기 때문에 스프링 시큐리티가 로그인 할때 POST로 들어온 정보를 가로채서 처리해 주기 때문에 따로 PostMapping을 만들어줄 필요가 없다.
3. SecurityConfig.java
@EnableWebSecurity
@Configuration
@RequiredArgsConstructor
public class SecurityConfig {
//비밀번호 암호화 객체
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(
authorize -> authorize
//인증없이 접속 가능한 URL들
.requestMatchers(new AntPathRequestMatcher("/h2/**")
,new AntPathRequestMatcher("/signin*")
,new AntPathRequestMatcher("/signup")
,new AntPathRequestMatcher("/signin/error")
,new AntPathRequestMatcher("/checkUsername.do")
,new AntPathRequestMatcher("/checkNickname.do")
,new AntPathRequestMatcher("/checkNickname.do")
,new AntPathRequestMatcher("/error**")
,new AntPathRequestMatcher("/freeboard")
,new AntPathRequestMatcher("/dietrecommendation")
,new AntPathRequestMatcher("/")).permitAll()
//정적 리소스요청시 허용
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
//이외 다른 리소스 인증 받아야 함.
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/signin") //사용자 정의 로그인 페이지
.loginProcessingUrl("/signin") //로그인 form action URL
.defaultSuccessUrl("/") //로그인 성공 후 이동 페이지
.failureUrl("/signin?error") //로그인 실패 후 이동 페이지
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/signout.do") //로그아웃 로직 실행 URL
.logoutSuccessUrl("/") //로그아웃 성공 후 이동 페이지
)
.csrf(CsrfConfigurer::disable)
.cors(AbstractHttpConfigurer::disable)
.headers(headers -> headers
.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)
)
.build();
}
}
Method
bCryptPasswordEncoder : 회원가입 시 비밀번호를 암호화 해줄때 사용한다. 스프링 회원가입 구현 게시글에 Controller와 Member.java부분을 보면 사용하는 부분을 알 수 있다. 자세한 구현은 아래 링크를 참고하자!
FilterChain
기본적인 것들은 주석을 달아놨고 설명이 필요한 부분은 csrf부분인것 같다.
CSRF란 Cross Site Request Forgery로 정상적인 사용자가 의도치 않은 위조 요청을 보내는 공격을 의미한다. 이러한 공격을 막아주는 기능을 disabled하는 이유는 시큐리티 API문서에서는 브라우저에서 처리할 수 있는 모든 요청에 CSRF protection을 권장한다. 하지만 우리는 REST API를 활용하여 만들고 있기 때문에 브라우저가 아니기 때문에 실습을 포함한 책이나 블로그에서는 disable을 하는 거라고 추측? 한다.
https://docs.spring.io/spring-security/reference/features/exploits/csrf.html
CSRF에 대해서는 좀 더 알아봐야 하지만 내 기준 CSRF를 꺼준 이유는 내가 H2데이터 베이스를 사용하는데 CSRF 보호를 사용하면 H2접속이 안되서 꺼줬다. 하지만 CSRF를 다 꺼주면 서비스 전체적으로 보안이 떨어지기 때문에 H2에 대해서만 꺼주는 방법을 택할 수도 있을 것 같다. 이 부분은 지금까지 한 부분에 대해서 블로그 글을 다 쓰고 리팩토링 겸 추가해 봐야 겠다
4. UserDetailsImpl.java
@Data
@AllArgsConstructor
public class UserDetailsImpl implements UserDetails {
private final Member member;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
return authorities;
}
@Override
public String getPassword() {
return member.getPassword();
}
@Override
public String getUsername() {
return member.getUsername();
}
//계정이 만료되지 않았는지를 담아두기 위해(true : 만료안됨)
@Override
public boolean isAccountNonExpired() {
return true;
}
//계정이 잠겨있지 않았는지를 담아두기 위해 (true : 만료안됨)
@Override
public boolean isAccountNonLocked() {
return true;
}
//계정의 비밀번호가 만료되지 않았는지를 담아두기 위해(true:만료안됨)
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 계정이 활성화 되어있는지를 담아두기 위해(true: 활성화 됨)
@Override
public boolean isEnabled() {
return true;
}
}
아까 Controller에서 PostMapping이 없는 대신 스프링 시큐리티에서는 로그인을 할 때 사용자가 입력한 ID와 PW를 POST로 보내면 시큐리티가 가로채서 로그인을 진행 해 준다. 대신 우리가 지정해줘야 할 정보들을 여기서 지정해 줄 수 있다.
5. MemberService.java
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//사용자 Id기반으로 정보 조회
Member findMember = memberRepository.findByUsername(username);
if(findMember == null) {
throw new UsernameNotFoundException("Not found: " + username);
}
log.info("findMember : {}", findMember);
return User.builder()
.username(findMember.getUsername())
.password(findMember.getPassword())
.build();
}
반환 타입은UserDetails이지만 User를 들어가보면 UserDetail를 implemet하고 있기 때문에 User객체를 반환해도 된다.
로그인이 잘 되는것을 볼 수 있다! 로그인 즉시 인증 객체를 받아 로그인 한 사용자의 아이디를 같이 출력해준다.
스프링 시큐리티가 아이디랑 비밀번호를 바당 어떻게 로그인을 하는지 API문서를 보고 조금 더 공부해야겠따
만들고 싶은 서비스를 스스로 공부하고 만들어 보면서 기록하는 개인 공부 블로그입니다.
내용 중 최적화가 가능한 부분 혹은 궁금한 부분은 언제든지 댓글로 남겨주세요🧐
'Projects > 식단 짜주는 웹' 카테고리의 다른 글
[Spring/식단 추천 API] 게시판 글 수정 기능 구현 - 6 (0) | 2023.11.18 |
---|---|
[Spring/식단 추천 API] 게시판 글 상세보기 기능 구현 - 5 (0) | 2023.11.16 |
[Spring/식단 추천 API] 게시판 글 작성 기능 구현 - 4 (1) | 2023.11.16 |
[Spring/식단 추천 API] 게시판 글 목록 페이지 구현 - 3 (0) | 2023.11.16 |
[Spring/식단 추천 API] 회원가입 기능 구현 - 1 (0) | 2023.10.26 |