-
[ Spring Boot + JPA + Thymeleaf ] 로그인/회원가입Spring Boot 2021. 2. 23. 21:31
이번에는 게시판에 회원가입과 로그인 기능을 추가해볼것이다.
해당 기능을 구현하기 위해 spring security를 사용하였다.
[ Spring Security란? ]
Spring Security는 Spring 기반의 애플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크이다. Spring Security는 '인증'과 '권한'에 대한 부분을 Filter 흐름에 따라 처리하고 있다. Filter는 Dispatcher Servlet으로 가기 전에 적용되므로 가장 먼저 URL 요청을 받지만, Interceptor는 Dispatcher와 Controller사이에 위치한다는 점에서 적용 시기의 차이가 있다. Spring Security는 보안과 관련해서 체계적으로 많은 옵션을 제공해주기 때문에 개발자 입장에서는 일일이 보안관련 로직을 작성하지 않아도 된다는 장점이 있다.
이러한 Spring Security의 아키텍쳐는 아래와 같다.
[ 인증(Authorizatoin)과 인가(Authentication) ]
-
인증(Authentication): 해당 사용자가 본인이 맞는지를 확인하는 절차
- 인가(Authorization): 인증된 사용자가 요청한 자원에 접근 가능한지를 결정하는 절차
1. 우선 해당 기능을 사용하기 위해서 dependency를 추가해 주었다.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency>
그리고 config/WebSecurityConfig.java 파일을 생성해 준 후,
WebSecurityConfigurerAdapter 클래스를 상속받는 클래스를 생성해 준 후 security에 대한 세팅을 해 준다.
- WebSecurityConfig.java
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private DataSource dataSource; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/","/css/**","/user/join").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/user/login") .permitAll() .and() .logout() .permitAll(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.jdbcAuthentication() .dataSource(dataSource) .passwordEncoder(passwordEncoder()) .usersByUsernameQuery("select username,password,enabled " // 로그인처리 + "from user " + "where username = ?") .authoritiesByUsernameQuery("select u.username,r.name " // 권한처리 + "from user_role ur inner join user u on ur.user_id = u.id " + "inner join role r on ur.role_id = r.id " + "where u.username = ?"); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
antMatchers().permitAll() 은 접근을 허용해줄 페이지를 설정해 주는것인데, 우선 나는 제일 처음 접속되는 페이지와 css파일,회원가입 페이지를 지정해주었다.
만약 위와같이 설정해주지 않은 페이지에 접속하게 되면 로그인 페이지로 자동으로 이동시켜 준다. 로그인 페이지 경로는 .loginPage()에 설정해 주면 된다.
그리고 하단에 usersByUsernameQuery()와 authoritiesByUsernameQuery()에는 각각 로그인과 권한처리시 사용되는 데이터를 가져오는 쿼리를 알맞게 작성해주면 된다.
2. 그 다음으로 우선 회원가입을 위한 페이지를 작성해줄것이다.
- join.html
<!doctype html> <html xmlns:th="http://www.thymelef.org"> <head th:replace="fragments/common :: head('Hello, Spring Boot')"> </head> <body> <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top" th:replace="fragments/common :: menu('home')"> </nav> <main class="container"> <div th:id="login_div"> <form th:action="@{/user/join}" th:object="${user}" method="post"> <div class="mb-3"> <label for="username" class="form-label">이름</label> <input type="text" class="form-control" id="username" aria-describedby="emailHelp" th:field="*{username}"> <input type="text" class="form-control" id="username" aria-describedby="emailHelp" name="username"> </div> <div class="mb-3"> <label for="useremail" class="form-label">이메일</label> <input type="email" class="form-control" id="useremail" aria-describedby="emailHelp" th:field="*{useremail}"> <input type="email" class="form-control" id="useremail" aria-describedby="emailHelp" name="useremail"> </div> <div class="mb-3"> <label for="userpassword" class="form-label">비밀번호</label> <input type="password" class="form-control" id="userpassword" th:field="*{userpassword}"> <label for="password" class="form-label">비밀번호</label> <input type="password" class="form-control" id="password" name="password"> </div> <!-- <div class="mb-3"> <label for="userpasswordcheck" class="form-label">비밀번호 확인</label> <input type="password" class="form-control" id="userpasswordcheck"> </div> --> <button type="submit" class="btn btn-primary">가입</button> </form> </div>
보통의 회원가입에는 아이디 중복체크,비밀번호 확인 등 여러가지 기능이 들어가지만 나는 아직 그부분에 까지 처리하기에는 힘이 벅차므로 아주 기본적인 이름,이메일,비밀번호만 받도록 하였다. 우선 기본적인 모든 기능이 돌아가게끔 진행 한 후 디테일 한 부분을 손 볼 것이기 때문에, 여러가지 체크 기능등은 뒷부분에서 다룰것이다.
회원가입 페이지 폼을 만들어 주었으면 이제 컨트롤러에 해당 페이지를 연결 해 준다.
- UserController.java
@Controller @RequestMapping("user") public class UserController { @Autowired private UserService userService; @GetMapping("/login") public String loginForm(){ return "user/login"; } // 회원가입 페이지로 이동 @GetMapping("/join") public String joinForm(){ return "user/join"; } // 회원가입 @PostMapping("/join") public String join(User user){ userService.save(user); return "redirect:/"; } }
이제 회원가입 기능이 실행될 때에 대한 처리를 해주어야하는데, 보통 회원가입시 해당 서비스마다 요구하는 정보들도 다르고 비밀번호도 암호화 해주어야하고.. 여러가지 내부적인 처리가 필요하므로 service패키지를 생성하여 해당 파일에 UserService클래스를 작성하여 사용해 줄 것이다. 그렇기 때문에 상단에 @Autowired를 통하여 해당 클래스를 참조하는 코드를 넣어주었다.
- UserSerive.java
package com.example.myhome.service; import com.example.myhome.model.Role; import com.example.myhome.model.User; import com.example.myhome.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @Service public class UserService { @Autowired private UserRepository userRepository; @Autowired private PasswordEncoder passwordEncoder; public User save(User user){ String enCodePassword = passwordEncoder.encode((user.getPassword())); // 비밀번호 암호화 user.setPassword(enCodePassword); user.setUsername(user.getUsername()); user.setEnabled(true); // 활성화여부 Role role = new Role(); role.setId(1L); // 권한을 넣어줘야하는데 우선 하드코딩으로 넣어줌.. user.getRoles().add(role); return userRepository.save(user); } }
UserService클래스에 save메서드를 통하여 회원가입시 작성한 정보들을 저장해주는 코드를 작성하였다.
우선 비밀번호는 암호화 하여 저장해야하기 때문에 상단에 WebSecurityConfig.java파일에서 @Bean으로 선언해주었던 passwordEncoder를 이용하여 암호화 처리를 해주었고. 활성화 여부와 권한에 대한 정보도 같이 저장해주었다.
권한에 대해서는 DB에서 user테이블과role테이블을 조인하는 user_role테이블을 생성해두었기 때문에 회원가입시 자동으로 user_role테이블에도 데이터가 저장되어야 하므로 해당 코드도 추가해주었다.
두개의 테이블을 조인하기 위해서 model에 Role.java 파일과 User.java 파일에 조인해주는 코드도 작성해주었다.
- Role.java
package com.example.myhome.model; import jdk.dynalink.linker.LinkerServices; import lombok.Data; import javax.persistence.*; import java.util.List; @Entity @Data public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @ManyToMany(mappedBy = "roles") private List<User> users; }
- User.java
package com.example.myhome.model; import lombok.Data; import javax.persistence.*; import java.util.ArrayList; import java.util.List; @Entity @Data public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String useremail; private String username; private String password; private Boolean enabled; @ManyToMany @JoinTable( name = "user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id") ) private List<Role> roles = new ArrayList<>(); }
이제 회원가입에 대한 모든 세팅이 끝났다.
회원가입을 통하여 만들어진 데이터를 이용하여 로그인 기능을 구현해 보겠다.
우선 로그인 페이지 폼은 아래와 같이 넣어주었다.
- login.html
<!doctype html> <html xmlns:th="http://www.thymelef.org"> <head th:replace="fragments/common :: head('Hello, Spring Boot')"> </head> <body> <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top" th:replace="fragments/common :: menu('home')"> </nav> <main class="container"> <div th:id="login_div"> <form th:action="@{/user/login}" method="post"> <div class="alert alert-danger" role="alert" th:if="${param.error}"> 비밀번호가 틀렸습니다. </div> <div class="alert alert-primary" role="alert" th:if="${param.logout}"> 로그아웃 되었습니다. </div> <div class="mb-3"> <label for="username" class="form-label">이름</label> <input type="text" class="form-control" id="username" name="username" aria-describedby="emailHelp"> </div> <div class="mb-3"> <label for="password" class="form-label">비밀번호</label> <input type="password" class="form-control" id="password" name="password"> </div> <!-- <div class="mb-3 form-check"> <input type="checkbox" class="form-check-input" id="exampleCheck1"> <label class="form-check-label" for="exampleCheck1">아이디 저장</label> </div> --> <button type="submit" class="btn btn-primary">로그인</button> <a class="float-right" href="#" th:href="@{/user/join}">회원가입</a> </form> </div> </main><!-- /.container --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script> </body> </html>
spring security에서 따로 설정을 해주지 않으면 아이디와 비밀번호의 name은 username과 password로 지정해주어야지 정상적으로 로그인이 동작된다.
만약 비밀번호가 틀렸거나 로그인이 정상적으로 처리되지 않을 경우에는 파라미터로 error를 넘겨주기때문에 해당 에러에 대한 메시지 표시도 추가해주었다.
로그아웃의 경우에는 form의 action="/logout"으로 설정하여 submit해주면 정상적으로 로그아웃 처리되는것을 볼 수 있다.
'Spring Boot' 카테고리의 다른 글
[ Spring Boot + JPA + Thymeleaf ] 페이징 기능 구현 (0) 2021.03.04 [ Spring Boot + JPA + Thymeleaf ] validation 추가 (0) 2021.03.02 [ Spring Boot + JPA + Thymeleaf ] 게시판 만들기 - 3 (0) 2021.02.10 [ Spring Boot + JPA + Thymeleaf ] 게시판 만들기 - 2 (0) 2021.02.10 [ Spring Boot + JPA + Thymeleaf ] 게시판 만들기 - 1 (0) 2021.02.09 -