๐ฅ #8 ์ ์ด์ด์ ์งํ~!
UserApiController๋ฅผ ๋ณด๋ฉด, /auth/loginProc
๋ฅผ ๋ง๋ค์ง ์์๋ค.
์๋ํ๋ฉด ์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ ๊ฐ๋ก์ฑ๋๋ก ํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ด๋ค.
๊ฐ๋ก์ฑ๋๋ก ํ๊ธฐ ์ํด์๋ SecurityConfig ์ configure ๋ฉ์๋์์ .loginProcessingUrl("/auth/loginProc")
๋ฅผ ์ถ๊ฐํด์ผ ํ๋ค.
๋ฐ๋ผ์, ํ์ฌ SecurityConfig๋ ์๋์ฒ๋ผ ์์ฑํ๋ฉด ๋๋ค.
@Configuration // ๋น ๋ฑ๋ก (IoC๊ด๋ฆฌ)
@EnableWebSecurity // Security ํํฐ๊ฐ ๋ฑ๋ก๋จ = ์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ ์ด๋ฏธ ํ์ฑํ๋ ๋์ด์์ง๋ง, ์ค์ ์ ํด๋น ํ์ผ์์ ํ ๊ฒ์
@EnableGlobalMethodSecurity(prePostEnabled = true) // ํน์ ์ฃผ์๋ก ์ ๊ทผ์ ํ๋ฉด ๊ถํ ๋ฐ ์ธ์ฆ์ ๋ฏธ๋ฆฌ ์ฒดํฌ (์ํํ ํ์ ์ฒดํฌํ๋ ๊ฒ์ด ์๋)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 1. Bean ์ด๋
ธํ
์ด์
์ ๋ฉ์๋์ ๋ถ์ฌ์ ๊ฐ์ฒด ์์ฑ์ ์ฌ์ฉ
@Bean // IoC๊ฐ ๋๋ค.
public BCryptPasswordEncoder encodePWD() {
return new BCryptPasswordEncoder(); // ์ด ๊ฐ์ฒด๋ฅผ ์คํ๋ง์ด ๊ด๋ฆฌํ๊ฒ ๋จ. ํ์ํ ๋๋ง๋ค ๊ฐ์ ธ๊ฐ์ ์ฐ๋ฉด ๋๋ค.
}
// ํํฐ๋ง
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // csrf ํ ํฐ ๋นํ์ฑํ (ํ
์คํธ์ ๊ฑธ์ด๋๋๊ฒ ์ข์)
.authorizeRequests() // request๊ฐ ๋ค์ด์ค๋ฉด
.antMatchers("/", "/auth/**", "/js/**", "/css/**", "/image/**") // ์ฌ๊ธฐ๋ก ๋ค์ด์ค๋ฉด
.permitAll() // ๋ชจ๋ ๊ฐ๋ฅ (๋๊ตฌ๋ ๊ฐ๋ฅ)
.anyRequest() // ๊ทธ๊ฒ ์๋ ๋ค๋ฅธ ๋ชจ๋ ์์ฒญ์
.authenticated() // ์ธ์ฆ์ด ๋์ด์ผ ํ๋ค
.and()
.formLogin()
.loginPage("/auth/loginForm") // ์ธ์ฆ์ด ํ์ํ ์์ฒญ์ ์ด ๋ก๊ทธ์ธ ํผ์ผ๋ก ์จ๋ค
.loginProcessingUrl("/auth/loginProc") // ์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ ํด๋น ์ฃผ์๋ก ์์ฒญ์ด ์ค๋ ๋ก๊ทธ์ธ์ ๊ฐ๋ก์ฑ์ ๋์ ๋ก๊ทธ์ธ์ ํ๋ค.
.defaultSuccessUrl("/"); // ๋ก๊ทธ์ธ ์ฑ๊ณตํ๋ฉด "/"๋ก ๊ฐ๋ค.
// .failureUrl("/fail"); // ์คํจ์ url
}
// ์ฐธ๊ณ : .headers().frameOptions().disable() // ์์ดํ๋ ์ ์ ๊ทผ ๋ง๊ธฐ
// ์ฐธ๊ณ : .csrf().disable() // csrf ํ ํฐ ๋นํ์ฑํ (ํ
์คํธ์ ๊ฑธ์ด์ฃผ๋ ๊ฒ์ด ์ข์)
}
์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ ์ฌ์ฉ์๊ฐ ์์ฒญํ username๊ณผ password๋ฅผ ๊ฐ๋ก์ฑ์ ๋ก๊ทธ์ธ์ ํ๋๋ฐ,
UserDetails ํ์
์ User ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ผ ํ๋ค.
์๋ํ๋ฉด ๋ก๊ทธ์ธ์ ์งํํ๊ณ ์๋ฃ๊ฐ ๋๋ฉด ์คํ๋ง ์ํ๋ฆฌํฐ ์ธ์ ์ ์ ์ ์ ๋ณด๋ฅผ ๋ฑ๋กํด์ผ ํ๋๋ฐ User ๊ฐ์ฒด๋ฅผ ๋ฑ๋กํ ์ ์๊ณ , UserDetails ํ์ ๋ง ๋ฑ๋กํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
// ์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ ๋ก๊ทธ์ธ ์์ฒญ์ ๊ฐ๋ก์ฑ์ ๋ก๊ทธ์ธ์ ์งํํ๊ณ ์๋ฃ๊ฐ ๋๋ฉด,
// UserDetails ํ์
์ ์ค๋ธ์ ํธ๋ฅผ ์คํ๋ง ์ํ๋ฆฌํฐ์ ๊ณ ์ ํ ์ธ์
์ ์ฅ์์ ์ ์ฅํ๋ค.
public class PrincipalDetail implements UserDetails {
private User user; // ์ฝคํฌ์ง์
(๊ฐ์ฒด๋ฅผ ํ๊ณ ์๋ ๊ฒ)
public PrincipalDetail(User user) {
this.user = user;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
// ๊ณ์ ์ด ๋ง๋ฃ๋์ง ์์๋์ง ๋ฆฌํดํ๋ค. (true : ๋ง๋ฃ ์๋จ)
@Override
public boolean isAccountNonExpired() {
return true;
}
// ๊ณ์ ์ด ์ ๊ฒจ์๋์ง ์ ์ ๊ฒจ์๋์ง ๋ฆฌํดํ๋ค. (true : ์ ๊ธฐ์ง ์์)
@Override
public boolean isAccountNonLocked() {
return true;
}
// ๋น๋ฐ๋ฒํธ๊ฐ ๋ง๋ฃ๋์ง ์์๋์ง๋ฅผ ๋ฆฌํดํ๋ค. (true : ๋ง๋ฃ ์๋จ)
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// ๊ณ์ ์ด ํ์ฑํ(์ฌ์ฉ๊ฐ๋ฅ)์ธ์ง ๋ฆฌํดํ๋ค. (true : ํ์ฑํ)
@Override
public boolean isEnabled() {
return true;
}
// ๊ณ์ ์ด ๊ฐ๊ณ ์๋ ๊ถํ ๋ชฉ๋ก์ ๋ฆฌํดํ๋ค. (๊ถํ์ด ์ฌ๋ฌ๊ฐ ์์ ์ ์์ด์ ๋ฃจํ๋ฅผ ๋์์ผ ํ๋๋ฐ ํ์ฌ๋ ํ๋๋ง)
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collectors = new ArrayList<>(); // ArrayList๋ Collection ํ์
์ด๋ค. (์์)
// collectors.add(new GrantedAuthority() {
//
// @Override
// public String getAuthority() {
// return "ROLE_"+user.getRole(); // role ์ ๋ฐ์ ๋ ์์ "ROLE_" ๋ถ์ด๋ ๊ฒ(prefix)์ด ์คํ๋ง์ ๊ท์น, ์ฆ ROLE_USER ๊ฐ์ ์์ผ๋ก ๋ฆฌํด๋จ
// }
// });
collectors.add(()->{return "ROLE_"+ user.getRole();}); // ์ด์ฐจํผ add ์์ ๋ค์ด๊ฐ ํจ์๋ GrantedAuthority() ๋ฟ์ด๊ธฐ ๋๋ฌธ์ ๋๋ค์์ผ๋ก ๊ฐ๋ฅ
return collectors;
}
}
์ธ์ ์ PrincipalDetail๋ฅผ ์ ์ฅํ๊ฒ ๋๋๋ฐ, ๊ทธ๋ ์ฐ๋ฆฌ๊ฐ DB์ ์ ์ฅํ๋ User ๊ฐ์ฒด๋ ํฌํจ๋์ด ์์ด์ผ ํ๊ธฐ ๋๋ฌธ์ ์ปดํฌ์ง์ ํ๋ค.
ํ์
์ UserDetails ์ด์ฌ์ผ ํ๊ธฐ ๋๋ฌธ์ implements๋ฅผ ํ๋ค.
UserDetails๊ฐ ๋ค๊ณ ์๋ ์ถ์ ๋ฉ์๋๋ค์ ์ ๋ถ ์ค๋ฒ๋ผ์ด๋ฉ ํ๋ค.
// ์ปดํฌ์ง์
public class PrincipalDetail {
private User user; // ์ฝคํฌ์ง์
(๊ฐ์ฒด๋ฅผ ํ๊ณ ์๋ ๊ฒ) }
// ์์
public class PrincipalDetail extends User { }
- ์์ - 'IS-A' ๊ด๊ณ
- ์ปดํฌ์ง์
- 'HAS-A' ๊ด๊ณ
- DI - ?????
'IS-A', 'HAS-A' ๊ด๊ณ๋ ์์, ์์์ **์ ์ ์ธ ํด๋์ค** ๊ด๊ณ์์ ์ค๋ช
ํ๋ ๋ฐฉ์์ด๋ค.
**DI**๋ 'HAS-A' ๊ด๊ณ์์ **๋์ ์ผ๋ก ๋์ ๊ฐ์ฒด๋ฅผ ๋ณ๊ฒฝํ๋ ๋ฐฉ๋ฒ**์ ๋ํ ๊ฒ์ด๋ค.
์ฆ, 'HAS-A' ๊ด๊ณ๋ก ๋ณด๋ฉด ์ธํฐํ์ด์ค(์ถ์ ํด๋์ค)์ ์์กดํ๋๋ก ๊ด๊ณ๋ฅผ ๋งบ๋ ๊ฒ๊น์ง ์ธ๋ฐ, DI๋ ์ฌ๊ธฐ์ ์ถ๊ฐํด์ ๋์ ์ผ๋ก ์ธํฐํ์ด์ค ๊ตฌํ ๊ฐ์ฒด๋ฅผ ๋ณ๊ฒฝํ๋ ๋ฐฉ๋ฒ๊น์ง๋ฅผ ์ด์ผ๊ธฐํ๋ค.
@Service // Bean ๋ฑ๋ก
public class PrincipalDetailService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
// ์คํ๋ง์ด ๋ก๊ทธ์ธ ์์ฒญ์ ๊ฐ๋ก์ฑ ๋, username๊ณผ password ๋ณ์ 2๊ฐ๋ฅผ ๊ฐ๋ก์ฑ
// password ์ฒ๋ฆฌ๋ ์คํ๋ง์ด ์์์ ํจ
// ๋๋ username์ด ํด๋น DB์ ์๋์ง๋ง ํ์ธํด์ ๋ฆฌํดํด์ฃผ๋ฉด ๋จ
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User principal = userRepository.findByUsername(username) // Optional ํ์
์ด๊ธฐ ๋๋ฌธ์ .orElseThrow
.orElseThrow(()->{
return new UsernameNotFoundException("ํด๋น ์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค." + username);
});
return new PrincipalDetail(principal); // ์ด๋ ์ํ๋ฆฌํฐ ์ธ์
์ ์ ์ ์ ๋ณด ์ ์ฅ๋จ
}
}
์์๋ฐ์ ํ์
์ UserDetailService ๋ก ํ๋ค.
์ค๋ฒ๋ผ์ด๋ฉํ loadUserByUsername ํจ์์์๋ ๊ฐ๋ก์ฑ username ๋ณ์๊ฐ DB์ ์๋์ง ํ์ธํ๋ค.
๊ทธ๋ผ ์ด์ ๋ก๊ทธ์ธ์ด ์์ฒญ๋ ๋ loadUserByUsername ํจ์๊ฐ ์๋์ผ๋ก ์คํ๋๋ฉด์ username์ ํด๋นํ๋ User๋ฅผ ์ฐพ๊ณ PrincipalDetail ํ์
(์ฆ, UserDetails ํ์
)์ผ๋ก ๋ฆฌํดํ๋ค.
-> UserDetails ํ์
์ด ๋์์ผ๋ฏ๋ก ์ํ๋ฆฌํฐ์ ์ธ์
์ ์ ์ ์ ๋ณด๊ฐ ์ ์ฅ์ด ๋จ.
์ด ํจ์๋ฅผ ์ค๋ฒ๋ผ์ด๋ฉํ์ฌ ๊ตฌํํด์ผํ๋ค. ๊ทธ๋์ผ ์ฐ๋ฆฌ์ ์ง์ง(?) User ์ ๋ณด๋ฅผ ๋ด์์ ๋ฆฌํดํ ์ ์๋ค.
public interface UserRepository extends JpaRepository<User, Integer> {
// SELECT * FROM user WHERE username = 1?;
Optional<User> findByUsername(String username);
}
findByUsername ์์ด์ UserRepository์์ ๋ง๋ค์๋ค. (๋ค์ด๋ฐ ๊ท์น)
// ์ํ๋ฆฌํฐ๊ฐ ๋์ ๋ก๊ทธ์ธ ํจ -> password ๊ฐ๋ก์ฑ
// ๊ฐ๋ก์ฑ password๊ฐ ํ์๊ฐ์
๋ ๋ ๋ฌด์์ผ๋ก ํด์ฌ๊ฐ ๋์๋์ง ์์์ผํจ -> ๊ทธ๋์ผ ๊ฐ์ ํด์ฌ๋ก ์ํธํ ํ๊ณ DB์ ์๋ ํด์ฌ์ ๋น๊ตํ์ฌ ๋ก๊ทธ์ธ
// ์ฆ, ํจ์ค์๋ ๋น๊ตํ๋ ๋ฉ์๋
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(principalDetailService).passwordEncoder(encodePWD()); // passwordEncoder ํ๋ ์ ๊ฐ encodePWD ์.
}
์ฌ๊ธฐ์ ๋ก๊ทธ์ธ ํ password๋ฅผ encode ํด์ ๋น๊ต๋ฅผ ํ๋ค.
@Configuration // ๋น ๋ฑ๋ก (IoC๊ด๋ฆฌ)
@EnableWebSecurity // Security ํํฐ๊ฐ ๋ฑ๋ก๋จ = ์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ ์ด๋ฏธ ํ์ฑํ๋ ๋์ด์์ง๋ง, ์ค์ ์ ํด๋น ํ์ผ์์ ํ ๊ฒ์
@EnableGlobalMethodSecurity(prePostEnabled = true) // ํน์ ์ฃผ์๋ก ์ ๊ทผ์ ํ๋ฉด ๊ถํ ๋ฐ ์ธ์ฆ์ ๋ฏธ๋ฆฌ ์ฒดํฌ (์ํํ ํ์ ์ฒดํฌํ๋ ๊ฒ์ด ์๋)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PrincipalDetailService principalDetailService;
// 1. Bean ์ด๋
ธํ
์ด์
์ ๋ฉ์๋์ ๋ถ์ฌ์ ๊ฐ์ฒด ์์ฑ์ ์ฌ์ฉ
@Bean // IoC๊ฐ ๋๋ค.
public BCryptPasswordEncoder encodePWD() {
return new BCryptPasswordEncoder(); // ์ด ๊ฐ์ฒด๋ฅผ ์คํ๋ง์ด ๊ด๋ฆฌํ๊ฒ ๋จ. ํ์ํ ๋๋ง๋ค ๊ฐ์ ธ๊ฐ์ ์ฐ๋ฉด ๋๋ค.
}
// 2. ์ํ๋ฆฌํฐ๊ฐ ๋ก๊ทธ์ธํ ๋ ์ด๋ค ์ํธํ๋ก ์ธ์ฝ๋ฉํด์ ๋น๋ฒ์ ๋น๊ตํ ์ง ์๋ ค์ค์ผ ํจ.
// ์ํ๋ฆฌํฐ๊ฐ ๋์ ๋ก๊ทธ์ธ ํจ -> password ๊ฐ๋ก์ฑ
// ๊ฐ๋ก์ฑ password๊ฐ ํ์๊ฐ์
๋ ๋ ๋ฌด์์ผ๋ก ํด์ฌ๊ฐ ๋์๋์ง ์์์ผํจ -> ๊ทธ๋์ผ ๊ฐ์ ํด์ฌ๋ก ์ํธํ ํ๊ณ DB์ ์๋ ํด์ฌ์ ๋น๊ตํ์ฌ ๋ก๊ทธ์ธ
// ์ฆ, ํจ์ค์๋ ๋น๊ตํ๋ ๋ฉ์๋
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(principalDetailService).passwordEncoder(encodePWD()); // passwordEncoder ํ๋ ์ ๊ฐ encodePWD ์.
}
// 3. ํํฐ๋ง
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // csrf ํ ํฐ ๋นํ์ฑํ (ํ
์คํธ์ ๊ฑธ์ด๋๋๊ฒ ์ข์)
.authorizeRequests() // request๊ฐ ๋ค์ด์ค๋ฉด
.antMatchers("/", "/auth/**", "/js/**", "/css/**", "/image/**") // ์ฌ๊ธฐ๋ก ๋ค์ด์ค๋ฉด
.permitAll() // ๋ชจ๋ ๊ฐ๋ฅ (๋๊ตฌ๋ ๊ฐ๋ฅ)
.anyRequest() // ๊ทธ๊ฒ ์๋ ๋ค๋ฅธ ๋ชจ๋ ์์ฒญ์
.authenticated() // ์ธ์ฆ์ด ๋์ด์ผ ํ๋ค
.and()
.formLogin()
.loginPage("/auth/loginForm") // ์ธ์ฆ์ด ํ์ํ ์์ฒญ์ ์ด ๋ก๊ทธ์ธ ํผ์ผ๋ก ์จ๋ค
.loginProcessingUrl("/auth/loginProc") // ์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ ํด๋น ์ฃผ์๋ก ์์ฒญ์ด ์ค๋ ๋ก๊ทธ์ธ์ ๊ฐ๋ก์ฑ์ ๋์ ๋ก๊ทธ์ธ์ ํ๋ค.
.defaultSuccessUrl("/"); // ๋ก๊ทธ์ธ ์ฑ๊ณตํ๋ฉด "/"๋ก ๊ฐ๋ค.
// .failureUrl("/fail"); // ์คํจ์ url
}
// ์ฐธ๊ณ : .headers().frameOptions().disable() // ์์ดํ๋ ์ ์ ๊ทผ ๋ง๊ธฐ
// ์ฐธ๊ณ : .csrf().disable() // csrf ํ ํฐ ๋นํ์ฑํ (ํ
์คํธ์ ๊ฑธ์ด์ฃผ๋ ๊ฒ์ด ์ข์)
}
์์๊น์ง ํ๋ฉด, ์คํ๋ง ์ํ๋ฆฌํฐ ์ธ์
์ ์ ์ ์ ๋ณด๊ฐ ์ ์ฅ๋๋ค.
์ฐ๋ฆฌ๊ฐ UserDetail ํ์
์ผ๋ก PrincipalDetail๋ฅผ ๋ง๋ค์์ผ๋ PrincipalDetail๋ก ๊ฐ์ธ์ ์ ์ฅ๋ ๊ฒ์ด๋ค.
@GetMapping({"", "/"})
public String index(@AuthenticationPrincipal PrincipalDetail principal) { // ์ปจํธ๋กค๋ฌ์์ ๋ก๊ทธ์ธ ๋ ์ธ์
์ ์ฐพ๋ ๋ฐฉ์ : @AuthenticationPrincipal PrincipalDetail principal
// /WEB-INF/views/index.jsp
System.out.println("๋ก๊ทธ์ธ ์ฌ์ฉ์ ์์ด๋:"+principal.getUsername());
return "index";
}
์์ ๋งค๊ฐ๋ณ์๋ก PrincipalDetail principal
๋ฅผ ๋ฃ๋ ๊ฒ์ ๋์ง๋ง,
์์กด์ฑ ์ฃผ์
๋ฐฉ์์ผ๋ก @Autowired private PrincipalDetail principal;
๋ฅผ ์ฃผ์
ํ๋ ๊ฒ์ ๋ถ๊ฐ๋ฅํ๋ค.