CodeView
:
기능(임시)
- 로그인
- 게시판
학습했던 수업에서 구현했던 로그인과는 달리
Spring Security
를 활용해 로그인을 구현
Spring Security
: 스프링 기반 애플리케이션 보안을 담당하는 프레임워크
사용자 인증, 권한, 보안 처리를 간단하지만 강력하게 구현할 수 있다.
package codebook.codeview.auth;
import codebook.codeview.entity.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
//import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;
public class MyUserDetail implements UserDetails {
private String email;
private String password;
private String auth;
public MyUserDetail(User user) {
this.email = user.getEmail();
this.password = user.getPassword();
this.auth = "ROLE_" + user.getRole();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singletonList(new SimpleGrantedAuthority(this.auth));
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.email;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
package codebook.codeview.auth;
import codebook.codeview.service.UserService;
import lombok.RequiredArgsConstructor;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@EnableWebSecurity //spring security 를 적용한다는 Annotation
@RequiredArgsConstructor
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserService exService;
/**
* 규칙 설정
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/welcome").hasRole("USER")
.antMatchers("/signup").anonymous()
.and()
.formLogin()
.and()
.csrf().disable(); //로그인 창
}
/**
* 로그인 인증 처리 메소드
* @param auth
* @throws Exception
*/
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(exService).passwordEncoder(new BCryptPasswordEncoder());
}
}
package codebook.codeview.controller;
import codebook.codeview.auth.MyUserDetail;
import codebook.codeview.entity.User;
import codebook.codeview.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
//import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import javax.servlet.ServletException;
import java.io.IOException;
@Controller
@RequiredArgsConstructor
@Slf4j
public class UserController {
private final UserService service;
/**
* 회원가입 폼
* @return
*/
@GetMapping("/signup")
public String signupForm() {
return "signup";
}
/**
* 회원가입 진행
* @param user
* @return
*/
@PostMapping("/signup")
public String signup(User user) {
user.setRole("USER");
service.joinUser(user);
return "redirect:/check";
}
// @PostMapping("/signUp")
// public String signUp(User user) {
// user.setRole("USER");
// service.joinUser(user);
// return "redirect:/check";
// }
/**
* 유저 페이지
* @param model
* @param authentication
* @return
*/
@GetMapping("/")
public String userAccess(Model model, Authentication authentication) {
//Authentication 객체를 통해 유저 정보를 가져올 수 있다.
MyUserDetail userDetail = (MyUserDetail)authentication.getPrincipal(); //userDetail 객체를 가져옴
model.addAttribute("info", userDetail.getUsername()); //유저 이메일
return "welcome";
}
}
package codebook.codeview.entity;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@Getter
@Setter
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email; //이메일
private String password; //패스워드
private String role; //권한
}
package codebook.codeview.repository;
import codebook.codeview.entity.User;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
@Repository
@RequiredArgsConstructor
public class UserRepository {
private final EntityManager em;
public void saveUser(User user){
em.persist(user);
}
public User findUserByEmail(String email){
TypedQuery<User> query = em.createQuery("select m from User as m where m.email = ?1", User.class)
.setParameter(1, email);
return query.getSingleResult();
}
}
package codebook.codeview.service;
import codebook.codeview.auth.MyUserDetail;
import codebook.codeview.entity.User;
import codebook.codeview.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
//import org.springframework.security.core.userdetails.User;
import org.springframework.beans.factory.annotation.Autowired;
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.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
@Service
@RequiredArgsConstructor
@Slf4j
public class UserService implements UserDetailsService {
// @Autowired
private final UserRepository repository;
@Transactional
public void joinUser(User user){
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
user.setPassword(passwordEncoder.encode(user.getPassword()));
repository.saveUser(user);
}
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
//여기서 받은 유저 패스워드와 비교하여 로그인 인증
User user = repository.findUserByEmail(email);
return new MyUserDetail(user);
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>TEST!!!!!!!!</title>
</head>
<body>
TEST!!!!!!!!!!!
</body>
</html>
<!--회원가입 페이지-->
<!--signup.html-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<form method="post" action="/signup">
email : <input type="email" name="email">
password : <input type="password" name="password">
<button>회원가입</button>
</form>
</body>
</html>
<!--유저 페이지-->
<!--user_access.html-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="UTF-8">
<title>user access</title>
</head>
<body>
<span>환영합니다</span>
<p th:text="${info}"></p>
</body>
</html>
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:tcp://localhost/~/codeview
username: sa
password:
jpa:
hibernate:
#옵션 create, update, none, create-drop, validate 있음
ddl-auto: none
properties:
hibernate:
format_sql: true
logging:
level:
org:hibernate:SQL: DEBUG
plugins {
id 'org.springframework.boot' version '2.5.5'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'codebook'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
// compileOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
참고와는 다르게 sql을 h2를 사용
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
SQL Error: 23502, SQLState: 23502
NULL not allowed for column "ID"; SQL statement:
insert into user (id, email, password, role) values (null, ?, ?, ?) [23502-200]
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause
테이블을 생성해보고 H2로 SQL 설정도 바꾸고 애노테이션도 몇개 추가해보았지만 해결하지 못함
오늘은 에러를 미처 해결하지 못했다.
후회하는 점:
maven이 아닌 gradle으로 프로젝트를 생성한 것.
MySQL이 아닌 H2를 사용한 것.
내가 Spring Security를 잘 모른다는 점..