개발자의 오르막

Spring Security Login 기능 구현 본문

ORM/JPA

Spring Security Login 기능 구현

계단 2019. 9. 24. 21:04

Spring Security Login 도 되고, 다 되는데,,

내가 하고 싶은 건, 도메인에서 Get 으로 보낼 uid는 따로 Long 타입으로 만들고

사용자들이 로그인 할 때 쓰는 건 Email 을 하고 싶다는 거다.  물론 Email 은 unity 속성을 집어넣는다.

 

 

먼저 구조를 이해하고 있는 것이 제일 중요하다.

로그인 구성을 하기 위해서는 먼저 도메인이 필요하다.

 

# Account 도메인 생성

 

@Getter
@Setter
public class Account {
	
	private long id;
	
	private String email;
	
	private String password;

}

 

- @Getter / @Setter 는 lombok 라이브러리를 사용한 것이다.

   get/set 을 직접 구성하지 않아도 자동으로 생성해준다. 

 

- id는 long 타입으로 정해주고, 로그인할 Email 과 password 를 정의해준다.

 

 

# MVCConfig

@Configuration
public class MvcConfig implements WebMvcConfigurer{

	@Override
	public void addViewControllers(ViewControllerRegistry registry) {
		registry.addViewController("/guest").setViewName("guest");
		registry.addViewController("/").setViewName("index");
		registry.addViewController("/index").setViewName("index");
		registry.addViewController("/join/login").setViewName("login");
	}

	
}

- 각각 컨트롤러에 적용될 url 과 view name을 설정하는 클래스이다.

 

# AccountController 생성

public class AccountController {
	
	@Autowired
	AccountService accountService;

	@GetMapping("/create")
	public Account create() {
		Account account = new Account();
		account.setEmail("kjuiop@naver.com");
		account.setPassword("123123qq!");
		
		accountService.save(account);
		
		return account;
	}
}

- 컨트롤러는 말그대로 url 이 동작했을 때 메소드를 실행하는 것을 의미한다.

  원래 web.xml 에 일일이 어떤 클래스에 어떤 url 패턴이 적용될 것인지를 등록했지만

  WebMvcConfigurer 를 통해 정의했다.

 

 

- 이 컨트롤러에서는 account 에 이메일, 비밀번호를 설정한 후 account 를 리턴한다.

 

 

# AccountService 생성

@Service
public class AccountService implements UserDetailsService{

	@Autowired
	private AccountRepository accounts;
	
	@Autowired
	private PasswordEncoder passwordEncoder;
	
//	private final AccountRepository accounts;
//	
//	public AccountService(AccountRepository accounts, PasswordEncoder passwordEncoder) {
//		this.accounts = accounts;
//	}
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		
		Account account = accounts.findByEmail(username);
		
		List<GrantedAuthority> authorities = new ArrayList<>();
		authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
		
		return new User(account.getEmail(), account.getPassword(), authorities);
	}
	
	public Account save(Account account) {
		account.setPassword(passwordEncoder.encode(account.getPassword()));
		return accounts.save(account);
	}
		
}

- service 로직은 Repository에 넘기기 전에 저장하려는 정보를 가공하는 곳이다.

  예를 들어 위에서는 계정의 비밀번호를 인코딩 하기 위해 Repository에 저장하기 전에

  account 객체를 Service 클래스의 save(account account) 메소드에 넘겼다.

  그리고, repository의 accounts.save(account) 를 통해 인코딩한 account 객체를 다시 리턴한다.

 

- 위의 loadUserByUsername(String username) 메소드는 UserDetailService 인터페이스를

  implements 할 때 오버라이드 한 메소드이다.

  스프링 시큐러티에서는 로그인에 관한 클래스를 User 란 이름으로 미리 정의해놓았는데,

  로그인 할 때 쓸 수 있는 다양한 메소드를 미리 정의한 것이다.

 

  위의 코드는 UserDetails 부분에서 필요한 부분만을 가져와 리턴한 코드이다.

 

# AccountService 의 Userdetails 메소드

@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		
		Account account = accounts.findByEmail(username);
		
		UserDetails userDetails = new UserDetails() {

			@Override
			public Collection<? extends GrantedAuthority> getAuthorities() {
				List<GrantedAuthority> authorities = new ArrayList<>();
				authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
				return authorities;
			}

			@Override
			public String getPassword() {
				return account.getPassword();
			}

			@Override
			public String getUsername() {
				// TODO Auto-generated method stub
				return account.getEmail();
			}

			@Override
			public boolean isAccountNonExpired() {
				return true;
			}

			@Override
			public boolean isAccountNonLocked() {
				// TODO Auto-generated method stub
				return true;
			}

			@Override
			public boolean isCredentialsNonExpired() {
				// TODO Auto-generated method stub
				return true;
			}

			@Override
			public boolean isEnabled() {
				// TODO Auto-generated method stub
				return true;
			}
			
		};
		
		return userDetails;
		
		
	}

 

- 이 메소드 중 사용하고자 하는 메소드를 선택 및 사용하면 된다.

 

 

# AccountRepository 생성

@Repository
public class AccountRepository {

	private Map<String, Account> accounts = new HashMap<>();
	private Random random = new Random();
	
	public Account save(Account account) {
		account.setId(random.nextInt());
		accounts.put(account.getEmail(), account);
		
		return account;
	}
	
	public Account findByEmail(String username) {
		return accounts.get(username);
	}
}

 

- Repository는 DB에 입력하는 것을 말한다.

 

 

# WebSecurityConfig

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
			.antMatchers("/", "/home","/create").permitAll()
			.antMatchers("/admin/**").hasRole("ROLE")
			.anyRequest().authenticated()
			.and()
		.formLogin()
			.loginPage("/login")  // 로그인 페이지를 요청하는 url 이 뭐냐
			.permitAll()
			.and()
		.logout()
			.permitAll();
	}

	@Override
	protected UserDetailsService userDetailsService() {
		UserDetails user = User.withDefaultPasswordEncoder()
				.username("user")
				.password("")
				.roles("USER")
				.build();
		
		return new InMemoryUserDetailsManager(user);
	}
	
	

	
	@Bean
	public PasswordEncoder passwordEncoder() {
		return PasswordEncoderFactories.createDelegatingPasswordEncoder();
	}

}

 

- 이 부분이 시큐러티 로그인에 관한 부분이다. 

  먼저 url로 /, /home ,  /create 리소스가 들어왔을 때, 모든 사용자에게 권한을 허용하는

  permitAll() 메소드가 사용되고, /admin 같은 경우는 ADMIN 이라는 ROLE을 가진 사람만 

  들어오게 권한을 부여한다.

 

- 이 권한을 갖지 못한 사람들은 .formLogin()에 의해 넘어가는데, 이때

  .loginPage() 는 어떤 url 로 로그인 요청을 받을 것이냐에 대한 것이다.

 

 

# 로그인 할 때 필드 선택

 

- 일단 내가 궁금했던 부분은 이 부분이다. User 클래스 안의 username을 

 

- 서비스 부분의 return 타입의 User 객체에 account.getEmail() 을 넣으면 로그인 할때

  계정의 이메일을 username 으로 사용한다는 부분이다.

 

예제를 보고 정리한 것이지만 실제 내 프로젝트는 구성이 조금 달라서 연구해보고 해결해야겠다.

 

   

 

 

Comments