하나씩 차근차근
article thumbnail

이번 포스트에서는 로그인 기능을 구현하겠습니다.

 

시작

스프링 시큐리티 dependency 를 적용시키고 브라우저를 통해 접속을 하면 아래와 같은 화면이 출력되었습니다.

이전에 만들었던 SecurityConfig 파일에서 설정을 해서 모든 url 을 인증없이 접속할 수 있도록 했으며,

다음으로 위의 로그인 페이지는 직접 만든 로그인 페이지로 바꾸도록 설정 파일을 작성하겠습니다.

package com.crud;

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.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http.authorizeHttpRequests().requestMatchers(
				new AntPathRequestMatcher("/**")).permitAll()
			.and()
				.formLogin()
				.loginPage("/user/login")
				.defaultSuccessUrl("/");
		
		return http.build();
	}
}

formLogin() 과 loginPage("접속할 url ") 를 통해 로그인 페이지를 직접 만든 url 로 연결시킬 수 있으며,

로그인이 성공했을 경우 defaultSuccessUrl("로그인 성공했을 경우 이동할 url") 을 통해 url 를 설정할 수 있습니다.

 

로그인 폼

다음으로 로그인 폼인 login_form.html 을 작성합니다.

<html layout:decorate="~{layout}">
	<div layout:fragment="content" class="container my-3">
		<form th:action="@{/user/login}" method="post">
			<div th:if="${param.error}">
				<div class="alert alert-danger">
					아이디 또는 비밀번호를 확인해주세요.
				</div>
			</div>
			<div class="mb-3">
				<label for="username" class="form-label">아이디</label>
				<input type="text" name="username" id="username" class="form-control">
			</div>
			<div class="mb-3">
				<label for="password" class="form-label">비밀번호</label>
				<input type="password" name="password" id="password" class="form-control">
			</div>
			<button type="submit" class="btn btn-primary">로그인</button>
		</form>
	</div>
</html>

네비게이션 메뉴에서 로그인페이지에 접속할 수 있도록 layout.html 의 로그인 <a> 태그에 url 을 연결합니다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
	...
 
</head>
<body>
	...
	            <ul class="navbar-nav me-auto mb-2 mb-lg-0">
	                <li class="nav-item">
	                    <a class="nav-link" href="/user/login">로그인</a>
	                </li>
	                <li class="nav-item">
	                    <a class="nav-link" href="/user/signup">회원가입</a>
	                </li>
	            </ul>
	...
</body>
</html>

UserRepository

다음으로 User 를 username 을 통해 조회할 수 있는 메서드를 UserRepository 에 추가합니다.

package com.crud.repository;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

import com.crud.model.User;

public interface UserRepository extends JpaRepository<User, Long>{

	Optional<User> findByusername(String username);
}

 

 

UserSecurityService

스프링 시큐리티 설정에 사용할 UserSecurityService 를 생성하고 작성합니다.

package com.crud.service;

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class UserSecurityService implements UserDetailsService {

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		// TODO Auto-generated method stub
		return null;
	}

}

UserSecurityService 는 스프링 시큐리티의 UserDetailsService 인터페이스를 상속받는 클래스로

loadUserByUsername 메서드를 구현해야 하므로 다음과 같이 작성합니다.

package com.crud.service;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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 com.crud.model.User;
import com.crud.model.UserRole;
import com.crud.repository.UserRepository;

@Service
public class UserSecurityService implements UserDetailsService {
	
	@Autowired
	private UserRepository userRepository;
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		
		Optional<User> _user = userRepository.findByusername(username);
		
		if(_user.isEmpty()) {
			throw new UsernameNotFoundException("사용자를 찾을 수 없습니다.");
		}
		
		User user = _user.get();	
		List<GrantedAuthority> authorities = new ArrayList<>();
		
		if("admin".equals(username)) {
			authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
		} else {
			authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
		}
		
		return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
	}

}

 loadUserByUsername 메서드는 UserRepository 의 findByusername 으로 user 를 조회하고

가입된 유저가 없을 경우 UsernameNotFoundException 을 발생합니다.

또한 아이디가 "admin" 인 경우 ADMIN 권한을 부여하고 그 외의 경우 USER 권한을 부여합니다.

 

SecurityConfig 추가

다음으로 스프링 시큐리티 설정파일인 SecurityConfig 에 AuthenticationManager 빈을 추가합니다.

package com.crud;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
	...
	
	@Bean
	AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
		return authenticationConfiguration.getAuthenticationManager();
	}
}

 

결과

Springboot 를 실행시키고 로그인을 해보면 아래와 같은 화면이 나옵니다.

이것은 PasswordEncoder 가 연결이 되어있지 않아서 발생하는 문제인데

앞의 회원가입 단계에서 UserService 클래스에 BCryptPasswordEncoder 를 생성해서 사용했습니다.

이것을 스프링 시큐리티 설정파일에 빈으로 등록해서 사용하는것으로 수정하겠습니다.

package com.crud;

...

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	...
	
	@Bean
	PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
	
	@Bean
	AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
		return authenticationConfiguration.getAuthenticationManager();
	}
}

위와 같이 BCryptPasswordEncoder 를 빈으로 등록합니다.

회원가입 단계에서 만든 UserService 클래스 또한 빈으로 등록한 PasswordEncoder 를 사용하는것으로 수정합니다.

package com.crud.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import com.crud.model.User;
import com.crud.repository.UserRepository;

@Service
public class UserService {

	@Autowired
	private UserRepository userRepository;
	
	@Autowired
	private PasswordEncoder passwordEncoder;
	
	public User create(String username, String email, String password) {
		User user = new User();
		
		user.setUsername(username);
		user.setEmail(email);
		user.setPassword(passwordEncoder.encode(password));
		
		userRepository.save(user);
		return user;
	}	
}

로그인을 다시 해보면 제대로 접속이 되는것을 확인할 수 있습니다.

profile

하나씩 차근차근

@jeehwan_lee

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!