로그인 기능 만들기
spring.io 에서 의존성 복사
https://spring.io/guides/gs/securing-web/
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-test'
설정 코드도 복사
package com.example.securingweb;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
)
.formLogin((form) -> form
.loginPage("/login")
.permitAll()
)
.logout((logout) -> logout.permitAll());
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
user 테이블 만들기
커스터마이징 하기
https://www.baeldung.com/spring-security-jdbc-authentication
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select email,password,enabled "
+ "from bael_users "
+ "where email = ?")
.authoritiesByUsernameQuery("select email,authority "
+ "from authorities "
+ "where email = ?");
}
package com.toy.board.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import javax.sql.DataSource;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Autowired
private DataSource dataSource;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
)
.formLogin((form) -> form
.loginPage("/login")
.permitAll()
)
.logout((logout) -> logout.permitAll());
return http.build();
}
@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 email,authority "
+ "from authorities "
+ "where email = ?");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
role 테이블 생성
user_role 테이블 생성
WebSecurityConfig.java
package com.toy.board.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.WebSecurityConfiguration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import javax.sql.DataSource;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Autowired
private DataSource dataSource;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.antMatchers("/", "/css/**").permitAll()
.anyRequest().authenticated()
)
.formLogin((form) -> form
.loginPage("/login")
.permitAll()
)
.logout((logout) -> logout.permitAll());
return http.build();
}
@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 username, 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 email = ?");
}
@Bean
public static PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
BeanCurrentlyInCreationExeption, Error creating bean with name 'webSecurityConfig' 에러가 발생 시
PasswordEncoder 에 static을 붙여주자.
login 페이지 만들기
Bootstrap 코드 복사
https://getbootstrap.com/docs/5.2/examples/sign-in/
login.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
<link th:href="@{/css/signin.css}" rel="stylesheet">
<title>Login</title>
</head>
<body class="text-center">
<main class="form-signin w-100 m-auto">
<form>
<img class="mb-4" src="https://getbootstrap.com/docs/5.2/assets/brand/bootstrap-logo.svg" alt="" width="72" height="57">
<h1 class="h3 mb-3 fw-normal">Please sign in</h1>
<div class="form-floating">
<input type="text" class="form-control" id="floatingInput" placeholder="Username">
<label for="floatingInput">Username</label>
</div>
<div class="form-floating">
<input type="password" class="form-control" id="floatingPassword" placeholder="Password">
<label for="floatingPassword">Password</label>
</div>
<!-- <div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> Remember me
</label>
</div>-->
<button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017–2022</p>
</form>
</main>
</body>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></script>
</body>
</html>
signin.css
html,
body {
height: 100%;
}
body {
display: flex;
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
}
.form-signin {
max-width: 330px;
padding: 15px;
}
.form-signin .form-floating:focus-within {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
AccountController.java
package com.toy.board.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/account")
public class AccountController {
@GetMapping("/login")
public String login() {
return "account/login";
}
}
WebSecurityConfig.java
package com.toy.board.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.WebSecurityConfiguration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import javax.sql.DataSource;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Autowired
private DataSource dataSource;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.antMatchers("/", "/css/**").permitAll()
.anyRequest().authenticated()
)
.formLogin((form) -> form
.loginPage("/account/login")
.permitAll()
)
.logout((logout) -> logout.permitAll());
return http.build();
}
@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 username = ?");
}
@Bean
public static PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
login form 전송
error 메시지 추가
body에 코드 추가
<div th:if="${param.error}">
Invalid username and password.
</div>
<div th:if="${param.logout}">
You have been logged out.
</div>
alert 를 Bootstrap 에서 코드 복사
https://getbootstrap.com/docs/5.2/components/alerts/
class="alert alert-danger" role="alert"
<div th:if="${param.error}" class="alert alert-danger" role="alert">
Invalid username and password.
</div>
<div th:if="${param.logout}" class="alert alert-primary" role="alert">
You have been logged out.
</div>
login.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
<link th:href="@{/css/signin.css}" rel="stylesheet">
<title>Login</title>
</head>
<body class="text-center">
<main class="form-signin w-100 m-auto">
<form th:action="@{/account/login}" method="post">
<img class="mb-4" src="https://getbootstrap.com/docs/5.2/assets/brand/bootstrap-logo.svg" alt="" width="72" height="57">
<h1 class="h3 mb-3 fw-normal">Please sign in</h1>
<div th:if="${param.error}" class="alert alert-danger" role="alert">
Invalid username and password.
</div>
<div th:if="${param.logout}" class="alert alert-primary" role="alert">
You have been logged out.
</div>
<div class="form-floating">
<input type="text" class="form-control" id="username" name="username" placeholder="Username">
<label for="username">Username</label>
</div>
<div class="form-floating">
<input type="password" class="form-control" id="password" name="password" placeholder="Password">
<label for="password">Password</label>
</div>
<!-- <div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> Remember me
</label>
</div>-->
<button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017–2022</p>
</form>
</main>
</body>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></script>
</body>
</html>
회원가입 페이지 만들기
register.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
<link th:href="@{/css/signin.css}" rel="stylesheet">
<title>Login</title>
</head>
<body class="text-center">
<main class="form-signin w-100 m-auto">
<form th:action="@{/account/register}" method="post">
<img class="mb-4" src="https://getbootstrap.com/docs/5.2/assets/brand/bootstrap-logo.svg" alt="" width="72" height="57">
<h1 class="h3 mb-3 fw-normal">회원가입</h1>
<div th:if="${param.error}" class="alert alert-danger" role="alert">
Invalid username and password.
</div>
<div th:if="${param.logout}" class="alert alert-primary" role="alert">
You have been logged out.
</div>
<div class="form-floating">
<input type="text" class="form-control" id="username" name="username" placeholder="Username">
<label for="username">Username</label>
</div>
<div class="form-floating">
<input type="password" class="form-control" id="password" name="password" placeholder="Password">
<label for="password">Password</label>
</div>
<!-- <div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> Remember me
</label>
</div>-->
<button class="w-100 btn btn-lg btn-primary" type="submit">회원가입</button>
<p class="mt-5 mb-3 text-muted">© 2017–2022</p>
</form>
</main>
</body>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></script>
</body>
</html>
User.java
package com.toy.board.domain;
import lombok.Data;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Data
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
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<>();
}
Role,java
package com.toy.board.domain;
import lombok.Data;
import javax.persistence.*;
import java.util.List;
@Data
@Entity
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(mappedBy = "roles")
private List<User> users;
}
UserRespository.java
package com.toy.board.reopsitory;
import com.toy.board.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}
UserService.java
package com.toy.board.service;
import com.toy.board.domain.Role;
import com.toy.board.domain.User;
import com.toy.board.reopsitory.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 encodedPassword = passwordEncoder.encode(user.getPassword());
user.setPassword(encodedPassword);
user.setEnabled(true);
Role role = new Role();
role.setId(1L);
user.getRoles().add(role);
return userRepository.save(user);
}
}
AccountController.java
package com.toy.board.web;
import com.toy.board.domain.User;
import com.toy.board.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/account")
public class AccountController {
@Autowired
private UserService userService;
@GetMapping("/login")
public String login() {
return "account/login";
}
@GetMapping("/register")
public String register() {
return "account/register";
}
@PostMapping("/register")
public String register(User user) {
userService.save(user);
return "redirect:/";
}
}
role 테이블에 ROLE_USER 추가
login, logout 추가
의존성 추가
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
common.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.w3.org/1999/xhtml">
<head th:fragment="head(title)">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title th:text="${title}">Hello Spring Boot!</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
<link href="starter-template.css" th:href="@{/css/starter-template.css}" rel="stylesheet">
</head>
<body class="d-flex flex-column h-100">
<header th:fragment="menu(menu)">
<!-- Fixed navbar -->
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="#">Spring Boot Tutorial</a>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav me-auto mb-2 mb-md-0">
<li class="nav-item">
<a class="nav-link" th:classappend="${menu} == 'home'? 'active'" aria-current="page" href="#" th:href="@{/}">홈</a>
</li>
<li class="nav-item">
<a class="nav-link" th:classappend="${menu} == 'board'? 'active'" href="#" th:href="@{/board/list}">게시판</a>
</li>
</ul>
</div>
<a class="btn btn-outline-success" th:href="@{/account/login}"sec:authorize="!isAuthenticated()">Login</a>
<a class="btn btn-outline-success" th:href="@{/account/register}"sec:authorize="!isAuthenticated()">회원가입</a>
<form class="d-flex" role="search" th:action="@{/logout}" method="post" sec:authorize="isAuthenticated()">
<!-- <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">-->
<span class="text-white mx-2 my-2" sec:authentication="name">사용자</span>
<span class="text-white mx-4 my-2" sec:authentication="principal.authorities">권한</span>
<button class="btn btn-outline-success" type="submit" >Logout</button>
</form>
</div>
</nav>
</header>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></script>
</body>
</html>
회원이 아닌 사람
"sec:authorize="!isAuthenticated()"
<a class="btn btn-outline-success" th:href="@{/account/login}"sec:authorize="!isAuthenticated()">Login</a>
<a class="btn btn-outline-success" th:href="@{/account/register}"sec:authorize="!isAuthenticated()">회원가입</a>
<form class="d-flex" role="search" th:action="@{/logout}" method="post" sec:authorize="isAuthenticated()">
<!-- <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">-->
<span class="text-white mx-2 my-2" sec:authentication="name">사용자</span>
<span class="text-white mx-4 my-2" sec:authentication="principal.authorities">권한</span>
<button class="btn btn-outline-success" type="submit" >Logout</button>
</form>
'JPA' 카테고리의 다른 글
JPA - Jpa를 이용하여 @OneToMany 관계 설정하기 (0) | 2022.09.11 |
---|---|
JPA - 다대다 관계 한계 극복 (0) | 2022.09.11 |
JPA - Jpa를 이용한 페이지 처리 및 검색 (0) | 2022.09.09 |
JPA - Jpa를 이용한 RESTful API 만들기 (0) | 2022.09.08 |
JPA - Pageable (0) | 2022.09.02 |