Spring Security
λ₯Ό μ΄μ©ν΄μ μΈμ
κΈ°λ° λ‘κ·ΈμΈ λ° κΆν κ²μ¦μ ꡬνν©λλ€.
λν
μΌν μ€μ 보λ€λ μ 체μ μΈ νλ¦μ 보기 μν νλ‘μ νΈμ
λλ€.
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "MEMBER")
@Getter
public class Member {
@Id @GeneratedValue
private Long id;
private String username;
private String password;
private boolean enabled;
@Enumerated(EnumType.STRING)
private Role role;
@Builder
public Member(String username, String password, boolean enabled, Role role) {
this.username = username;
this.password = password;
this.enabled = enabled;
this.role = role;
}
}
spring security
κ° μ 곡νλ ROLE
λ€μ΄λ° μ μ±
μ΄ ROLE_κΆν
μ΄λ―λ‘ λ§μΆ°μ μμ±ν΄μ€λλ€.
@Getter
public enum Role {
ROLE_ADMIN("κ΄λ¦¬μ"), ROLE_MANAGER("맀λμ "), ROLE_MEMBER("μΌλ°μ¬μ©μ");
private String description;
Role(String description) {
this.description = description;
}
}
Spring security
μ User
ν΄λμ€λ₯Ό 보면 "ROLE_"
λ‘ μμνλ κΆνμ μ°Ύλ κ²μ νμΈν μ μμ΅λλ€.
username
μ μ΄μ©ν΄ λ‘κ·ΈμΈ μ²λ¦¬λ₯Ό ν κ²μ΄κΈ° λλ¬Έμ JPA λ€μ΄λ° 쿼리λ₯Ό μ΄μ©ν΄ findByUsername
λ©μλ νλλ₯Ό μ μν©λλ€.
κ°λ¨ν μ€μ΅μ μν΄ μλΉμ€ κ³μΈ΅μ λμ§ μμμ΅λλ€.
public interface MemberRepository extends CrudRepository<Member, Long> {
Optional<Member> findByUsername(String username);
}
spring security
κ° μ 곡νλ User
ν΄λμ€λ₯Ό μ°λ¦¬κ° μ μν Member
λ‘ μ¬μ©νκΈ° μν΄ μ»€μ€ν
ν©λλ€.
μ΄ν SecurityUser
λ₯Ό ν΅ν΄ Member
μ μ κ·Όν κ²μ΄λ―λ‘ Member
λ₯Ό νλλ‘ κ°κ² νκ³ μμ±μλ₯Ό ν΅ν΄ κ°μ μ μ§μμΌ μ€λλ€.
super
ν€μλλ₯Ό μ΄μ©ν΄ λΆλͺ¨ ν΄λμ€(User
)μ μμ±μλ‘ username
, password
, role
μ λ겨μ€λλ€.
μμ²λλ λ°μ΄ν°λ₯Ό νμΈνκΈ° μν΄ λ‘κ·Έλ μ°μ΄λ³΄μμ΅λλ€.
@Slf4j
@Getter @Setter
public class SecurityUser extends User {
private Member member;
public SecurityUser(Member member) {
super(member.getUsername(), member.getPassword(), AuthorityUtils.createAuthorityList(member.getRole().toString()));
log.info("SecurityUser member.username = {}", member.getUsername());
log.info("SecurityUser member.password = {}", member.getPassword());
log.info("SecurityUser member.role = {}", member.getRole().toString());
this.member = member;
}
}
λ‘κ·ΈμΈμ μν username
(νΉμ id
, email
) μ΄ DBμ μλμ§ νμΈνλ λ©μλ loadUserByUsername
λ©μλλ₯Ό μμ±ν©λλ€.
μ΄ λ©μλμμ μ¬μ©νκΈ° μν΄ repository
μ findByUsername
λ©μλλ₯Ό μ μν κ² μ
λλ€.
memberRepository
λ₯Ό μ£Όμ
λ°κ³ findByUsername
λ©μλλ₯Ό μ΄μ©ν΄ μ
λ ₯λ username
μ΄ μ ν¨νμ§ νμΈν©λλ€.
username
μ΄ μ ν¨νμ§ μλ€λ©΄ μμΈλ₯Ό λ°μμν€κ³ μ ν¨νλ€λ©΄ username
μΌλ‘ μ°Ύμμ¨ Member
λ₯Ό μ΄μ©ν΄ Custom User
μΈ SecurityUser
λ₯Ό μμ±νμ¬ λ°νν©λλ€.
@Slf4j
@Component
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<Member> findMember = memberRepository.findByUsername(username);
if (!findMember.isPresent()) throw new UsernameNotFoundException("μ‘΄μ¬νμ§ μλ username μ
λλ€.");
log.info("loadUserByUsername member.username = {}", username);
return new SecurityUser(findMember.get());
}
}
μ΄μ μΈμ¦ λ° κΆν μ²λ¦¬λ₯Ό μν SecutiryConfig
ν΄λμ€λ₯Ό μμ±ν©λλ€.
WebSecurityConfigurerAdapter
λ₯Ό μμλ°κ³ configure
λ©μλλ₯Ό μ€λ²λΌμ΄λ© ν©λλ€.
κ°μ₯ λ¨Όμ authorizaRequests
λ₯Ό μ΄μ©ν΄ κΆν κ²μ¦μ μνν©λλ€.
antMatchers
μλ 곡ν΅λ κΆνμ κ°μ§λ endpoint
λ₯Ό λ¬Άμ΄μ€λλ€. κ·Έλ¦¬κ³ λ€μμ λ©μλλ₯Ό μ΄μ©ν΄μ λ¬Άμ΄μ§ endpoint
μ λν κΆν λΆκΈ°λ₯Ό μνν©λλ€.
permitAll
: μΈμ¦ μμ΄ μ κ·Ό κ°λ₯authentication
: μΈμ¦λ μ¬μ©μλΌλ©΄ νΉλ³ν κΆν μμ΄ μ κ·Ό κ°λ₯hasAnyRole
: λͺ
μν κΆνμ κ°μ§ μΈμ¦λ μ¬μ©μλ§ μ κ·Ό κ°λ₯hasRole
: hasAnyRole
κ³Ό λμΌν κΈ°λ₯μ΄μ§λ§ νλμ κΆνκ³Ό 맀νλ€μμ formLogin
μ μ΄μ©ν΄ λ‘κ·ΈμΈ κ΄λ ¨ κΈ°λ₯μ μ μν©λλ€.
loginPage
: μΈμ¦λμ§ μμ μ¬μ©μκ° μΈμ¦μ νμλ‘ νλ endpoint
μ μ κ·Όμ μ€μ ν κ²½λ‘λ‘ μ΄λμμΌμ€λ€. (λ³΄ν΅ λ‘κ·ΈμΈ νμ΄μ§λ‘ μ΄λ)loginProcessingUrl
: μ§μ ν endpoint
λ‘ POST
μμ²μ΄ μ¬ λ μμ²λ μ 보λ₯Ό κΈ°λ°μΌλ‘ λ‘κ·ΈμΈ μ²λ¦¬λ₯Ό μνexceptionHandling().accessDiniedPage
: μΈμ¦λ μ¬μ©μμ΄μ§λ§ κΆν λ°μ endpoint
μ μ κ·Όμ μ΄λμν¬ uri λͺ
μlogout().logoutUrl
: ν΄λΉ URLλ‘ μμ²μ΄ μ€λ κ²½μ° λ‘κ·Έμμ μνhttp.userDetailsService(userDetailsService);
μ€μ λ‘κ·ΈμΈ μ²λ¦¬λ₯Ό μννλ UserDetailsService
λ₯Ό μ§μ
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/","/registry","/login", "/css/**").permitAll()
.antMatchers("/member/**").authenticated() // μΌλ°μ¬μ©μ μ κ·Ό κ°λ₯
.antMatchers("/manager/**").hasAnyRole("MANAGER", "ADMIN") // 맀λμ , κ΄λ¦¬μ μ κ·Ό κ°λ₯
.antMatchers("/admin/**").hasRole("ADMIN"); // κ΄λ¦¬μλ§ μ κ·Ό κ°λ₯
// μΈμ¦ νμμ λ‘κ·ΈμΈ νμ΄μ§μ λ‘κ·ΈμΈ μ±κ³΅μ 리λ€μ΄λν
κ²½λ‘ μ§μ
http.formLogin().loginPage("/login").defaultSuccessUrl("/", true);
// λ‘κ·ΈμΈμ΄ μνλ uri 맀ν (post μμ²μ΄ κΈ°λ³Έ)
http.formLogin().loginProcessingUrl("/login").defaultSuccessUrl("/", true);
// μΈμ¦λ μ¬μ©μμ΄μ§λ§ μΈκ°λμ§ μμ κ²½λ‘μ μ κ·Όμ 리λ€μ΄λν
μν¬ uri μ§μ
http.exceptionHandling().accessDeniedPage("/forbidden");
// logout
http.logout().logoutUrl("/logout").logoutSuccessUrl("/");
http.userDetailsService(userDetailsService);
}
}
μΈμ¦λ μ¬μ©μμκ² viewλ₯Ό λ¬λ¦¬νκΈ° μν΄ thymeleaf extras Springsecurity5
λ₯Ό μ¬μ©νμ΅λλ€. (μΈμ¦λ μ¬μ©μμκ²λ§ λ‘κ·Έμμ λ²νΌ νμ, λ‘κ·ΈμΈ λ²νΌ λ―Ένμ λ± ..)
Maven Repository λ§ν¬
μμ‘΄μ± μΆκ° ν μλμ κ°μ΄ λ€μμ€νμ΄μ€λ₯Ό μΆκ°ν΄μ€λλ€.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="my-4">
<h2>ν</h2>
<div sec:authorize="isAuthenticated()">
<p th:text="|${principal.username}λ νμν©λλ€.|"></p>
<p th:text="|νμ¬ ${principal.username} λμ κΆν : ${role}|"></p>
<ul>
<li th:text="|session.getId = ${#session.getId()}|"></li>
<li th:text="|session.getAttributeNames = ${#session.getAttributeNames()}|"></li>
<li th:text="|session.getCreationTime = ${#session.getCreationTime()}|"></li>
</ul>
</div>
<div class="btn">
<a th:href="@{/registry}">
<button class="btn btn-lg btn-secondary">νμκ°μ
</button>
</a>
<a th:href="@{/login}">
<button sec:authorize="!isAuthenticated()" class="btn btn-lg btn-dark">λ‘κ·ΈμΈ</button>
</a>
<a th:href="@{/logout}">
<button sec:authorize="isAuthenticated()" class="btn btn-danger btn-lg">λ‘κ·Έμμ</button>
</a>
<hr class="my-4">
<a th:href="@{/admin/home}"><button class="btn btn-lg btn-dark">ADMIN page μ κ·Ό </button></a>
<a th:href="@{/manager/home}"><button class="btn btn-lg btn-dark">MANAGER page μ κ·Ό</button></a>
</div>
</div>
</div>
</body>
</html>
[ νμκ°μ
νΌ ]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>νμκ°μ
</title>
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="my-4">
<h2>νμκ°μ
</h2>
</div>
<form th:action method="post" th:object="${member}">
<div class="mb-3">
<label for="username" class="form-label">username</label>
<input type="text" class="form-control" id="username" th:field="*{username}">
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" th:field="*{password}">
</div>
<hr class="my-3">
<div class="mb-3">
<div th:each="r : ${roles}" class="form-check form-check-inline">
<input type="radio" th:field="*{role}" th:value="${r.value}" class="form-check-input">
<label th:for="${#ids.prev('role')}" th:text="${r.key}" class="form-check-label"></label>
</div>
</div>
<button type="submit" class="btn btn-primary">νμκ°μ
</button>
</form>
</div>
</body>
</html>
[ λ‘κ·ΈμΈ νΌ ]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Login</title>
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="my-4">
<h2>λ‘κ·ΈμΈ</h2>
</div>
<form th:action method="post" th:object="${member}">
<div class="mb-3">
<label for="username" class="form-label">username</label>
<input type="text" class="form-control" id="username" th:field="*{username}">
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" th:field="*{password}">
</div>
<button type="submit" class="btn btn-primary">λ‘κ·ΈμΈ</button>
</form>
</div>
</body>
</html>
μλΉμ€ κ³μΈ΅μ λ°λ‘ λμ§ μμ 컨νΈλ‘€λ¬μμ λ°λ‘ λ ν¬μ§ν 리λ₯Ό μ£Όμ λ°μ μ¬μ©νμ΅λλ€.
password
μ μ₯μ BCryptPasswordEncoder
λ₯Ό μ΄μ©ν΄ λ¨λ°©ν₯ ν΄μλ‘ μΈμ½λ©νμ΅λλ€.
@Slf4j
@RequiredArgsConstructor
@Controller
public class RegistryController {
private final MemberRepository memberRepository;
private final BCryptPasswordEncoder passwordEncoder;
@GetMapping("/registry")
public String registryForm(Model model) {
model.addAttribute("member", new RegistryRequest());
return "registration";
}
@PostMapping("/registry")
public String registry(@ModelAttribute RegistryRequest registryRequest) {
Member member = Member.builder()
.username(registryRequest.getUsername())
.password(passwordEncoder.encode(registryRequest.getPassword()))
.role(registryRequest.getRole())
.build();
memberRepository.save(member);
return "redirect:/login";
}
@ModelAttribute("roles")
public Map<String, Role> roles() {
Map<String, Role> map = new LinkedHashMap<>();
map.put("κ΄λ¦¬μ", Role.ROLE_ADMIN);
map.put("맀λμ ", Role.ROLE_MANAGER);
map.put("μΌλ° μ¬μ©μ", Role.ROLE_MEMBER);
return map;
}
}
BCryptPasswordEncoder
λ κ°λ¨νκ² main ν΄λμ€μμ λΉμΌλ‘ λ±λ‘ν΄μ£Όμμ΅λλ€.
viewμ controller κ° λ°μ΄ν° μΈν°νμ΄μ±μ μν΄ μ¬μ©ν μμ²κ°μ²΄μ λλ€.
@Getter @Setter
public class RegistryRequest {
private String username;
private String password;
private Role role = Role.ROLE_MEMBER;
}
μ λΆμ λλ€..
λ‘κ·ΈμΈ μͺ½ 컨νΈλ‘€λ¬λ λ‘κ·ΈμΈ νΌ λλλ§λ§μ λ΄λΉν©λλ€.
λ‘κ·ΈμΈ μ²λ¦¬λ SecurityConfig
μ loginProcessingUrl
λ‘ λ³΄λ΄μ£ΌκΈ°λ§ νλ©΄ λ©λλ€.
@Controller
public class LoginController {
@GetMapping("/login")
public String loginForm(Model model) {
model.addAttribute("member", new LoginRequest());
return "login";
}
}
μ΄μμΌλ‘ Spring Security
λ₯Ό μ΄μ©ν κ°λ¨ν μΈμ
λ°©μ λ‘κ·ΈμΈμ μμ보μμ΅λλ€.
μ¬μ€ μ΄λ° λ°©μμ μ΄μ λ μ μ°μ΄μ§ μλλ€κ³ ν©λλ€. μ£Όλ‘ JWT
ν ν°λ°©μμ μ¬μ©νμ£ . 보μμ μΈ λ¬Έμ κ° κ°μ₯ ν¬κ² μ§λ§ μ΄λ° μΈμ
λ°©μμ μ¬μ©νλ€λ©΄ μ΄ν Oauth2
μ¬μ©μ λ¬Έμ κ° λ μ μμ΅λλ€.
κ·Έλλ κΈ°λ³Έμ μκ³ μμ΄μΌνλ€κ³ μκ°νκΈ°μ ν λ² κ°λ¨νκ² κ΅¬νν΄λ΄€μ΅λλ€. γ
λ€μμλ JWT
λ₯Ό μ΄μ©ν μΈμ¦μ ꡬνν΄λ³΄κ² μ΅λλ€ !
μ 체 μμ€μ½λλ κΉνλΈλ₯Ό μ°Έκ³ ν΄μ£ΌμΈμ κ°μ¬ν©λλ€ γ π
μλ νμΈμ.
OAuth2 μ¬μ©μ μ λ¬Έμ κ° μλ€κ³ μκ°νμλμ?