: spring ๊ธฐ๋ฐ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ณด์(์ธ์ฆ๊ณผ ๊ถํ, ์ธ๊ฐ...)์ ๋ด๋นํ๋ ์คํ๋ง ํ์ ํ๋ ์์ํฌ
spring security์์ ์ ๊ณตํด์ฃผ๋ ๋ณด์ ์๋ฃจ์ ์ ์ฌ์ฉํ๋ฉด ๊ฐ๋ฐ์๊ฐ ๊ด๋ จ ๋ณด์ ๋ก์ง์ ๊ตฌํํ ํ์๊ฐ ์์ด์ง๋ค.
: ํด๋น ์ฌ์ฉ์๊ฐ ๋ณธ์ธ์ด ๋ง๋์ง ํ์ธํ๋ ์ ์ฐจ
: ํน์ ๋ถ๋ถ์ ์ ๊ทผํ ์ ์๋์ง์ ๋ํ ์ฌ๋ถ๋ฅผ ํ์ธํ๋ ์ ์ฐจ
: ์ธ์ฆ๋ ์ฌ์ฉ์๊ฐ ์์ฒญํ ์์์ ์ ๊ทผ ๊ฐ๋ฅํ์ง ๊ฒฐ์ ํ๋ ์ ์ฐจ
Spring Security๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ธ์ฆ ์ ์ฐจ๋ฅผ ๊ฑฐ์น ๋ค ์ธ๊ฐ ์ ์ฐจ๋ฅผ ์งํํ๋ฉฐ, ์ธ๊ฐ ๊ณผ์ ์์ ํด๋น ๋ฆฌ์์ค์ ์ ๊ทผ ๊ถํ์ด ์๋์ง ํ์ธํ๋ค.
Spring Security์์๋ ์ด๋ฌํ ์ธ์ฆ๊ณผ ์ธ๊ฐ๋ฅผ ์ํด Principal ์ ์์ด๋๋ก, Credential ์ ๋น๋ฐ๋ฒํธ๋ก ์ฌ์ฉํ๋ Credential ๊ธฐ๋ฐ์ ์ธ์ฆ ๋ฐฉ์์ ์ฌ์ฉํ๋ค.
principal: ์ ๊ทผ ์ฃผ์ฒด. ๋ณดํธ๋ฐ๋ ๋ฆฌ์์ค์ ์ ๊ทผํ๋ ๋์
credential: ๋ฆฌ์์ค์ ์ ๊ทผํ๋ ๋์์ ๋น๋ฐ๋ฒํธ
: ๋ณด์ ์ฃผ์ฒด์ ์ธ๋ถ ์ ๋ณด๋ฅผ ํฌํจํ์ฌ ์์ฉ ํ๋ก๊ทธ๋จ์ ํ์ฌ ๋ณด์ ์ปจํ ์คํธ์ ๋ํ ์ธ๋ถ ์ ๋ณด๊ฐ ์ ์ฅ๋จ.
SecurityContextHolder๋ ๊ธฐ๋ณธ์ ์ผ๋ก SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
๋ฐฉ๋ฒ๊ณผ SecurityContextHolder.MODE_THREADLOCAL
๋ฐฉ๋ฒ์ ์ ๊ณต
: Authentication
(์ธ์ฆ)๋ฅผ ๋ณด๊ดํ๋ ์ญํ
SecurityContext๋ฅผ ํตํด Authentication
๊ฐ์ฒด๋ฅผ ๊บผ๋ด์ฌ ์ ์์
: ํ์ฌ ์ ๊ทผํ๋ ์ฃผ์ฒด์ ์ ๋ณด์ ๊ถํ์ ๋ด๋ ์ธํฐํ์ด์ค
Authentication ๊ฐ์ฒด๋ SecurityContext
์ ์ ์ฅ๋๋ฉฐ, SecurityContextHolder
๋ฅผ ํตํด SecurityContext
์ ์ ๊ทผํ๊ณ , SecurityContext
๋ฅผ ํตํด Authentication
์ ์ ๊ทผํ ์ ์์
: Authentication
(์ธ์ฆ)์ ๊ตฌํํ AbstractAuthenticationToken
์ ํ์ ํด๋์ค
user์ ID๊ฐ principal
(์ ๊ทผ ์ฃผ์ฒด) ์ญํ ์ ํ๊ณ ,
pw๊ฐ Credential
(๋ฆฌ์์ค์ ์ ๊ทผํ๋ ๋์์ ๋น๋ฐ๋ฒํธ) ์ญํ ์ ํจ
UsernamePasswordAuthenticationToken์ ์ฒซ ๋ฒ์งธ ์์ฑ์๋ ์ธ์ฆ ์ ์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ , ๋ ๋ฒ์งธ ์์ฑ์๋ ์ธ์ฆ์ด ์๋ฃ๋ ๊ฐ์ฒด๋ฅผ ์์ฑ
: ์ค์ ์ธ์ฆ์ ๋ํ ๋ถ๋ถ์ ์ฒ๋ฆฌํจ
์ธ์ฆ ์ Authentication
(์ธ์ฆ) ๊ฐ์ฒด๋ฅผ ๋ฐ์ ์ธ์ฆ์ด ์๋ฃ๋ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ ์ญํ
AuthenticationProvider
์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํด ์ปค์คํ
ํ AuthenticationProvider
๋ฅผ ์์ฑํด AuthenticationManager
์ ๋ฑ๋กํ๋ฉด ๋จ
: ์ธ์ฆ์ ๋ํ ๋ถ๋ถ์ SpringSecurity์ AuthenticationManager
๋ฅผ ํตํด ์ฒ๋ฆฌ๋๋๋ฐ,
์ค์ง์ ์ผ๋ก AuthenticationManager
์ ๋ฑ๋ก๋ AuthenticationProvider
์ ์ํด ์ฒ๋ฆฌ๋จ
์ธ์ฆ์ ์ฑ๊ณตํ๋ฉด ๋ ๋ฒ์งธ ์์ฑ์๋ฅผ ์ด์ฉํด ์ธ์ฆ์ด ์ฑ๊ณตํ ๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ SecurityContext
์ ์ ์ฅ.
๊ทธ๋ฆฌ๊ณ ์ธ์ฆ ์ํ๋ฅผ ์ ์งํ๊ธฐ ์ํด ์ธ์
์ ๋ณด๊ดํ๋ฉฐ, ์ธ์ฆ์ด ์คํจํ ๊ฒฝ์ฐ์๋ AuthenticationException๋ฅผ ๋ฐ์
: ์ธ์ฆ์ ์ฑ๊ณตํด ์์ฑ๋ UserDetails ๊ฐ์ฒด๋ Authentication
(์ธ์ฆ) ๊ฐ์ฒด๋ฅผ ๊ตฌํํ UsernamePasswordAuthenticationToken
์ ์์ฑํ๊ธฐ ์ํด ์ฌ์ฉ๋จ
: ์ด ์ธํฐํ์ด์ค๋ UserDetails
๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ ๋จ ํ๋์ ๋ฉ์๋๋ฅผ ๊ฐ์ง๊ณ ์๋๋ฐ,
์ผ๋ฐ์ ์ผ๋ก ์ด๋ฅผ ๊ตฌํํ ํด๋์ค ๋ด๋ถ์ UserRepository
๋ฅผ ์ฃผ์
๋ฐ์ DB์ ์ฐ๊ฒฐํด ์ฒ๋ฆฌํจ
: AuthenticationManagerBuilder.userDetailsService().passwordEncoder()
๋ฅผ ํตํด ํจ์ค์๋ ์ํธํ์ ์ฌ์ฉ๋ PasswordEncoder ๊ตฌํ์ฒด๋ฅผ ์ง์ ํ ์ ์์
: ํ์ฌ ์ฌ์ฉ์(principal
)๊ฐ ๊ฐ์ง ๊ถํ
ํน์ ์์์ ๋ํ ๊ถํ์ด ์๋์ง ๊ฒ์ฌํด ์ ๊ทผ ํ์ฉ ์ฌ๋ถ๋ฅผ ๊ฒฐ์
* Session Fixation
: ๊ณต๊ฒฉ์๊ฐ ์ฌ์ดํธ์ ์ ์ํด ์ธ์ ์์ฑ ํ ์ธ์ ์์ด๋๋ฅผ ์ฌ์ฉ์์๊ฒ ์ ๋ฌํ๋ฉด, ์ฌ์ฉ์๋ก ํ์ฌ๊ธ ๋์ผ ์ธ์ ์์ด๋๋ฅผ ์ฌ์ฉํด ์ฌ์ดํธ์ ๋ก๊ทธ์ธํ๋๋ก ํจ.์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ๋ฉด ๊ณต๊ฒฉ์๋ ์ฌ์ฉ์์ ๋์ผํ ์ธ์ ์ ๊ณต์ ํ ์ ์๊ฒ ๋จ. ๊ณต๊ฒฉ์๋ ์ด ์ํ์์ ์ ์์ ํ์๋ฅผ ์ํํ๋ ๊ฒ.
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
์ ์ฝ๋๋ฅผ ์ถ๊ฐํด์ฃผ๊ธฐ
security
: spring security ์ฌ์ฉํ๊ธฐ ์ํจ
springsecurity5
: view ๋จ์์ ํ์ฌ ๋ก๊ทธ์ธ ๋ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํจ
์งํ์ค์ด๋ผ ๋ด์ผ ์ค์ผ๋ก ๋ง์ ์ฌ๋ฆฌ๋๋ก ํ๊ฒ ์ต๋๋ค.๐(21.11.02)
: ๊ด๋ จ ์ค์ ์ ํด์ฃผ๊ธฐ ์ํ ํ์ผ
@RequiredArgsConstructor
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserService userService;
@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login", "/signup", "/user")
.permitAll()
.antMatchers("/")
.hasRole("USER")
.antMatchers("/admin")
.hasRole("ADMIN")
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/")
.and()
.logout()
.logoutSuccessUrl("/login")
.invalidateHttpSession(true);
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService)
.passwordEncoder(new BCryptPasswordEncoder());
}
}
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Getter
public class UserInfo implements UserDetails {
@Id
@Column(name = "code")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long code;
@Column(name = "email", unique = true)
private String email;
@Column(name = "password")
private String password;
@Column(name = "auth")
private String auth;
@Builder
public UserInfo(String email, String password, String auth) {
this.email = email;
this.password = password;
this.auth = auth;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> roles = new HashSet<>();
for(String role : auth.split(",")) {
roles.add(new SimpleGrantedAuthority(role));
}
return roles;
}
@Override
public String getUsername() {
return email;
}
@Override
public String getPassword() {
return password;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
: ์ฌ์ฉ์ ์ํฐํฐ๋ฅผ ๋ชจ๋ ๊ตฌํํ์ผ๋ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํ repository ๊ตฌํํ๊ธฐ
package SpringSecurity.FirstSpringSecurity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<UserInfo, Long> {
Optional<UserInfo> findByEmail(String email);
}
@RequiredArgsConstructor
@Service
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException((email)));
}
public Long save(UserInfoDto infoDto) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
infoDto.setPassword(encoder.encode(infoDto.getPassword()));
return userRepository.save(UserInfo.builder()
.email(infoDto.getEmail())
.auth(infoDto.getAuth())
.password(infoDto.getPassword())
.build())
.getCode();
}
}
: form์ผ๋ก ๋ฐ์ ํ์ ์ ๋ณด๋ฅผ ๋งคํ์์ผ์ค ๊ฐ์ฒด ์์ฑ
package SpringSecurity.FirstSpringSecurity;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class UserInfoDto {
private String email;
private String password;
private String auth;
}
public Long save(UserInfoDto infoDto) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
infoDto.setPassword(encoder.encode(infoDto.getPassword()));
return userRepository.save(UserInfo.builder()
.email(infoDto.getEmail())
.auth(infoDto.getAuth())
.password(infoDto.getPassword())
.build())
.getCode();
}
@RequiredArgsConstructor
@Controller
public class UserController {
private final UserService userService;
@PostMapping("/user")
public String signup(UserInfoDto infoDto) {
userService.save(infoDto);
return "redirect:/login";
}
}
@GetMapping(value = "/logout")
public String logoutPage(HttpServletRequest request, HttpServletResponse response) {
new SecurityContextLogoutHandler()
.logout(request, response, SecurityContextHolder.getContext().getAuthentication());
return "redirect:/login";
}
package SpringSecurity.FirstSpringSecurity;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("main");
registry.addViewController("/login").setViewName("login");
registry.addViewController("/admin").setViewName("admin");
registry.addViewController("/signup").setViewName("signup");
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<h1>Login</h1> <hr>
<img src="/img/info.jpg" alt="ํ์ ์ด๋ฏธ์ง" style="max-width: 500px; height: 250px"/>
<form action="/login" method="POST">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
email : <input type="text" name="username"> <br>
password : <input type="password" name="password"> <br>
<button type="submit">Login</button>
</form> <br>
<a href="/signup">Go to join! โ</a>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>sign up</title>
</head>
<body>
<h1>Sign Up</h1> <hr>
<form th:action="@{/user}" method="POST">
email : <input type="text" name="email"> <br>
password : <input type="password" name="password"> <br>
<input type="radio" name="auth" value="ROLE_ADMIN,ROLE_USER"> admin
<input type="radio" name="auth" value="ROLE_USER" checked="checked"> user <br>
<button type="submit">Join</button>
</form> <br>
<a href="/login">Go to login โ</a>
</body>
</html>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<title>main</title>
</head>
<body>
<h2>ํ์ ์ ์ฉ ํ์ด์ง</h2>
ID : <span sec:authentication="name"></span> <br>
์์ ๊ถํ : <span sec:authentication="authorities"></span> <br>
<form id="logout" action="/logout" method="POST">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<input type="submit" value="๋ก๊ทธ์์"/>
</form>
</body>
</html>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<meta charset="UTF-8">
<title>admin</title>
</head>
<body>
<h2>๊ด๋ฆฌ์ ์ ์ฉ ํ์ด์ง</h2>
ID : <span sec:authentication="name"></span> <br>
์์ ๊ถํ : <span sec:authentication="authorities"></span> <br>
<form id="logout" action="/logout" method="POST">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<input type="submit" value="๋ก๊ทธ์์"/>
</form>
</body>
</html>
์ฌ์ ํ์ ์ ๊ฐ ๋ฃ์์ด์.
user๋ก ๊ฐ์ ํด ๋ก๊ทธ์ธ ํ์ ๋
admin์ผ๋ก ๋ก๊ทธ์ธ ํ์ ๋
๊ทธ๋ฆฌ๊ณ ์ด๋ฏธ ํ์๊ฐ์
ํ user@abc.com
, admin@abc.com
์ผ๋ก ๊ฐ์
๋ถ๊ฐํจ