각진 세상에 둥근 춤을 추자
[Spring Boot] Spring Security 회원 등록 (인증, 인가) + BCryptPasswordEncoder 본문
[Spring Boot] Spring Security 회원 등록 (인증, 인가) + BCryptPasswordEncoder
circle.j 2023. 1. 13. 19:34이전 글에 이어서
[Spring Boot] Spring Security 처리 과정 + 로그인 예제 (인증, 인가)
이전 게시글에 이어 회원 등록 기능을 추가함과 동시에 코드를 좀더 편리한 방식으로 수정해 본다.
프로젝트 - src/main/resources - templates - user2 - register.html 생성
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>user2::register</title>
</head>
<body>
<h3>user2 회원가입</h3>
<a th:href="@{/}">메인이동</a>
<a th:href="@{/user2/login}">로그인</a>
<form th:action="@{/user2/register}" method="post">
<table border="1">
<tr>
<td>아이디</td>
<td><input type="text" name="uid"/></td>
</tr>
<tr>
<td>비밀번호</td>
<td><input type="password" name="pass"/></td>
</tr>
<tr>
<td>이름</td>
<td><input type="text" name="name"/></td>
</tr>
<tr>
<td>휴대폰</td>
<td><input type="text" name="hp"/></td>
</tr>
<tr>
<td>나이</td>
<td><input type="number" name="age"/></td>
</tr>
<tr>
<td colspan="2" align="right"><input type="submit" value="회원가입"/></td>
</tr>
</table>
</form>
</body>
</html>
프로젝트 - src/main/java - kr.co.user - vo - User2VO.java
DB의 칼럼(pass, grade, rdate) 을 추가한 후, VO에서 추가한 칼럼을 생성한다.
package kr.co.ch08.vo;
import java.time.LocalDateTime;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.CreationTimestamp;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Entity
@Table(name = "user2")
public class User2VO {
@Id
private String uid;
private String pass;
private String name;
private int grade;
private String hp;
private int age;
@CreationTimestamp
private LocalDateTime rdate;
}
날짜는 기존의 sql문 NOW()가 아닌 LocalDateTime을 이용해 선언한다.
프로젝트 - src/main/java - kr.co.user - controller - User2Controller.java
GetMapping, PostMapping한 register 페이지에 대한 내용을 덧붙인다.
package kr.co.ch08.controller;
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 kr.co.ch08.service.User2Service;
import kr.co.ch08.vo.User2VO;
@Controller
public class User2Controller {
@Autowired
private User2Service service;
@GetMapping("/user2/login")
public String login() {
return "/user2/login";
}
@GetMapping("/user2/loginSuccess")
public String loginSuccess() {
return "/user2/loginSuccess";
}
@GetMapping("/user2/register")
public void register() {
}
@PostMapping("/user2/register")
public String register(User2VO vo) {
service.insertUser(vo);
return "redirect:/user2/login";
}
}
앞서 User2Controller에서 PostMapping 내용에서 작성한 service인 insertUser를 작성한다.
이때 repository를 통해 해당 내용을 저장하기 전, BCryptPasswordEncoder를 통해 작성한 비밀번호를 암호화해 준다.
프로젝트 - src/main/java - kr.co.user - service 패키지 - User2Service.java
package kr.co.ch08.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import kr.co.ch08.repository.User2Repo;
import kr.co.ch08.vo.User2VO;
@Service
public class User2Service {
@Autowired
private User2Repo repo;
public void insertUser(User2VO vo) {
// BcryptPassword = SHA2 + Salted (강력 암호화)
BCryptPasswordEncoder passEncoder = new BCryptPasswordEncoder();
vo.setPass(passEncoder.encode(vo.getPass()));
repo.save(vo);
}
public User2VO selectUser2(String uid, String pass) {
return repo.findUser2VOByUidAndPass(uid, pass);
}
}
프로젝트 - src/main/java - kr.co.user - security 패키지 생성 - SecurityConfig.java
비로그인 시 로그인 성공 화면 접근 불가능 기능 + 로그인 인증처리 서비스 등록 (BcryptPassword)
package kr.co.ch08.security;
import org.springframework.beans.factory.annotation.Autowired;
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;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 인가(접근권한) 설정 (index : 모든 링크(사용자)에 대해 허용을 해 준 상태, 권한관리필터)
http.authorizeHttpRequests().antMatchers("/").permitAll();
// admin 하위의 모든 자원 -> "ADMIN"에게 부여
http.authorizeHttpRequests().antMatchers("/admin/**").hasRole("ADMIN");
// member 하위의 모든 자원 -> "ADMIN", "MEMBER" 에게 부여
http.authorizeHttpRequests().antMatchers("/member/**").hasAnyRole("ADMIN", "MEMBER");
// GUEST는 무권한 -> 생략
// loginSuccess 접근 -> "ADMIN"만 접근 허용
http.authorizeHttpRequests().antMatchers("/user2/loginSuccess").hasAnyRole("3", "4", "5");
// 사이트 위변조 요청 방지
http.csrf().disable();
// 로그인 설정
http.formLogin()
.loginPage("/user2/login")
.defaultSuccessUrl("/user2/loginSuccess")
.failureUrl("/user2/login?success=100")
.usernameParameter("uid")
.passwordParameter("pass");
// 로그아웃 설정
http.logout()
.invalidateHttpSession(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/user2/logout"))
.logoutSuccessUrl("/user2/login?success=200");
}
@Autowired
private SecurityUserService service;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 로그인 인증처리 서비스 등록 (기본 SHA2)
//auth.userDetailsService(service).passwordEncoder(new MessageDigestPasswordEncoder("SHA-256"));
// 로그인 인증처리 서비스 등록 (BcryptPassword)
auth.userDetailsService(service).passwordEncoder(new BCryptPasswordEncoder());
}
}
프로젝트 - src/main/java - kr.co.user - security 패키지 - SecurityUserService.java
[기존]
// Security 기본 사용자 객체 생성 : 인증 거치는 과정 (사용자가 폼에 입력한 정보가 맞는지 확인) -> userDts를 세션에 등록
UserDetails userDts = User.builder()
.username(user.getUid())
.password(user.getPass())
.roles("MEMBER") //마지막에 권한 주기
.build();
return userDts;
*/
기존의 방식으로 userDts를 사용하게 되면 userDts에 저장된 정보는 아이디와 비밀번호로 한정되어, 만약 다른 페이지에서 다른 칼럼(이름, 번호 등)을 출력할 수 없다.
이를 변경하기 위해 userDts를 커스텀할 필요가 있다.
[변경]
// MyUserDetails -> @Builder : Builder 방식으로 생성 (build 초기화, build 패턴)
UserDetails myUser = MyUserDetails.builder()
.uid(user.getUid())
.pass(user.getPass())
.name(user.getName())
.grade(user.getGrade())
.hp(user.getHp())
.age(user.getAge())
.rdate(user.getRdate())
.build();
return myUser;
package kr.co.ch08.security;
import org.springframework.beans.factory.annotation.Autowired;
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.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import kr.co.ch08.repository.User2Repo;
import kr.co.ch08.vo.User2VO;
@Service
public class SecurityUserService implements UserDetailsService{
@Autowired
private User2Repo repo;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 스프링 시큐리티 인증 동작방식은 아이디/패스워드를 한 번에 조회하는 방식이 아닌
// 아이디만 이용해서 사용자 정보를 로딩하고 이후 패스워드를 검증하는 방식이다.
// security: 사용자 계정 먼저 확인 (username)
User2VO user = repo.findById(username).get();
if(user == null) {
throw new UsernameNotFoundException(username);
}
/*
// Security 기본 사용자 객체 생성 : 인증 거치는 과정 (사용자가 폼에 입력한 정보가 맞는지 확인) -> userDts를 세션에 등록
UserDetails userDts = User.builder()
.username(user.getUid())
.password(user.getPass())
.roles("MEMBER") //마지막에 권한 주기
.build();
return userDts;
*/
// MyUserDetails -> @Builder : Builder 방식으로 생성 (build 초기화, build 패턴)
UserDetails myUser = MyUserDetails.builder()
.uid(user.getUid())
.pass(user.getPass())
.name(user.getName())
.grade(user.getGrade())
.hp(user.getHp())
.age(user.getAge())
.rdate(user.getRdate())
.build();
return myUser;
}
}
프로젝트 - src/main/java - kr.co.user - security 패키지 - MyUserDetails.java 생성
(UserDetails 커스텀)
package kr.co.ch08.security;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
//userDts는 아이디와 패스워드만 포함 -> 이름, 번호 등 다른 페이지에 출력하기 위해서는 커스텀 필요 -> MyUserDetails
// 로그인하면 MyUserDetails 생성
@Setter
@Getter
@Builder
public class MyUserDetails implements UserDetails{
private static final long serialVersionUID = 1L;
// 필드 정의
private String uid;
private String pass;
private String name;
private int grade;
private String hp;
private int age;
private LocalDateTime rdate;
public void roles(String... grade) {
}
// 사용자 권한 목록 - 로그인한 계정이 갖는 권한 목록
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_"+grade));
return authorities;
}
// 계정이 갖는 비밀번호
@Override
public String getPassword() {
return pass;
}
// 계정이 갖는 아이디
@Override
public String getUsername() {
return uid;
}
// 계정 만료 여부
// true - 만료 안됨, false - 만료(로그인 불가)
@Override
public boolean isAccountNonExpired() {
return true;
}
// 계정 잠김 여부
// true - 잠김 안됨, false - 잠김(로그인 불가)
@Override
public boolean isAccountNonLocked() {
return true;
}
// 계정 비밀번호 만료 여부
// true - 만료 안됨, false - 만료(로그인 불가)
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 계정 활성화 여부
// true - 활성화, false - 비활성화(로그인 불가)
@Override
public boolean isEnabled() {
return true;
}
}
마지막으로 로그인 완료 시 페이지를 앞서 작성한 코딩에 맞게 수정한다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<title>user2::loginSuccess</title>
</head>
<body>
<h3>user2 loginSuccess</h3>
<!-- isAuthenticated(): 로그인 했을 경우 출력 (만약 로그인 안 하고 페이지 강제 이동 시 에러) -->
<p sec:authorize="isAuthenticated()">
아이디 : <span>[[${#authentication.principal.uid}]]</span><br>
이름 : <span>[[${#authentication.principal.name}]]</span><br>
휴대폰 : <span sec:authentication="principal.hp">휴대폰번호</span><br>
나이 : <span sec:authentication="principal.age">나이</span><br>
날짜 : <span sec:authentication="principal.rdate">가입일</span><br>
<a th:href="@{/user2/logout}">로그아웃</a>
</p>
<!-- 로그인을 안 했을 경우 출력 -->
<p sec:authorize="isAnonymous()">
로그인을 하셔야 합니다.<br><br>
<a th:href="@{/user2/login}">로그인</a>
</p>
</body>
</html>
비밀번호는 전부 1234이다.
비암호화 비밀번호 SHA2 암호화 비밀번호 BCrypt 암호화 비밀번호
우선 임의로 grade에 숫자를 작성한 후, grade가 입력된 아이디로 로그인 실행한다.
http://localhost:8080/Ch08/user2/register
http://localhost:8080/Ch08/user2/login
'Spring' 카테고리의 다른 글
[Spring Boot] REST 웹 서비스 + 실습 (0) | 2023.01.17 |
---|---|
[Spring Boot] WebSecurityConfigurerAdapter 지원 중단 (0) | 2023.01.16 |
[Spring Boot] Spring Security 처리 과정 + 로그인 예제 (인증, 인가) (0) | 2023.01.12 |
[Spring Boot] JPA 사용 - 간단 회원 정보 입력, 목록, 수정, 삭제 (0) | 2023.01.11 |
[Spring Boot] 스프링부트 DB 연동 + Lombok 설치 +간단 회원 정보 입력, 목록, 수정 (0) | 2023.01.10 |