728x90
반응형
로그인 처리는 스프링 시큐리티를 이용
login
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments.html :: head"></head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<nav th:replace="fragments.html :: main-nav"></nav>
<div class="container">
<!--패딩 1~5 -->
<div class="py-5 text-center">
<p class="lead"> 데모</p>
<h2>로그인</h2>
</div>
<!-- 폼을 감쌀 영역-->
<!-- 로그인을 처리하는 핸들러를 직접 작성할 필요는 없지만, 유저정보를 전달하는 인터페이스는 구현해야한다.-->
<div class="row justify-content-center">
<!--파라미터에 에러가 존재한다면, 출력 -->
<div th:if="${param.error}" class="alert alert-danger" role="alert">
<p>이메일(또는 닉네임)과 패스워드가 정확하지 않습니다.</p>
<p>또는 확인되지 않은 이메일을 사용했습니다. 이메일을 확인해 주세요.</p>
<p>
확인 후 다시 입력하시거나, <a href="#" th:href="@{/find-password}"> 패스워드 찾기</a>를 이용하세요.
</p>
</div>
<!--form 작성 -->
<form class="needs-validation col-sm-6" action="#" th:action="@{/login}" method="post" novalidate>
<div class="form-group">
<label for="username">이메일 또는 닉네임</label>
<input id="username" type="text" name="username" class="form-control" placeholder="your@email.com"
aria-describedby="emailHelp" required>
<small id="emailHelp" class="form-text text-muted">
가입할 때 사용한 이메일 또는 닉네임을 입력하세요.
</small>
<small class="invalid-feedback">이메일을 입력하세요.</small>
</div>
<div class="form-group">
<label for="password">패스워드</label>
<input id="password" type="password" name="password" class="form-control"
aria-describedby="passwordHelp" required>
<small id="passwordHelp" class="form-text text-muted">
패스워드가 기억나지 않는다면, <a href="#" th:href="@{/email-login}">패스워드 없이 로그인하기</a>
</small>
<small class="invalid-feedback">패스워드를 입력하세요.</small>
</div>
<div class="form-group">
<button class="btn btn-success btn-block" type="submit"
aria-describedby="submitHelp">로그인</button>
<small id="submitHelp" class="form-text text-muted">
스터디올래에 처음 오신거라면 <a href="#" th:href="@{/sign-up}">계정을 먼저 만드세요.</a>
</small>
</div>
</form>
</div>
<div th:replace="fragments.html :: footer"></div>
</div>
<script th:replace="fragments.html :: form-validation"></script>
</body>
</html>
SecurityConfig
package com.demo.config;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.WebSecurityCustomizer;
import org.springframework.security.web.SecurityFilterChain;
import lombok.RequiredArgsConstructor;
//회원가입을 위한 시큐리티 수동 설정
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
// private final AccountService accountService;
// private final DataSource dataSource;
/**
* Spring Security 5.7.x 부터 WebSecurityConfigurerAdapter 는 Deprecated. ->
* SecurityFilterChain, WebSecurityCustomizer 를 상황에 따라 빈으로 등록해 사용한다.
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// 오버라이딩을 통해 원하는 요청은 시큐리티 체크를 하지 않도록 설정
return http.authorizeRequests()
// 모듈에서 걸러내야 하는 요청들
.mvcMatchers("/", "/login", "/sign-up", "/check-email-token", "/email-login",
"/check-email-login", "login-link")
//재전송 기능 만들때에는 "/check-email" permitall에서 제외, 로그인한 사용자만 접근해야하므로
.permitAll()
// .mvcMatchers("/", "/login", "/sign-up", "/check-email", "/check-email-token",
// "/email-login", "/check-email-login", "login-link", "/profile/*").permitAll()
// -> 프로필에 붙어있는 모든 요청은 get만 허용
.mvcMatchers(HttpMethod.GET, "/profile/*").permitAll()
// 그외에 나머지 요청의 경우 모두 로그인이 필요하다.
.anyRequest().authenticated()
//스프링 시큐리티가 제공하는 로그인 페이지 그대로 사용한다면
//http.formLogin();
//커스텀한 로그인 페이지를 통해 로그인 처리를 한다면
.and()
.formLogin().loginPage("/login").permitAll()
//permitAll을 주는 이유는, 로그인한 사용자도, 로그인 안한 사용자도 접근 가능하도록 하기 위해
//로그아웃 처리를 위해 -> 로그아웃 성공시 index 페이지로 이동
.and()
.logout().logoutSuccessUrl("/").and().build();
//
// .and()
// .rememberMe().userDetailsService(accountService).tokenRepository(tokenRepository())
// .and().build();
//
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
// static리소스도 요청을 거부하지 않는다.
return (web) -> web.ignoring().mvcMatchers("/node_modules/**")
.requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}
}
MainController
package com.demo.main;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import com.demo.account.CurrentUser;
import com.demo.domain.Account;
@Controller
//첫 페이지로 가는 요청 핸들러 작성
public class MainController {
//스프링이 제공하는 @AuthenticationPrincipal를 동적으로 사용하기 위해
//익명일 경우 null로 익명이 아닐 경우 account 객체로 사용
@GetMapping("/")
public String home(@CurrentUser Account account, Model model) {
//인증한 사용자인 경우
if(account !=null) {
model.addAttribute(account);
}
return "index";
//login 메서드에는 account라는 프로퍼티가 없기 때문에 중간 매개자 UserAccount 작성
//도메인의 유저정보와 스프링 시큐리티 유저정보의 연결고리
}
//로그인의 경우 메인 컨트롤러에 작성 -> 뷰 컨트롤러를 이용한다면 단축 가능
@GetMapping("/login")
public String login() {
return "login";
//로그인 요청시 로그인 뷰 리턴
}
}
AccountService에서 UserDetailsService에서 상속받아 오버라이딩해서 구현 [따라서 extends가 아닌 implements]
package com.demo.account;
import java.util.List;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.demo.domain.Account;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class AccountService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String emailOrNickname) throws UsernameNotFoundException {
// TODO Auto-generated method stub
//이메일 또는 닉네임을 이용한 로그인 처리를 위해 필요한 인터페이스 작성
Account account= accountRepository.findByEmail(emailOrNickname);
if(account==null) {
account= accountRepository.findByNickname(emailOrNickname);
}
if(account==null) {
throw new UsernameNotFoundException(emailOrNickname);
//닉네임이나 이메일이 잘못되었다.
}
//principal에 해당하는 객체 리턴
return new UserAccount(account);
//자동으로 빈을 가져다 사용한다.
}
}
Nickname을 이용해 계정을 찾는 메서드 만들기
AccountRepository
package com.demo.account;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Transactional;
import com.demo.domain.Account;
//해당 클래스를 인터페이스로 만든다. -> Account에서, ID타입으로 조회
//기본적으로 write를 안쓰고 읽기만 하게해서 메모리 사용량을 최적화
@Transactional(readOnly = true)
public interface AccountRepository extends JpaRepository<Account, Long> {
boolean existsByEmail(String email);
boolean existsByNickname(String nickname);
Account findByEmail(String email);
Account findByNickname(String nickname);
}
728x90
반응형
'Server Programming > Spring Boot Full-Stack Programming' 카테고리의 다른 글
[스프링 풀스택 클론 코딩 - 회원가입] (1-20) 가입 일자 데이터의 변경이 DB에 반영되지 않는 버그 (0) | 2022.09.01 |
---|---|
[스프링 풀스택 클론 코딩 - 회원가입] (1-19) 로그인 기억하기 (0) | 2022.08.30 |
[스프링 풀스택 클론 코딩 - 회원가입] (1-17) 가입 확인 이메일 재전송 (0) | 2022.08.30 |
[스프링 풀스택 클론 코딩] 인증된 사용자와 미인증 사용자 분류 (0) | 2022.08.30 |
[스프링 풀스택 클론 코딩 - 회원가입] (1-16) 현재 인증된 사용자 정보 참조 (0) | 2022.08.30 |