์คํ๋ง ์ํ๋ฆฌํฐ๋ก ํ์๊ฐ์ , ๋ก๊ทธ์ธ ๊ตฌํํ๊ธฐ
์ง๋ ๋ฒ์ ์ด์ด ์ด๋ฒ์ ํ์๊ฐ์ ๊ณผ ๋ก๊ทธ์ธ์ด๋ค.
ํ์๊ฐ์
ํ์๊ฐ์ ์ ์ฌ์ฉ์ ๊ธฐ๋ฅ ํ๋ฆ์ ๋ค์๊ณผ ๊ฐ๋ค.
๊ถํ์ด ์๋ ์ฌ์ฉ์๊ฐ ์์ฒญ์ ํ ๊ฒฝ์ฐ ๋ก๊ทธ์ธ ํ์ด์ง๋ฅผ ๋์์ผ ํ๋ค. ์ด๋ ๋ก๊ทธ์ธ ํ์ด์ง์ ํ์๊ฐ์ ํ์ด์ง๋ก ์ด๋ํ ์ ์๋ ๋งํฌ๋ฅผ ๊ฑธ์ด๋๋ค. ์ด๋ฅผ ํตํด ์ฌ์ฉ์๊ฐ ํ์๊ฐ์ ๋งํฌ๋ก ์ ์ํ์ฌ ํ์๊ฐ์ ํผ์ ์์ฑํ ๋ค ์๋ฒ์ ์ ์ถํ๋ค. ์๋ฒ๋ ํ์๊ฐ์ ๋ก์ง์ ์ฒ๋ฆฌํ ๋ค ๋ค์ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธํ์ฌ ๋ก๊ทธ์ธ ํ ์ฌ์ฉํ ๊ฒ์ ์ ๋ํ๋ค.
ํ์๊ฐ์ ํ์ด์ง ๋ง๋ค๊ธฐ
๊ฐ๋จํ๊ฒ ๋ธ๋ผ์ฐ์ ์ ๋์ธ ํ์๊ฐ์
ํ์ด์ง๋ฅผ ๊ตฌํํ๋ค. src/main/resources/templates
์๋์ joinForm.html ํ์ผ์ ์์ฑํ๋ค.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ํ์๊ฐ์
ํ์ด์ง</title>
</head>
<body>
<h1>ํ์๊ฐ์
ํ์ด์ง</h1>
<hr/>
<form action="/join" method="POST">
<input type="text" name="username" placeholder="Username"/> <br/>
<input type="password" name="password" placeholder="Password"/> <br/>
<input type="email" name="email" placeholder="Email"/> <br/>
<button>ํ์ธ</button>
</form>
</body>
</html>
์ปจํธ๋กค๋ฌ๋ก ์์ฒญ ๋ฐ๊ธฐ
์ปจํธ๋กค๋ฌ๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌํํ์ฌ ํด๋ผ์ด์ธํธ ์์ฒญ์ ๋ฐ๋๋ค.
/**
* ์ปจํธ๋กค๋ฌ
*/
@Controller
public class IndexController {
@Autowired
private UserRepository userRepository;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
// ํ์ ๊ฐ์
ํผ ํ์ด์ง ๋ฐํ
@GetMapping("/joinForm")
public String joinForm() {
return "joinForm";
}
// ํ์ ๊ฐ์
ํผ ํ์ด์ง์์ ํ์ธ ๋ฒํผ์ ๋๋ฌ ์ค์ ๋ฐ์ดํฐ๊ฐ ์ ์ฅ๋๋ ๋ก์ง ์ฒ๋ฆฌ
@PostMapping("/join")
public String join(User user) {
System.out.println(user);
user.setRole("ROLE_USER");
String rawPassword = user.getPassword();
String encPassword = bCryptPasswordEncoder.encode(rawPassword);
user.setPassword(encPassword);
userRepository.save(user);
return "redirect:/loginForm";
}
}
/joinForm
์ผ๋ก ํ์๊ฐ์ ํ์ด์ง๋ฅผ ์์ฒญํ๋ค. ์ปจํธ๋กค๋ฌ๋ ์์ ๋ง๋ค์ด๋ joinForm.html ํ์ผ์ ๋ฐํํ๋ค.- ์ฌ์ฉ์๊ฐ
/joinForm
์์ ๊ฐ์ ์ ๋ ฅํ๊ณ ์ ์ถํ๋ฉด ์ปจํธ๋กค๋ฌ์/join
์ผ๋ก ์์ฒญ์ด ๋ค์ด์จ๋ค. ๊ณ ๋ง์ด JPA ๋๋ถ์ User ํ์ ์ ๊ฐ์ฒด๋ก ๊ฐ์ ๋ฐ๋๋ค.- ๋น๋ฐ๋ฒํธ๋ฅผ ์ธ์ฝ๋ฉํ์ฌ ์ ์ฅํ๊ธฐ ์ํด
BCryptPasswordEncoder
๊ฐ์ฒด์ encode() ๋ฉ์๋๋ฅผ ์ด์ฉํ๋ค.- DB์ ์ ์ฅํ๋ ๋ฐ์ดํฐ ์์ธ์ค ๋ก์ง์ ์ฒ๋ฆฌํ๊ธฐ ์ํด JPA
UserRepository
๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ save() ํ๋ค.- ์ปจํธ๋กค๋ฌ์์ ํ์๊ฐ์ ์ ์ฐจ๊ฐ ๋๋๋ฉด ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธํ์ฌ ๋ก๊ทธ์ธ ํ ์ฌ์ฉ์ ์ ๋ํ๋ค.
SecurityConfig์ ์ธ์ฝ๋ฉ์ ์ํ ์์กด์ฑ ์ถ๊ฐ
์์ ๊ฐ์ด ์ปจํธ๋กค๋ฌ์์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ธ์ฝ๋ฉ ํ๊ธฐ์ํด BCryptPasswordEncoder
ํ์
์ ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ค.
SecurityConfig ํด๋์ค์ ์ธ์ฝ๋ฉ์ ์ํ ์์กด์ฑ์ ์ถ๊ฐํ๋ค.
/**
* ์ํ๋ฆฌํฐ ์ค์
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
// ํด๋น ๋ฉ์๋๋ก ๋ฆฌํด๋๋ ์ค๋ธ์ ํธ๋ฅผ IoC๋ก ๋ฑ๋ก
@Bean
public BCryptPasswordEncoder encodePwd() {
return new BCryptPasswordEncoder();
}
...
}
๋น๋ฐ๋ฒํธ ์ํธํ ์ฑ๊ณต~
๋ก๊ทธ์ธ
๋๋ง์ ๋ก๊ทธ์ธ! ์ฌ์ฉ์ ๊ธฐ๋ฅ ํ๋ฆ์ ๋ค์๊ณผ ๊ฐ๋ค.
ํ์๊ฐ์ ํ ๋ฆฌ๋ค์ด๋ ํธ๋์ด ๋ก๊ทธ์ธ ํ์ด์ง๋ก ์ด๋ํ๊ฑฐ๋ ํน์ ์ฒ์๋ถํฐ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ์ ์ํ ๊ฒฝ์ฐ, ๋์๊ฐ ๊ถํ์ด ์๋ ์ฌ์ฉ์๊ฐ ๊ถํ์ด ํ์ํ ํ์ด์ง๋ฅผ ์์ฒญํ ๊ฒฝ์ฐ ๋ก๊ทธ์ธ ํ์ด์ง๊ฐ ๋์์ง๋ค. ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ ํผ์ ์์ฑํ ๋ค ์ ์ถํ๋ฉด ์๋ฒ๋ ๋ก๊ทธ์ธ ๊ฒ์ฆ ๋ก์ง์ ์ฒ๋ฆฌํ ๋ค ์ธ๋ฑ์ค ํ์ด์ง๋ก ์ด๋ํ๊ฑฐ๋ ์๋๋ฉด ์ฌ์ฉ์๊ฐ ์๋ ์ ๊ทผํ๊ณ ์ ํ๋ ํ์ด์ง๋ก ์ด๋ํ๋ค.
๋ก๊ทธ์ธ ํ์ด์ง ๋ง๋ค๊ธฐ
๊ฐ๋จํ๊ฒ ๋ก๊ทธ์ธ ํ์ด์ง๋ฅผ ๊ตฌํํ๋ค. src/main/resources/templates
์๋์ loginForm.html ํ์ผ์ ์์ฑํ๋ค.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>๋ก๊ทธ์ธ ํ์ด์ง</title>
</head>
<body>
<h1>๋ก๊ธด ํ์ด์ง</h1>
<hr/>
<form action="/login" method="POST">
<input type="text" name="username" placeholder="Username"/> <br/>
<input type="password" name="password" placeholder="Password"/> <br/>
<button>ํ์ธ</button>
</form>
<a href="/joinForm">ํ์๊ฐ์
ํ๋ฌ ๊ฐ๊ธฐ</a>
</body>
</html>
์ปจํธ๋กค๋ฌ๋ก ์์ฒญ ๋ฐ๊ธฐ
์ปจํธ๋กค๋ฌ๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌํํ์ฌ ํด๋ผ์ด์ธํธ ์์ฒญ์ ๋ฐ๋๋ค.
/**
* ์ปจํธ๋กค๋ฌ
*/
@Controller
public class IndexController {
@GetMapping("/loginForm")
public String loginForm() {
return "loginForm";
}
}
- ๋ก๊ทธ์ธ ์ฒ๋ฆฌ ๋ก์ง์ SecurityConfig ํด๋์ค์ configure() ๋ฉ์๋ ์์์ ์ค์ ํ ์ ์๋ค.
- ๋ฐ๋ผ์ ์ปจํธ๋กค๋ฌ์์
/login
์์ฒญ์ ๋ํ @PostMapping์ ํด์ฃผ์ง ์์๋ ๋๋ค!(๋จ, spring security ์ด๊ธฐ ๊ฐ์ ์ํ๋ฉด ์ปจํธ๋กค๋ฌ์/login
์์ฒญ์ ์์ฒด ์ ์๋ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ์ฐ๊ฒฐ๋๋ค)
์ํ๋ฆฌํฐ ์ค์ ์์ ๋ก๊ทธ์ธ ์์ฒญ ์ฒ๋ฆฌ
SecurityConfig ํด๋์ค์์ ์ฒด์ด๋์ ํตํด ๋ก๊ทธ์ธ ํผ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ค.
/**
* ์ํ๋ฆฌํฐ ์ค์
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/user/**").authenticated() // ์ธ์ฆ๋ง ๋๋ฉด ๋ค์ด๊ฐ ์ ์๋ ์ฃผ์
.antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/loginForm")
.loginProcessingUrl("/login") // /login ์ฃผ์๊ฐ ํธ์ถ๋๋ฉด ์ํ๋ฆฌํฐ๊ฐ ๋์์ฑ ๋์ ๋ก๊ทธ์ธ ์งํ -> Controller์ '/login' ๋ง๋ค์ง ์์๋ ๋จ
.defaultSuccessUrl("/");
}
}
.formLogin()
: ๋ก๊ทธ์ธ ํผ ๋ฐ์ดํฐ ์ฒ๋ฆฌ๋ฅผ ์ํด ๊ธฐ๋ณธ์ ์ผ๋ก/login
์์ฒญ์ ๋ง๋ค์ด์ค.loginPage("/loginForm")
: ์ปจํธ๋กค๋ฌ์์ ์ ์ํ ๋ก๊ทธ์ธ ํผ ์์ฒญ ํ์ด์ง ์ค์ .loginProcessingUrl("/login")
: ๋ก๊ทธ์ธ ํผ ๋ฐ์ดํฐ๋ฅผ/login
์์ฒญ์ผ๋ก ๋๊น.defaultSuccessUrl("/");
: ๋ก๊ทธ์ธ ์ฑ๊ณต ์ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธ๋ ๊ฒฝ๋ก
๋ก๊ทธ์ธ ๊ฒ์ฆ ๋ก์ง ๊ตฌํํ๊ธฐ
๋ก๊ทธ์ธ ํผ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ฆํ ์ฐจ๋ก๋ค.
Spring Security๋ /login
์ฃผ์ ์์ฒญ์ด ๋ค์ด์ค๋ฉด ์ด๋ฅผ ๋์์ฑ ๋ก๊ทธ์ธ ๋ก์ง์ ์ฒ๋ฆฌํ๋ค. ๋ก๊ทธ์ธ์ด ์๋ฃ๋๋ฉด Security Session์ ์์ฑํ๋ค.(Security ContextHolder ๋ผ๋ ํค๊ฐ์ ์ธ์
์ ๋ณด๋ฅผ ์ ์ฅ)
์ด๋ Spring Security๋ Security Session์ Authentication
๋ผ๋ ํน์ ํ ๊ฐ์ฒด ํ์
์ผ๋ก ์ธ์ฆ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ค.
๋์์ Authentication
๊ฐ์ฒด๋ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ด์ ์ ์์ด์ผ ํ๊ณ ๋ง์ฐฌ๊ฐ์ง๋ก ์ด๋ฅผ ํํํ๋ ํน์ ๊ฐ์ฒด ํ์
์ด ์๋๋ฐ ์ด๋ฅผ UserDetails
๋ผ๊ณ ํ๋ค.
Security Session -> Authentication ํ์ ๊ฐ์ฒด ๊ฐ์ง -> UserDetails ํ์ ๊ฐ์ฒด ๊ฐ์ง
๋ฐ๋ผ์ auth
ํจํค์ง ์์ PrincipalDetails
ํด๋์ค๋ฅผ ์ ์ํ๋ค ์ด ํด๋์ค๋ UserDetails
์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋๋ก ํ๋ค.
/**
* ๋ก๊ทธ์ธ ๋ฐํ ์ค์
*/
public class PrincipalDetails implements UserDetails{
private User user;
// ์์ฑ์
public PrincipalDetails(User user) {
this.user = user;
}
// User์ ๊ถํ์ ๋ฆฌํด
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collect = new ArrayList<GrantedAuthority>();
collect.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole();
}
});
return collect;
}
// User์ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ฆฌํด
@Override
public String getPassword() {
return user.getPassword();
}
// User์ ์ ์ ๋ค์์ ๋ฆฌํด
@Override
public String getUsername() {
return user.getUsername();
}
// ๊ณ์ ์ ๋ณด ๋ง๋ฃ ์ฌ๋ถ
@Override
public boolean isAccountNonExpired() {
return true;
}
// ๊ณ์ ์ ๊ธ ์ฌ๋ถ
@Override
public boolean isAccountNonLocked() {
return true;
}
// ๊ณ์ ๋น๋ฐ๋ฒํธ ๋ฑ ๋ง๋ฃ ์ฌ๋ถ
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// ํด๋ฉด ์ฌ์ฉ์ ์ฌ๋ถ
@Override
public boolean isEnabled() {
// ๋ง์ฝ 1๋
๊ฐ ๋ก๊ธด ์ํด์ ํด๋ฉด ๊ณ์ ์ผ๋ก ๋ณํ๋ ๋ ์ด ๋ฉ์๋ ์์์ ๋นํ์ฑํ ์ํฌ ์ ์์
// User ์ค๋ธ์ ํธ ํ์
์ loginDate ๋ผ๋ ํ๋ ์ถ๊ฐํด์ ๋ก๊ธดํ ๋๋ง๋ค ๋ ์ง ์ ์ฅํด๋
// ํ์ฌ๋ ์ง - ๋ก๊ธด๋ ์ง > 1๋
-> return false ๋ก ๋ถ๊ธฐํด์ฃผ๋ฉด ๋จ
return true;
}
}
์์ ๊ฐ์ด ๋ก๊ทธ์ธ ์ ๋ฐํํ ํด๋์ค๋ฅผ ์ ์ํ๋ค๋ฉด ์ค์ ๋ก ๋ก๊ทธ์ธ ์๋น์ค ๋ก์ง์ ๊ตฌํํ ์ฐจ๋ก๋ค.
auth
ํจํค์ง ์์ PrincipalDetailsService
ํด๋์ค๋ฅผ ์ ์ํ๋ค ์ด ํด๋์ค๋ UserDetailsService
์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋๋ก ํ๋ค. ๋ํ @Service
์ด๋
ธํ
์ด์
์ ์์ง ๋ง์.
/login
์์ฒญ ์ ์๋์ผ๋ก UserDetailService
ํ์
์ผ๋ก IoC ๋์ด์๋ loadUserByUsername() ๋ฉ์๋๊ฐ ์คํ๋๋ค.
/**
* ๋ก๊ทธ์ธ ๋ก์ง ์๋น์ค
*/
@Service
public class PrincipalDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
// (Security Session ๋ด๋ถ์ (Authentication ๋ด๋ถ์ (UserDatails)))
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User userEntity = userRepository.findByUsername(username);
if (userEntity != null) {
return new PrincipalDetails(userEntity);
}
return null;
}
}
- DB์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์กฐํํ๊ธฐ ์ํด JPA
UserRepository
๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ Query Method ๋ฐฉ์์ผ๋ก ์ฟผ๋ฆฌ๋ฅผ ์ฒ๋ฆฌํ๋ค.- ๋ฐํ ํ์ ์
PrincipalDetails
๊ฐ์ฒด๊ฐ ๋๋ค.
์คํ๋ง ํ๋ก์ ํธ๋ฅผ ์คํํ๋ฉด ํ์๊ฐ์ , ๋ก๊ทธ์ธ๊น์ง ์ ๋๋ค. ใ ใ