03G. ๋ก๊ทธ์ธ๊ณผ ๋ก๊ทธ์์
03G. ๋ก๊ทธ์ธ๊ณผ ๋ก๊ทธ์์ ๊ด๋ จ
SBB๋ ์ฌ๋ฌ ์ฌ๋์ด ์ฌ์ฉํ๋ ๊ฒ์ํ ์๋น์ค๋ค. ๊ทธ๋ฌ๋ฏ๋ก ์ง๋ฌธํ ์ฌ๋, ๋ต๋ณํ ์ฌ๋์ ๊ตฌ๋ณํ๋ ๋ก๊ทธ์ธ, ๋ก๊ทธ์์์ ํ์ ๊ธฐ๋ฅ์ด๋ค. ์์ ํ์ ๊ฐ์ ๊ธฐ๋ฅ์ ๊ตฌํํ์ผ๋ฏ๋ก ์ด์ ๋ก๊ทธ์ธ, ๋ก๊ทธ์์ ๊ธฐ๋ฅ์ ๊ตฌํํ ์ ์๋ค.
๋ก๊ทธ์ธ ๊ตฌํํ๊ธฐ
ํ์ ๊ฐ์
๋จ๊ณ์์ SITE_USER
ํ
์ด๋ธ์ ํ์ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ค. SITE_USER
ํ
์ด๋ธ์ ์ ์ฅ๋ ์ฌ์ฉ์๋ช
(์ฌ์ฉ์ ID)๊ณผ ๋น๋ฐ๋ฒํธ๋ก ๋ก๊ทธ์ธ์ ํ๋ ค๋ฉด ๋ณต์กํ ๋จ๊ณ๋ฅผ ๊ฑฐ์ณ์ผ ํ๋ค. ํ์ง๋ง ์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ์ฌ์ฉํ๋ฉด ์ด ๋จ๊ณ๋ฅผ ๋ณด๋ค ์ฝ๊ฒ ์งํํ ์ ์๋ค.
์คํ๋ง ์ํ๋ฆฌํฐ๋ ๋ฐฉ๋ํ ํ๋ ์์ํฌ์ด๋ค.
์คํ๋ง ์ํ๋ฆฌํฐ๋ ๋ฐฉ๋ํ ํ๋ ์์ํฌ์ด๋ค. ๋ฐ๋ผ์ ์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ ๋ด๋ถ์ ์ผ๋ก ์ด๋ป๊ฒ ๋์ํ๋์ง ์๊ธฐ ์ํด์๋ ์คํ๋ง ์ํ๋ฆฌํฐ์ ๋ํด์ ์์ธํ ๊ณต๋ถํด์ผ ํ๋ค. (์คํ๋ง ์ํ๋ฆฌํฐ๋ ์ฑ 1๊ถ ๋ถ๋์ผ๋ก ๋์ฌ๋งํผ ๋ฐฉ๋ํ ํ๋ ์์ํฌ์ด๋ค. ์ค์ ๋ก ์คํ๋ง ์ํ๋ฆฌํฐ์ ๋ํ ์ฑ ์ ๋ง์ด ์ถํ๋์๋ค.) ์ด ์ฑ ์ ์คํ๋ง ์ํ๋ฆฌํฐ ์์ฒด์ ๋ํ ๋ด์ฉ๋ณด๋ค๋ ํ์ฉ์ ์ธ ์ธก๋ฉด์ ๋ํด์๋ง ๋ค๋ฃฐ ๊ฒ์ด๋ค. ํ์ง๋ง ๊ฐ๋ต์ ์ธ ๊ฐ๋ ์ค๋ช ์ ์ถ๊ฐํ์ผ๋ ์ดํด์ ๋์์ด ๋๊ธฐ๋ฅผ ๋ฐ๋๋ค.
๋ค์์ ์์๋๋ก ๋ฐ๋ผํด ๋ณด์.
๋ก๊ทธ์ธ URL ๋ฑ๋ก
๋จผ์ ์คํ๋ง ์ํ๋ฆฌํฐ์ ๋ก๊ทธ์ธ URL์ ๋ฑ๋กํ์.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/
SecurityConfig.java
// (... ์๋ต ...)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
.requestMatchers(new AntPathRequestMatcher("/**")).permitAll())
.csrf((csrf) -> csrf
.ignoringRequestMatchers(new AntPathRequestMatcher("/h2-console/**")))
.headers((headers) -> headers
.addHeaderWriter(new XFrameOptionsHeaderWriter(
XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)))
.formLogin((formLogin) -> formLogin
.loginPage("/user/login")
.defaultSuccessUrl("/"))
;
return http.build();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
์ถ๊ฐํ .formLogin
๋ฉ์๋๋ ์คํ๋ง ์ํ๋ฆฌํฐ์ ๋ก๊ทธ์ธ ์ค์ ์ ๋ด๋นํ๋ ๋ถ๋ถ์ผ๋ก ๋ก๊ทธ์ธ ํ์ด์ง์ URL์ /user/login
์ด๊ณ ๋ก๊ทธ์ธ ์ฑ๊ณต์์ ์ด๋ํ๋ ๋ํดํธ ํ์ด์ง๋ ๋ฃจํธ URL(/
)์์ ์๋ฏธํ๋ค.
UserController
์คํ๋ง ์ํ๋ฆฌํฐ์ ๋ก๊ทธ์ธ URL์ /user/login
์ผ๋ก ์ค์ ํ์ผ๋ฏ๋ก User
์ปจํธ๋กค๋ฌ์ ํด๋น ๋งคํ์ ์ถ๊ฐํด์ผ ํ๋ค.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/user/
UserController.java
// (... ์๋ต ...)
public class UserController {
// (... ์๋ต ...)
@GetMapping("/login")
public String login() {
return "login_form";
}
}
.login_form.html
ํ
ํ๋ฆฟ์ ๋ ๋๋งํ๋ GET ๋ฐฉ์์ login
๋ฉ์๋๋ฅผ ์ถ๊ฐํ๋ค. ์ค์ ๋ก๊ทธ์ธ์ ์งํํ๋ @PostMapping
๋ฐฉ์์ ๋ฉ์๋๋ ์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ ๋์ ์ฒ๋ฆฌํ๋ฏ๋ก ์ง์ ๊ตฌํํ ํ์๊ฐ ์๋ค.
login_form.html
๋ก๊ทธ์ธ ํ ํ๋ฆฟ์ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ์.
ํ์ผ๋ช :
/sbb/src/main/resources/templates/
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">
์ฌ์ฉ์ID ๋๋ ๋น๋ฐ๋ฒํธ๋ฅผ ํ์ธํด ์ฃผ์ธ์.
</div>
</div>
<div class="mb-3">
<label for="username" class="form-label">์ฌ์ฉ์ID</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>
์ฌ์ฉ์ID์ ๋น๋ฐ๋ฒํธ๋ก ๋ก๊ทธ์ธ์ ํ ์ ์๋ ๋ก๊ทธ์ธ ํ ํ๋ฆฟ์ ์์ฑํ๋ค. ์ํ๋ฆฌํฐ์ ๋ก๊ทธ์ธ์ด ์คํจํ ๊ฒฝ์ฐ์๋ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ค์ ๋ฆฌ๋ค์ด๋ ํธ ๋๋ค. ์ด ๋ ํ์ด์ง ํ๋ผ๋ฏธํฐ๋ก error๊ฐ ํจ๊ป ์ ๋ฌ๋๋ค. ๋ฐ๋ผ์ ๋ก๊ทธ์ธ ํ์ด์ง์ ํ๋ผ๋ฏธํฐ๋ก error๊ฐ ์ ๋ฌ๋ ๊ฒฝ์ฐ "์ฌ์ฉ์ID ๋๋ ๋น๋ฐ๋ฒํธ๋ฅผ ํ์ธํด ์ฃผ์ธ์." ๋ผ๋ ์ค๋ฅ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํ๋๋ก ํ๋ค.
๋ก๊ทธ์ธ ์คํจ์ ํ๋ผ๋ฏธํฐ๋ก error๊ฐ ์ ๋ฌ๋๋ ๊ฒ์ ์คํ๋ง ์ํ๋ฆฌํฐ์ ๊ท์น์ด๋ค.
์ฌ๊ธฐ๊น์ง ์์ ํ๊ณ ๋ธ๋ผ์ฐ์ ์์ http://localhost:8080/user/login
์ ํธ์ถํด ๋ณด์. ๊ทธ๋ฌ๋ฉด ๋ค์๊ณผ ๊ฐ์ ํ๋ฉด์ด ๋ํ๋๋ค.
ํ์ง๋ง ์์ง ๋ก๊ทธ์ธ์ ์ํํ ์๋ ์๋ค. ์๋ํ๋ฉด ์คํ๋ง ์ํ๋ฆฌํฐ์ ๋ฌด์์ ๊ธฐ์ค์ผ๋ก ๋ก๊ทธ์ธ์ ํด์ผ ํ๋์ง ์์ง ์ค์ ํ์ง ์์๊ธฐ ๋๋ฌธ์ด๋ค. ์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ํตํด ๋ก๊ทธ์ธ์ ์ํํ๋ ๋ฐฉ๋ฒ์๋ ์ฌ๋ฌ๊ฐ์ง๊ฐ ์๋ค. ์๋ฅผ ๋ค์ด ๊ฐ์ฅ ๊ฐ๋จํ๊ฒ๋ ์ํ๋ฆฌํฐ ์ค์ ํ์ผ์ ์ง์ ์์ด๋, ๋น๋ฐ๋ฒํธ๋ฅผ ๋ฑ๋กํ์ฌ ์ธ์ฆ์ ์ฒ๋ฆฌํ๋ ๋ฉ๋ชจ๋ฆฌ ๋ฐฉ์์ด ์๋ค. ํ์ง๋ง ์ฐ๋ฆฌ๋ ์ด๋ฏธ ์ด์ ์ฅ์์ ํ์๊ฐ์ ์ ํตํด ํ์ ์ ๋ณด๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ์ผ๋ฏ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ํ์์ ๋ณด๋ฅผ ์กฐํํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ฉํด์ผ ํ ๊ฒ์ด๋ค.
์ด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ฌ์ฉ์๋ฅผ ์กฐํํ๋ ์๋น์ค(UserSecurityService
)๋ฅผ ๋ง๋ค๊ณ ๊ทธ ์๋น์ค๋ฅผ ์คํ๋ง ์ํ๋ฆฌํฐ์ ๋ฑ๋กํ๋ ๋ฐฉ๋ฒ์ ๋ํด์ ์์๋ณด์. ํ์ง๋ง UserSecurityService
๋ฅผ ๋ง๋ค๊ณ ๋ฑ๋กํ๊ธฐ ์ ์ UserSecurityService
์์ ํ์ํ UserRepository
, UserRole
๋ฑ์ ๋จผ์ ์ค๋นํด์ผ ํ๋ค.
UserRepository
์์ผ๋ก ์์ฑํ UserSecurityService
๋ ์ฌ์ฉ์๋ฅผ ์กฐํํ๋ ๊ธฐ๋ฅ์ด ํ์ํ๋ฏ๋ก ๋ค์์ฒ๋ผ findByusername
๋ฉ์๋๋ฅผ User
๋ฆฌํฌ์งํฐ๋ฆฌ์ ์ถ๊ฐํ์.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/user/
UserRepository.java
package com.mysite.sbb.user;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<SiteUser, Long> {
Optional<SiteUser> findByusername(String username);
}
UserRole
์คํ๋ง ์ํ๋ฆฌํฐ๋ ์ธ์ฆ ๋ฟ๋ง ์๋๋ผ ๊ถํ๋ ๊ด๋ฆฌํ๋ค. ๋ฐ๋ผ์ ์ธ์ฆํ์ ์ฌ์ฉ์์๊ฒ ๋ถ์ฌํ ๊ถํ์ด ํ์ํ๋ค. ๋ค์๊ณผ ๊ฐ์ด ADMIN
, USER
2๊ฐ์ ๊ถํ์ ๊ฐ๋ UserRole
์ ์ ๊ท๋ก ์์ฑํ์.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/user/
UserRole.java
package com.mysite.sbb.user;
import lombok.Getter;
@Getter
public enum UserRole {
ADMIN("ROLE_ADMIN"),
USER("ROLE_USER");
UserRole(String value) {
this.value = value;
}
private String value;
}
UserRole
์ ์ด๊ฑฐ ์๋ฃํ(enum
)์ผ๋ก ์์ฑํ๋ค. ADMIN
์ "ROLE_ADMIN"
, USER
๋ "ROLE_USER"
๋ผ๋ ๊ฐ์ ๊ฐ์ง๋๋ก ํ๋ค. ๊ทธ๋ฆฌ๊ณ ์์ ์๋ฃํ์ด๋ฏ๋ก @Setter
์์ด @Getter
๋ง ์ฌ์ฉ๊ฐ๋ฅํ๋๋ก ํ๋ค.
์ด ์ฑ ์์ ๊ตฌํํ SBB๋ ๊ถํ์ผ๋ก ํน์ ๊ธฐ๋ฅ์ ์ ์ดํ์ง ์๋๋ค. ํ์ง๋ง SBB ์๋น์ค๋ฅผ ์์ฑํ๊ณ ๋ค๋ฅธ ์ฌ๋์ด ์์ฑํ ์ง๋ฌธ์ด๋ ๋ต๋ณ์ ADMIN ๊ถํ์ ์ง๋ ์ฌ์ฉ์๋ ์์ ์ด ๊ฐ๋ฅํ๋๋ก ๋ง๋ค์ด๋ ์ข์ ๊ฒ์ด๋ค.
UserSecurityService
๊ทธ๋ฆฌ๊ณ ์คํ๋ง ์ํ๋ฆฌํฐ ์ค์ ์ ๋ฑ๋กํ UserSecurityService
๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์ ๊ท๋ก ์์ฑํ์.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/user/
UserSecurityService.java
package com.mysite.sbb.user;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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 lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class UserSecurityService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<SiteUser> _siteUser = this.userRepository.findByusername(username);
if (_siteUser.isEmpty()) {
throw new UsernameNotFoundException("์ฌ์ฉ์๋ฅผ ์ฐพ์์ ์์ต๋๋ค.");
}
SiteUser siteUser = _siteUser.get();
List<GrantedAuthority> authorities = new ArrayList<>();
if ("admin".equals(username)) {
authorities.add(new SimpleGrantedAuthority(UserRole.ADMIN.getValue()));
} else {
authorities.add(new SimpleGrantedAuthority(UserRole.USER.getValue()));
}
return new User(siteUser.getUsername(), siteUser.getPassword(), authorities);
}
}
์คํ๋ง ์ํ๋ฆฌํฐ์ ๋ฑ๋กํ์ฌ ์ฌ์ฉํ UserSecurityService
๋ ์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ ์ ๊ณตํ๋ UserDetailsService
์ธํฐํ์ด์ค๋ฅผ ๊ตฌํ(implements)ํด์ผ ํ๋ค. ์คํ๋ง ์ํ๋ฆฌํฐ์ UserDetailsService
๋ loadUserByUsername
๋ฉ์๋๋ฅผ ๊ตฌํํ๋๋ก ๊ฐ์ ํ๋ ์ธํฐํ์ด์ค์ด๋ค. loadUserByUsername
๋ฉ์๋๋ ์ฌ์ฉ์๋ช
์ผ๋ก ๋น๋ฐ๋ฒํธ๋ฅผ ์กฐํํ์ฌ ๋ฆฌํดํ๋ ๋ฉ์๋์ด๋ค.
UserSecurityService
๋ ์คํ๋ง ์ํ๋ฆฌํฐ ๋ก๊ทธ์ธ ์ฒ๋ฆฌ์ ํต์ฌ ๋ถ๋ถ์ด๋ค.
์กฐ๊ธ ๋ ์์ธํ ์ดํด๋ณด๋ฉด, loadUserByUsername
๋ฉ์๋๋ ์ฌ์ฉ์๋ช
์ผ๋ก SiteUser
๊ฐ์ฒด๋ฅผ ์กฐํํ๊ณ ๋ง์ฝ ์ฌ์ฉ์๋ช
์ ํด๋นํ๋ ๋ฐ์ดํฐ๊ฐ ์์ ๊ฒฝ์ฐ์๋ UsernameNotFoundException
์ค๋ฅ๋ฅผ ๋ด๊ฒ ํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ฌ์ฉ์๋ช
์ด "admin"์ธ ๊ฒฝ์ฐ์๋ ADMIN
๊ถํ์ ๋ถ์ฌํ๊ณ ๊ทธ ์ด์ธ์ ๊ฒฝ์ฐ์๋ USER ๊ถํ์ ๋ถ์ฌํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ฌ์ฉ์๋ช
, ๋น๋ฐ๋ฒํธ, ๊ถํ์ ์
๋ ฅ์ผ๋ก ์คํ๋ง ์ํ๋ฆฌํฐ์ User
๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ ๋ฆฌํดํ๋ค. ์คํ๋ง ์ํ๋ฆฌํฐ๋ loadUserByUsername
๋ฉ์๋์ ์ํด ๋ฆฌํด๋ User ๊ฐ์ฒด์ ๋น๋ฐ๋ฒํธ๊ฐ ํ๋ฉด์ผ๋ก๋ถํฐ ์
๋ ฅ ๋ฐ์ ๋น๋ฐ๋ฒํธ์ ์ผ์นํ๋์ง๋ฅผ ๊ฒ์ฌํ๋ ๋ก์ง์ ๋ด๋ถ์ ์ผ๋ก ๊ฐ์ง๊ณ ์๋ค.
SecurityConfig
๊ทธ๋ฆฌ๊ณ ๋ค์์ฒ๋ผ ์คํ๋ง ์ํ๋ฆฌํฐ์ AuthenticationManager
๋น์ ์์ฑํ์.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/
SecurityConfig.java
// (... ์๋ต ...)
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
// (... ์๋ต ...)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// (... ์๋ต ...)
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
AuthenticationManager
๋น์ ์์ฑํ๋ค. AuthenticationManager
๋ ์คํ๋ง ์ํ๋ฆฌํฐ์ ์ธ์ฆ์ ๋ด๋นํ๋ค. AuthenticationManager
๋ ์ฌ์ฉ์ ์ธ์ฆ์ ์์์ ์์ฑํ UserSecurityService
์ PasswordEncoder
๋ฅผ ์ฌ์ฉํ๋ค.
navbar.html
๊ทธ๋ฆฌ๊ณ ๋ง์ง๋ง์ผ๋ก ๋ก๊ทธ์ธ ํ์ด์ง์ ์ง์ ํ ์ ์๋ ๋ก๊ทธ์ธ ๋งํฌ๋ฅผ ๋ค๋น๊ฒ์ด์ ๋ฐ์ ๋ค์๊ณผ ๊ฐ์ด ์ถ๊ฐํ์.
ํ์ผ๋ช :
/sbb/src/main/resources/templates/
navbar.html
<nav th:fragment="navbarFragment" class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
<div class="container-fluid">
<a class="navbar-brand" href="/">SBB</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" th:href="@{/user/login}">๋ก๊ทธ์ธ</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/user/signup}">ํ์๊ฐ์
</a>
</li>
</ul>
</div>
</div>
</nav>
username
๊ณผ password
๋ฅผ ์ ๋๋ก ์
๋ ฅํ๋ฉด ๋ก๊ทธ์ธ์ด ์ ์ ์ํ๋๊ณ ๋ฉ์ธ ํ๋ฉด์ผ๋ก ์ด๋ํ ๊ฒ์ด๋ค.
์ถํํ๋ค! ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ๋ค.
๋ก๊ทธ์ธ / ๋ก๊ทธ์์ ๋งํฌ
ํ์ง๋ง ๋ก๊ทธ์ธํ ํ์๋ ๋ด๋น๊ฒ์ด์ ๋ฐ์๋ ์ฌ์ ํ "๋ก๊ทธ์ธ" ๋งํฌ๊ฐ ๋จ์ ์๋ค. ๋ก๊ทธ์ธ์ ํ ์ํ๋ผ๋ฉด ์ด ๋งํฌ๋ "๋ก๊ทธ์์" ๋งํฌ๋ก ๋ฐ๋์ด์ผ ํ๋ค.
๋ฐ๋๋ก ๋ก๊ทธ์์ ์ํ์์๋ "๋ก๊ทธ์ธ" ๋งํฌ๋ก ๋ฐ๋์ด์ผ ํ๋ค.
์ฌ์ฉ์์ ๋ก๊ทธ์ธ ์ฌ๋ถ๋ ํ์๋ฆฌํ์ sec:authorize
์์ฑ์ ํตํด ์์ ์๋ค.
sec:authorize="isAnonymous()"
: ์ด ์์ฑ์ ๋ก๊ทธ์ธ ๋์ง ์์ ๊ฒฝ์ฐ์๋ง ํด๋น ์๋ฆฌ๋จผํธ๊ฐ ํ์๋๊ฒ ํ๋ค.sec:authorize="isAuthenticated()"
: ์ด ์์ฑ์ ๋ก๊ทธ์ธ ๋ ๊ฒฝ์ฐ์๋ง ํด๋น ์๋ฆฌ๋จผํธ๊ฐ ํ์๋๊ฒ ํ๋ค.
๋ฐ๋ผ์ ๋ค์๊ณผ ๊ฐ์ด ๋ด๋น๊ฒ์ด์ ๋ฐ๋ฅผ ์์ ํ ์ ์๋ค.
ํ์ผ๋ช :
/sbb/src/main/resources/templates/
navbar.html
<nav th:fragment="navbarFragment" class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
<div class="container-fluid">
<a class="navbar-brand" href="/">SBB</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" sec:authorize="isAnonymous()" th:href="@{/user/login}">๋ก๊ทธ์ธ</a>
<a class="nav-link" sec:authorize="isAuthenticated()" th:href="@{/user/logout}">๋ก๊ทธ์์</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/user/signup}">ํ์๊ฐ์
</a>
</li>
</ul>
</div>
</div>
</nav>
๋ก๊ทธ์ธ์ ์ํ ์ํ๋ผ๋ฉด sec:authorize="isAnonymous()"
๊ฐ ์ฐธ์ด๋์ด "๋ก๊ทธ์ธ" ๋งํฌ๊ฐ ํ์๋๊ณ ๋ก๊ทธ์ธ์ ํ ์ํ๋ผ๋ฉด sec:authorize="isAuthenticated()"
๊ฐ ์ฐธ์ด๋์ด "๋ก๊ทธ์์" ๋งํฌ๊ฐ ํ์๋ ๊ฒ์ด๋ค.
๋ก๊ทธ์์ ๋งํฌ๋ /user/logout
์ผ๋ก ์ง์ ํ๋ค. ํ์ง๋ง ๋ก๊ทธ์์ ๊ธฐ๋ฅ์ ์์ง ๊ตฌํํ์ง ์์ ์ํ์ด๋ค. ๋ก๊ทธ์์ ๊ธฐ๋ฅ์ ๋ฐ๋ก ์ด์ด์ ์งํํ๋ค.
๋ก๊ทธ์์ ๊ตฌํํ๊ธฐ
๋ก๊ทธ์ธ์ ํตํด ๋ก๊ทธ์ธ์ ํ๋ค๋ฉด ๋ด๋น๊ฒ์ด์ ์๋จ์ "๋ก๊ทธ์์" ๋งํฌ๊ฐ ๋ํ๋ ๊ฒ์ด๋ค. ํ์ง๋ง "๋ก๊ทธ์์" ๋งํฌ๋ฅผ ๋๋ฅด๋ฉด ๋ค์๊ณผ ๊ฐ์ด 404 ์ค๋ฅ ํ์ด์ง๊ฐ ํ์๋๋ค.
๋ก๊ทธ์์ ์ญ์ ์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ์ฌ์ฉํ์ฌ ์ฝ๊ฒ ๊ตฌํํ ์ ์๋ค.
SecurityConfig
๋ค์๊ณผ ๊ฐ์ด SecurityConfig
ํ์ผ์ ์์ ํ์.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/
SecurityConfig.java
// (... ์๋ต ...)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
.requestMatchers(new AntPathRequestMatcher("/**")).permitAll())
.csrf((csrf) -> csrf
.ignoringRequestMatchers(new AntPathRequestMatcher("/h2-console/**")))
.headers((headers) -> headers
.addHeaderWriter(new XFrameOptionsHeaderWriter(
XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)))
.formLogin((formLogin) -> formLogin
.loginPage("/user/login")
.defaultSuccessUrl("/"))
.logout((logout) -> logout
.logoutRequestMatcher(new AntPathRequestMatcher("/user/logout"))
.logoutSuccessUrl("/")
.invalidateHttpSession(true))
;
return http.build();
}
// (... ์๋ต ...)
}
๋ก๊ทธ์์์ ์ํ ์ค์ ์ ์ถ๊ฐํ๋ค. ๋ก๊ทธ์์ URL์ /user/logout
์ผ๋ก ์ค์ ํ๊ณ ๋ก๊ทธ์์์ด ์ฑ๊ณตํ๋ฉด ๋ฃจํธ(/
) ํ์ด์ง๋ก ์ด๋ํ๋๋ก ํ๋ค. ๊ทธ๋ฆฌ๊ณ ๋ก๊ทธ์์์ ์์ฑ๋ ์ฌ์ฉ์ ์ธ์
๋ ์ญ์ ํ๋๋ก ์ฒ๋ฆฌํ๋ค.
์์ ์ ์๋ฃํ ํ ๋ก๊ทธ์ธ, ๋ก๊ทธ์์ ๊ธฐ๋ฅ์ด ์ ์คํ๋๋์ง ํ์ธํด ๋ณด์.
๋ก๊ทธ์์์ ๋๋ฅด๋ฉด ๋ค๋น๊ฒ์ด์ ๋ฐ์๋ ๋ค์ "๋ก๊ทธ์ธ" ๋งํฌ๊ฐ ๋ํ๋ ๊ฒ์ด๋ค.