63일차 내용 정리

채공부·2025년 8월 21일

Security

  • Security 요청 & 응답 흐름
http://localhost:9000       ⟶ 요청
http://localhostL9000/login ⟵ 리다이렉트 응답
                            ⟶ 로그인 요청
http://localhost:9000       ⟵ 리다이렉트 응답
  • Security 기본 동작
Get  /login  ⟶ 로그인 폼이 응답된다
Post /login  ⟶ 로그인 프로세스가 진행된다
Get  /logout ⟶ 로그아웃 프로세스가 진행된다

SecurityConfig(보안) 설정

spring07.config 패키지 생성

  • SecurityConfig 클래스 생성
  • @Configuration : 설정에 관련된 bean 이라고 알려준다
  • @EnableWebSecurity : Spring Security 를 설정하기 위한 어노테이션
  • 메소드에 @Bean 어노테이션을 붙여 놓으면?
    1. spring 이 자동으로 해당 메소드를 호출
    2. 매개변수에 필요한 type 객체가 있으면 알아서 spring bean container 에서 해당 type 객체를 찾아서 전달
    3. 또한 메소드가 리턴해주는 객체를 spring bean container 에서 관리
@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
		// security 를 통과하는 white list
		String[] whiteList = {"/", "/user/loginform", "/user/login-fail", "/user/expired"};
		
		httpSecurity
		.csrf(csrf->csrf.disable()) // csrf 검증 사용하지 않기
		.authorizeHttpRequests(config->
			config
			.requestMatchers(whiteList).permitAll() // whiteList 는 무조건 받아 들이고
			.anyRequest().authenticated() // 다른 모든 요청은 인증을 진행한다
		)
		.formLogin(config->
			config
			.loginPage("/user/required-loginform")
			.loginProcessingUrl("/user/login")
			.usernameParameter("userName")
			.passwordParameter("password")
			.successForwardUrl("/user/login-success")
			.failureForwardUrl("/user/login-fail")
			.permitAll()
		)
		.logout(config->
			config
				.logoutUrl("/user/logout")
				.logoutSuccessUrl("/")
				.permitAll()
		)
		.exceptionHandling(config->
			config.accessDeniedPage("/user/denied")
		)
		.sessionManagement(config->
			config
				.maximumSessions(1)
				.expiredUrl("/user/expired")
		);
        
        return httpSecurity.build();
	}
}

➜ 해당 요청을 처리할 controller 와 view page 는 우리가 만들어야 한다

  • UserController 생성
  • loginForm & required_loginform & loginSuccess loginFail 메소드 생성
@Controller
public class UserController {
	@GetMapping("/user/loginform")
	public String loginForm() {
		// templates/user/loginform.html 페이지로 forward 이동해서 응답
		return "user/loginform";
	}
	
	// 로그인이 필요한 요청경로를 로그인하지 않은 상태로 요청하면 리다이렉트 되는 경로
	@GetMapping("/user/required-loginform")
	public String required_loginform() {
		return "user/required-loginform";
	}
	
	// POST 방식 /user/login 요청 후 로그인 성공인 경우 forward 이동될 url
	@PostMapping("/user/login-success")
	public String loginSuccess() {
		return "user/login-success";
	}
	
	// 로그인 폼을 제출(post) 한 로그인 프로세스 중에 forward 되는 경로이기 때문에 @PostMapping 임에 주의!
	@PostMapping("/user/login-fail")
	public String loginFail() {
		// 로그인 실패임을 알릴 페이지
		return "user/login-fail";
	}
}	
  • home.html 에 링크 추가
<ul>
	<li><a th:href="@{/study}">STUDY</a></li>
</ul>
  • HomeController 클래스에 study 메소드 생성
@GetMapping("/study")
@ResponseBody
public String study() {
		
	return "let's study";
}
  • user 폴더에 required-loginform.html 생성
<h3>로그인이 필요 합니다</h3>
<form th:action="@{/user/login}" method="post">
	<input type="text" name="userName" placeholder="사용자명..."/>
	<input type="password" name="password" placeholder="비밀번호..."/>
	<button type="submit">로그인</button>
</form>
  • login-success.html 생성
<p>로그인 되었습니다</p>
<a th:href="@{/}">인덱스로</a>
  • login-fail.html 생성
<script>
	alert("아이디 혹은 비밀번호가 틀려요");
	location.href="[[@{/user/loginform}]]";
</script>

STUDY 링크를 클릭하면 해당 페이지로 이동

⭐ "study config" 는 화이트리스트에 존재하지 않기에
    인증한 사용자만 접근 가능하여 로그인 폼으로 리다이렉트 된다

SecurityConfig 에서 .formLogin(config-> 안의 코드 분석

  • 인증을 거치지 않은 사용자를 리다이렉트 시킬 경로 설정
.loginPage("/user/required-loginform")`
  • 로그인 폼의 action (로그인 처리는 spring security 가 대신 해준다)
.loginProcessingUrl("/user/login")`
  • 로그인 폼의 사용자명 입력란의 name 속성의 값을
    <input type="text" name="userName">
.usernameParameter("userName")
  • 로그인 폼의 비밀번호 입력란의 name 속성의 값
    <input type="text" name="password">
.passwordParameter("password")
  • 로그인 성공시 forward 이돌될 url (login welcome 응답)
.successForwardUrl("/user/login-success")
  • 계정, 비밀번호 문제로 로그인 실패시 forward 이동될 url (실패되었다고 응답)
.failureForwardUrl("/user/login-fail")
  • 위의 요청은 whiteList 에 없어도 요청 가능하도록 설정
.permitAll()

로그인 결과 확인

⭐ /user/login-success 가 아닌 /user/login 으로 forward 이동
    요청 처리는 /user/login-success 에서 했다

로그인 상태 표시

  • home.html 에 로그인 & 로그아웃 코드 추가
  • sec:authorize="isAuthenticated()"
    로그인된 사용자만 보이게 한다

  • sec:authorize="!isAuthenticated()"
    로그인 안 한 사용자만 보이게 한다

  • sec:authentication="name"
    로그인된 사용자의 이름=출력

  • [[${#authentication.principal.username}]]
    로그인한 사용자 객체의 username 속성 출력

<div sec:authorize="isAuthenticated()">
	<strong sec:authentication="name"></strong> 님 로그인중
	<br />
	<strong>[[${#authentication.principal.username}]]</strong> 님 로그인중
	<br />
	<a th:href="@{/user/logout}">로그아웃</a>
</div>
		
<div sec:authorize="!isAuthenticated()">
	<a th:href="@{/user/loginform}">로그인 하러 가기</a>
</div>
  • loginform.html 생성
<h3>로그인 폼</h3>
<form th:action="@{/user/login}" method="post">
	<input type="text" name="userName" placeholder="사용자명"/>
	<input type="password" name="password" placeholder="비밀번호"/>
	<button type="submit">로그인</button>
</form>
  • SecurityConfig 에 메소드 추가
// 비밀번호를 암호화 해주는 객체를 bean 으로 만든다
@Bean
PasswordEncoder passwordEncoder() { 
	// 여기서 리턴해주는 객체도 bean 으로 된다
	return new BCryptPasswordEncoder();
}

// 인증 메니저 객체를 bean 으로 만든다 (Spring Security 가 자동 로그인 처리할때도 사용되는 객체)
@Bean
AuthenticationManager authenticationManager(HttpSecurity http,
		BCryptPasswordEncoder encoder, UserDetailsService service) throws Exception{
	// 적절한 설정을한 인증 메니저 객체를 리턴해주면 bean 이 되어서 Spring Security 가 사용한다 
	return http.getSharedObject(AuthenticationManagerBuilder.class)
			.userDetailsService(service)
			.passwordEncoder(encoder)
			.and()
			.build();
}

spring07.service 패키지 생성

  • CustomUserDetailService 클래스 생성
  • Spring Security 가 로그인 처리시 호출하는 메소드를 가지고 있는 서비스 클래스 정의하기
@Service // bean 으로 만들기 위한 어노테이션
public class CustomUserDetailService implements UserDetailsService{

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		return null;
	}
}

권한

spring07.dto 패키지 생성

  • UserDto 클래스 생성
  • @AllArgsConstructor
    모든 필드값을 전달받는 생성자 (Builder 를 사용하려면 필요)
  • @NoArgsConstructor : 빈 생성자
  • @Data : setter & getter
@Builder
@AllArgsConstructor // 모든 필드값을 전달받는 생성자 (Builder 를 사용하려면 필요)
@NoArgsConstructor // 빈 생성자
@Data // setter & getter
public class UserDto {
	private long num;
	private String userName;
	private String password;
	private String email;
	private String role;
	private String profileImage;
	private String createdAt;
	private String updatedAt;
}
  • CustomUserDetailService 에 loadUserByUsername 메소드 안에 코드 추가
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
	// 원래는 DB 에서 dao 를 이용해 username 에 해당하는 사용자정보(UserDto) 를 얻어와야 한다
	// 지금은 sample 데이터를 만들어서 사용하기
		
	// 실제 DB 에는 ROLE_XXX 형식으로 저장이 되어 있어야한다 
	String role="";
	if(username.equals("kimgura")) {
		role="ROLE_USER";
	}else if(username.equals("batman")) {
		role="ROLE_STAFF";
	}else if(username.equals("superman")){
		role="ROLE_ADMIN";
	}
		
	// 비밀번호를 암호화 하기 위한 객체 
	BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
	// DB 에는 암호화된 비밀번호가 저장되어 있다고 가정하자 (암호화 하기 이전의 비밀번호가 1234 라고 가정)
	String encodedPwd = encoder.encode("1234");
		
	// Sample UserDto 객체 만들기(원래는 DB 에서 읽어온 데이터)
	UserDto dto=UserDto.builder()
				.userName(username)
				.password(encodedPwd)
				.email("aaa@naver.com")
				.role(role)
				.build();
		
	// 권한 목록을 List 에 담아서  (지금은 1개 이지만)
	List<GrantedAuthority> authList = new ArrayList<>();
	authList.add(new SimpleGrantedAuthority(dto.getRole()));
		
	// UserDetails 객체를 생성해서 
	UserDetails ud = new User(dto.getUserName(), dto.getPassword(), authList);
	// 리턴해준다
	return ud;
}
  • SecurityConfig 에 requestMatchers 코드 추가

  • 역할에 따라서 요청 가능한 경로를 구분

.authorizeHttpRequests(config->
	config
	.requestMatchers(whiteList).permitAll() // whiteList 는 무조건 받아 들이고
	.requestMatchers("/admin/**").hasRole("ADMIN")
	.requestMatchers("/staff/**").hasAnyRole("ADMIN", "STAFF")
	.anyRequest().authenticated() // 다른 모든 요청은 인증을 진행한다
)
  • UserController 에 userDenied & userList & userManage 메소드 추가
// 권한 부족 시 or 403 인 경우
@RequestMapping("/user/denied")
public String userDenied() { // GET, POST 등 모두 가능
	return "user/denied";
}
	
// ROLL_STAFF, ROLL_ADMIN 만 요청 가능
@GetMapping("/staff/user/list")
public String userList() {
	return "user/list";
}
	
// ROLL_ADMIN 만 요청 가능
@GetMapping("/admin/user/manage")
public String userManage() {
		return "user/manage";
}
  • home.html 링크 추가
<li><a th:href="@{/staff/user/list}">회원 목록(STAFF, ADMIN 전용)</a></li>
<li><a th:href="@{/admin/user/manage}">회원 관리(ADMIN 전용)</a></li>

SecurityConfig 에서 .logout(config-> 안의 코드 분석

  • 로그아웃시 요청할 url (spring security 가 대신 처리)
.logoutUrl("/user/logout")
  • 로그아웃 후에 리다이렉트 이돌할 url
.logoutSuccessUrl("/")

SecurityConfig 에서 .exceptionHandling(config-> 안의 코드 분석

  • 권한 부족 시 forward 이동될 url
config.accessDeniedPage("/user/denied")

SecurityConfig 에서 .sessionManagement(config-> 안의 코드 분석

  • 최대 세션 허용 갯수 설정(다중 기기 로그인 허용 여부)
.maximumSessions(1)
  • 로그인이 해제되었을 때 응답할 url
.expiredUrl("/user/expired")

CustomUserDetailService 클래스 코드 분석

  1. 클라이언트가 로그인을 시도하면 이 메소드를 spring security 가 호출한다

  2. 메소드를 호출하면서 클라이언트가 입력한 userName 을 전달해준다

  3. 우리는 전달된 userName 을 이용해서 DB 에서 해당 유저의 정보를 select 해야 한다

  4. 만일 select 된 유저 정보가 없으면 예외를 발생시키면 되고

  5. select 된 정보가 있다면 UserDetails type 객체에 select 된 정보 (비밀번호, 권한 등)를 담아서 리턴한다

  6. 이 메소드에서 리턴한 UserDetails 객체를 이용해서 spring security 가 로그인 처리를 대신해 준다

@Service
public class CustomUserDetailService implements UserDetailsService{
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		
		String role="";
		if(username.equals("kimgura")) {
			role="ROLE_USER";
		}else if(username.equals("batman")) {
			role="ROLE_STAFF";
		}else if(username.equals("superman")){
			role="ROLE_ADMIN";
		}
		
		BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
		String encodedPwd = encoder.encode("1234");
		
		UserDto dto=UserDto.builder()
					.userName(username)
					.password(encodedPwd)
					.email("aaa@naver.com")
					.role(role)
					.build();
		
		List<GrantedAuthority> authList = new ArrayList<>();
		authList.add(new SimpleGrantedAuthority(dto.getRole()));
		
		UserDetails ud = new User(dto.getUserName(), dto.getPassword(), authList);
		return ud;
	}
}

로그인 성공 후 처리

  • AuthSuccessHandler 클래스 생성

  • 로그인 성공 후에 호출될 메소드를 가지고 있는 클래스 정의하기

public class AuthSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler{
	// 1. 요청 캐쉬 객체를 직접 생성해서 
    private RequestCache requestCache = new HttpSessionRequestCache();
    
    // 2. 생성자에서 부모 객체에 전달
    public AuthSuccessHandler() {
        super.setRequestCache(requestCache);
    }
	
	// 입력한 username 와 password 가 일치해서 로그인이 성공하면 이 메소드가 호출된다 
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws ServletException, IOException {
		
		// 세션 유지 시간 설정
		HttpSession session=request.getSession();
        session.setMaxInactiveInterval(60);//초단위로 설정
        
        // Authentication 객체의 메소드를 이용해서 지금 로그인된 사용자에 대한 자세한 정보를 얻어낼수 있다
        String userName = authentication.getName();
        System.out.println("로그인된 사용자 : "+userName);
		
		// 3. 로그인 성공이후 미리 저장된 요청이 있었는지 읽어와서
        SavedRequest cashed = requestCache.getRequest(request, response);
    	
        //4. 만일 미리 저장된 요청이 없다면 (로그인 하지 않은 상태로 인증이 필요한 경로를 요청하지 않았다면)
        if(cashed==null) {
    			// 5. 로그인 환영 페이지로 foward 이동 (POST 방식 요청임에 주의!!!)
        		RequestDispatcher rd = request.getRequestDispatcher("/user/login-success");
        		rd.forward(request, response);
    		} else {
    			// 6. 원래 가려던 목적지 경로로 리다일렉트 이동 시킨다 (GET 방식 요청 파라미터도 자동으로 같이 가지고 간다)
    			super.onAuthenticationSuccess(request, response, authentication);
        }
	}
}
  • SeculityConfig 에 코드 주석 후 코드 추가
// .successForwardUrl("/user/login-success") : 주석 처리
// 로그인 성공 시 동작하는 핸들러 객체 등록하기
.successHandler(new AuthSuccessHandler())
  • home.html 에 코드 추가
  • Spring Security 속성을 HTML에서 안전하게 쓰기 위해 필요
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

Thymeleaf에서 Spring Security 태그 사용을 위한 xmlns 설정

  1. Web ➜ HTML Files ➜ Editor ➜ Templates ➜ New HTML File(5) ➜ Edit

  1. xmlns:sec="http://www.thymeleaf.org/extras/spring-security" ➜ OK

  • HomeController 에 study 메소드에 코드 추가
// 컨트롤러나 서비스에서 로그인된 userName 을 알고 싶을 때
String userName = SecurityContextHolder.getContext().getAuthentication().getName();
System.out.println("로그인된 userName : "+userName);

Example

Spring08_Example 프로젝트 생성

패키지 생성
1. spring08_controller
2. spring08_dto
3. spring08_repository
4. spring08_config
5. spring08_service

  • application.properties 에 서버 & 경로 & DB 접속 코드 추가
server.port=9000
server.servlet.context-path=/spring08

# oracle DataSource Setting ( Connection Pool )
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:xe
spring.datasource.username=scott
spring.datasource.password=TIGER

# mybatis 에서 사용하는 xml 문서가 어디에 있는지 알려주기
# classpath: 은 resources 폴더를 가리킨다
mybatis.mapper-locations=classpath:mapper/*.xml
# type 에 별칭을 붙인 클래스를 찾아서 로딩하기 위해
# com.example.spring04. 하위 패키지에서 모든 @Alias 가 붙은 클래스를 찾아서 사용할 준비를 한다
mybatis.type-aliases-package=com.example.spring08.**
  • HomeController 클래스 생성
@Controller
public class HomeController {

	@GetMapping("/")
	public String home(Model model) {
		
		List<String> notice = List.of("공지사항 입니다", "공지1", "공지2");
		// 응답에 필요한 정보를 Model 객체에 담고
		model.addAttribute("notice", notice);
		// Thymeleaf 템플릿 페이지에서 응답 하기
		return "home";
	}
}
  • home.html 생성
<div class="container">
	<h1>인덱스 페이지</h1>
		
	<h2>공지사항</h2>
	<ul>
		<li th:each="tmp : ${notice}" th:text="${tmp}"></li>
	</ul>
</div>
  • UserDto 클래스 생성
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class UserDto {
	private long num;
	private String userName;
	private String password;
	private String email;
	private String role;
	private String profileImage;
	private String createdAt;
	private String updatedAt;
}
  • AuthSuccessHandler 클래스 생성
public class AuthSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler{
    private RequestCache requestCache = new HttpSessionRequestCache();
    
    public AuthSuccessHandler() {
        super.setRequestCache(requestCache);
    }
	
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws ServletException, IOException {
		
		HttpSession session=request.getSession();
        session.setMaxInactiveInterval(60);
        
        String userName = authentication.getName();
        System.out.println("로그인된 사용자 : "+userName);
		
        SavedRequest cashed = requestCache.getRequest(request, response);
    	
        if(cashed==null) {
        		RequestDispatcher rd = request.getRequestDispatcher("/user/login-success");
        		rd.forward(request, response);
    		} else {
    			super.onAuthenticationSuccess(request, response, authentication);
        }
	}
}
  • SecurityConfig 클래스 생성
@Configuration
@EnableWebSecurity
public class SecurityConfig {
	
	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
		String[] whiteList = {"/", "/user/loginform", "/user/login-fail", "/user/expired"};
		
		httpSecurity
		.csrf(csrf->csrf.disable())
		.authorizeHttpRequests(config->
			config
			.requestMatchers(whiteList).permitAll()
			.requestMatchers("/admin/**").hasRole("ADMIN")
			.requestMatchers("/staff/**").hasAnyRole("ADMIN", "STAFF")
			.anyRequest().authenticated()
		)
		.formLogin(config->
			config
			.loginPage("/user/required-loginform")
			.loginProcessingUrl("/user/login")
			.usernameParameter("userName")

			.passwordParameter("password")

			.successHandler(new AuthSuccessHandler())

			.failureForwardUrl("/user/login-fail")
			.permitAll()
		)
		.logout(config->
			config
				.logoutUrl("/user/logout")
				.logoutSuccessUrl("/")
				.permitAll()
		)
		.exceptionHandling(config->
			config.accessDeniedPage("/user/denied")
		)
		.sessionManagement(config->
			config
				.maximumSessions(1)
				.expiredUrl("/user/expired")
		);
		
		return httpSecurity.build();
	}
	
	@Bean
	PasswordEncoder passwordEncoder() { 
		return new BCryptPasswordEncoder();
	}
	@Bean
	AuthenticationManager authenticationManager(HttpSecurity http,
			BCryptPasswordEncoder encoder, UserDetailsService service) throws Exception{
		return http.getSharedObject(AuthenticationManagerBuilder.class)
				.userDetailsService(service)
				.passwordEncoder(encoder)
				.and()
				.build();
	}
}
  • UserController 클래스 생성
@Controller
public class UserController {
	
	@RequestMapping("/user/denied")
	public String userDenied() { 
		return "user/denied";
	}
	
	@GetMapping("/user/loginform")
	public String loginForm() {
		return "user/loginform";
	}
	
	@GetMapping("/user/required-loginform")
	public String required_loginform() {
		return "user/required-loginform";
	}
	
	@PostMapping("/user/login-success")
	public String loginSuccess() {
		return "user/login-success";
	}
	
	@PostMapping("/user/login-fail")
	public String loginFail() {
		return "user/login-fail";
	}	
}
  • home.html 에 로그인&로그아웃 과 회원가입 링크 코드 추가
<div sec:authorize="isAuthenticated()">
	<strong sec:authentication="name"></strong>님 로그인중
	<a th:href="@{/user/logout}">로그아웃</a>
</div>
<div sec:authorize="!isAuthenticated()">
	로그인중X
	<a th:href="@{/user/loginform}">로그인 하러 가기</a>
	<a th:href="@{/user/signup-form}">회원가입</a>
</div>
  • resources 폴더 안에 에 mapper 폴더 생성 후 UserMapper.xml 생성
<mapper namespace="user">
	<!-- 자주 사용하는 칼럼의 이름을 미리 정의해 놓고 참조해서 사용하기 -->
	<sql id="columnName">
		num, userName, password, email, profileImage, role, updatedAt, createdAt
	</sql>

	<update id="update" parameterType="com.example.spring08.dto.UserDto">
		UPDATE users
		SET email=#{email}, updatedAt=SYSDATE
		<if test="profileImage != null">, profileImage=#{profileImage}</if>
		WHERE userName=#{userName}
	</update>

	<update id="updatePassword" parameterType="com.example.spring08.dto.UserDto">
		UPDATE users
		SET password=#{newPassword}
		WHERE userName=#{userName}
	</update>

	<insert id="insert" parameterType="com.example.spring08.dto.UserDto">
		INSERT INTO users
		(num, userName, password, email)
		VALUES(users_seq.NEXTVAL, #{userName}, #{password}, #{email})
	</insert>
	
	<select id="getByNum" parameterType="long" resultType="com.example.spring08.dto.UserDto">
		SELECT <include refid="columnName"/>
		FROM users
		WHERE num=#{num}
	</select>
	
	<select id="getByUserName" parameterType="string" resultType="com.example.spring08.dto.UserDto">
		SELECT <include refid="columnName"/>
		FROM users
		WHERE userName=#{userName}
	</select>
</mapper>
  • UserDao 인터페이스 생성
public interface UserDao {
	public void insert(UserDto dto);
	public int update(UserDto dto);
	public int updatePassword(UserDto dto);
	public UserDto getByNum(long num);
	public UserDto getByUserName(String userName);
}
  • UserDaoImpl 클래스 생성
  • @Repository 어노테이션을 붙여서 bean 생성
    SqlException 이 발생하면 자동으로 DataAccessException 으로 변환해서 발생시켜 준다
    ➜ 그렇게 transaction 관리가 편해진다
@Repository
@RequiredArgsConstructor
public class UserDaoImpl implements UserDao{
	
	// lombok 에서 제공해주는 @RequiredArgsConstructor 어노테이션을 이용해서 의존 객체를 생성자로 주입받도록 한다
	private final SqlSession session;
	
	@Override
	public void insert(UserDto dto) {
		session.insert("user.insert", dto);
	}

	@Override
	public int update(UserDto dto) {
		return session.update("user.update", dto);
	}

	@Override
	public int updatePassword(UserDto dto) {
		return session.update("user.updatePassword", dto);
	}

	@Override
	public UserDto getByNum(long num) {
		UserDto dto = session.selectOne("user.getByNum", num);
		return dto;
	}

	@Override
	public UserDto getByUserName(String userName) {
		UserDto dto = session.selectOne("user.getByUserName", userName);
		return dto;
	}
}
  • CustomUserDetailsService 생성
@Service // bean 으로 만들기 위한 어노테이션
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService{
	
	// UserDao 객체 주입 받기
	private final UserDao dao;
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		// DB 에서 userName 에 해당하는 정보가 있는지 select 해준다
		UserDto dto = dao.getByUserName(username);
		
		// 만일 존재 하지 않는 사용자라면
		if(dto == null) {
			throw new UsernameNotFoundException("존재하지 않은 사용자 입니다");
		}
		
		// 권한 목록을 List 에 담아서  (지금은 1개 이지만)
		List<GrantedAuthority> authList = new ArrayList<>();
		authList.add(new SimpleGrantedAuthority(dto.getRole()));
		
		// UserDetails 객체를 생성해서 
		UserDetails ud = new User(dto.getUserName(), dto.getPassword(), authList);
		// 리턴해준다
		return ud;
	}
}   

회원가입

  • SecurityConfig 에 화이트리스트 추가
String[] whiteList = {"/", "/user/loginform", "/user/login-fail",
				"/user/expired", "/user/signup-form"};
  • UserController 에 signupForm 메소드 생성

  • 회원가입 폼 요청 처리

@GetMapping("/user/signup-form")
public String signupForm() {
	return "user/signup-form";
}
  • signup-form.html 생성

  • novalidate : 브라우저 기본 폼 검사 동작을 꺼주는 속성

<div class="container">
	<h1>회원가입 폼 양식</h1>
	<form th:action="@{/user/signup}" method="post" novalidate>
		<div>
			<label for="userName">아이디</label>
			<input type="text" name="userName" id="userName"/>
		</div>
		<div>
			<label for="password">비밀번호</label>
			<input type="text" name="password" id="password"/>
		</div>
		<div>
			<label for="email">이메일</label>
			<input type="email" name="email" id="email"/>
		</div>
		<button type="submit">가입</button>
	</form>
</div>
  • SecurityConfig 에 화이트리스트 추가
String[] whiteList = {"/", "/user/loginform", "/user/login-fail",
			"/user/expired", "/user/signup-form", "/user/signup"};
  • UserDto 에 signup 메소드 생성

  • 회원가입 요청 처리

@PostMapping("/user/signup")
public String signup(UserDto dto) {
	return "user/signup";
}
  • UserService 인터페이스 생성
public interface UserService {
	public void createUser(UserDto dto);
}
  • UserServiceImpl 클래스 생성
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService{
	
	private final UserDao dao;
	
	// 사용자를 추가하는 메소드
	@Override
	public void createUser(UserDto dto) {
		dao.insert(dto);
	}
}
  • UserController 에 signup 메소드 수정 및 의존 코드 추가
@RequiredArgsConstructor

private final UserService service;

// 회원가입 요청 처리
@PostMapping("/user/signup")
public String signup(UserDto dto) {
	// UserService 객체를 이용해서 사용자 정보를 추가
	service.createUser(dto);
		
	return "user/signup";
}
  • UserServiceImpl 비밀번호 암호화 코드 추가

  • 비밀번호를 암호화 하기 위한 객체도 spring bean container 로 주입 받는다

private final PasswordEncoder encoder;

// 사용자를 추가하는 메소드
@Override
public void createUser(UserDto dto) {
	// 날 것의 비밀번호를 암호화해서
	String encodedPwd = encoder.encode(dto.getPassword());
	// dto 에 다시 담는다
	dto.setPassword(encodedPwd);
	// DB 에 저장
	dao.insert(dto);
}
  • signup.html 생성
<div class="container">
	<strong th:text="${param.userName}"></strong>님 가입되었습니다
	<a th:href="@{/user/loginform}">로그인</a>
</div>
profile
학원 공부 내용 정리

0개의 댓글