MapleStory Finger Point

๐Ÿ”˜ ํ”„๋กœ์ ํŠธ

[ํ”„๋กœ์ ํŠธ/Spring Security] ๋กœ๊ทธ์ธ ๊ตฌํ˜„

HYEJU01 2024. 12. 4. 17:58

 

 

( ์ œ๊ฐ€ ์ž‘์—…ํ–ˆ๋˜  ํ”„๋กœ์ ํŠธ ์ฝ”๋“œ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ค๋ช…ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.)

 

 

 

1. build gradle ์— ์˜์กด์„ฑ ์ถ”๊ฐ€

//์‹œํ๋ฆฌํ‹ฐ ๋ฒ„์ „์€ ์Šคํ”„๋ง๋ฒ„์ „์— ๋”ฐ๋ผ ์‚ฌ์šฉ๋ฐฉ๋ฒ•์ด ์™„์ „ํžˆ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.
//์‹œํ๋ฆฌํ‹ฐ 5๋ฒ„์ „ => ์Šคํ”„๋ง ๋ถ€ํŠธ 2๋ฒ„์ „ (์ˆ˜์—…)
//์‹œํ๋ฆฌํ‹ฐ 6๋ฒ„์ „ => ์Šคํ”„๋ง ๋ถ€ํŠธ 3๋ฒ„์ „ (๋ฌธ๋ฒ•์ด ์™„์ „ ๋ณ€๊ฒฝ๋˜๋‹ˆ ์ฃผ์˜)
implementation 'org.springframework.boot:spring-boot-starter-security'
//์‹œํ๋ฆฌํ‹ฐ ํƒ€์ž„๋ฆฌํ”„์—์„œ ์‚ฌ์šฉ
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
//์‹œํ๋ฆฌํ‹ฐ ํ…Œ์ŠคํŠธ
testImplementation 'org.springframework.security:spring-security-test'

 

  • ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ์ฒ˜์Œ ์„ค์ • ๋˜๋ฉด ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด์„œ ์‹œํ๋ฆฌ๊ฐ€ ๊ธฐ๋ณธ ์ œ๊ณตํ•˜๋Š” ๋กœ๊ทธ์ธ ํ™”๋ฉด์ด ๋ณด์ด๊ฒŒ ๋จ.
  • ๊ธฐ๋ณธ์•„์ด๋””๋Š” user / ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” console์ฐฝ์— ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฐœ๊ธ‰๋จ.
  • ๋กœ๊ทธ์•„์›ƒ์˜ ๊ฒฝ๋กœ๋Š” /logout์ด ๊ธฐ๋ณธ์ด ๋œ๋‹ค.

 

 

 

 

2. ์‹œํ๋ฆฌํ‹ฐ ์„ค์ • ํŒŒ์ผ ๊ตฌํ˜„

 

security_config.java

 

 

 

์ฃผ์š”๋ฉ”์„œ๋“œ

 

 

 

3. SecurityConfig ์— SecurityFilterChain ๊ตฌํ˜„ 

 

package com.project.tobe.config;
import com.project.tobe.dto.UserRole;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity //์‹œํ๋ฆฌํ‹ฐ ์„ค์ •ํŒŒ์ผ์„ ์‹œํ๋ฆฌํ‹ฐ ํ•„ํ„ฐ์— ๋“ฑ๋ก
public class SecurityConfig {
@Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder(); // ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™”๋ฅผ ์œ„ํ•œ ์ธ์ฝ”๋”
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()//csrfํ† ํฐ ์‚ฌ์šฉx
.authorizeRequests() // ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ ๊ทœ์น™ ์„ค์ •
.antMatchers("/login.user").permitAll() // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋Š” ์ธ์ฆ ์—†์ด ์ ‘๊ทผ ํ—ˆ์šฉ
.antMatchers("/employee.do").hasAnyRole("S", "A") // "/employee.do" ๊ฒฝ๋กœ๋Š” "A","S" ์—ญํ• ์„ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ
.antMatchers("/*.do").authenticated() // ".do"๋กœ ๋๋‚˜๋Š” ๋ชจ๋“  ์š”์ฒญ์€ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ
.anyRequest().permitAll() // ๊ทธ์™ธ ์š”์ฒญ์€ ์ธ์ฆ ์—†์ด ์ ‘๊ทผ ๊ฐ€๋Šฅ
.and()
.formLogin() // ๋กœ๊ทธ์ธ ์„ค์ •
.loginPage("/login.user") // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ์„ค์ •(์‚ฌ์šฉ์ž ์ •์˜)
.usernameParameter("employeeId") //๋กœ๊ทธ์ธํผ์—์„œ ์•„์ด๋””ํ•„๋“œ name ๊ฐ’์„ employeeId ๋กœ ์„ค์ •
.passwordParameter("employeePw") //๋กœ๊ทธ์ธํผ์—์„œ ๋น„๋ฐ€๋ฒˆํ˜ธํ•„๋“œ name ๊ฐ’์„ employeePw ๋กœ ์„ค์ •
.loginProcessingUrl("/loginForm") //๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€๋กœ์ฑ„ ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ํด๋ž˜์Šค๋กœ ๋กœ๊ทธ์ธ์„ ์—ฐ๊ฒฐ
.defaultSuccessUrl("/main.do") //๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ ์ด๋™ํ•  URL
.failureUrl("/login.user?err=true") // ๋กœ๊ทธ์ธ ์‹คํŒจ์‹œ ์ด๋™ URL
// .failureHandler(new CustomAuthenticationFailureHandler()) // ์ปค์Šคํ…€ ์‹คํŒจ ํ•ธ๋“ค๋Ÿฌ ๋“ฑ๋ก
.and()
.logout() // ๋กœ๊ทธ์•„์›ƒ ์„ค์ •
.logoutUrl("/logout") // ๋กœ๊ทธ์•„์›ƒ ํŽ˜์ด์ง€ ์„ค์ •
.logoutSuccessUrl("/login.user") // ๋กœ๊ทธ์•„์›ƒ ์„ฑ๊ณต ์‹œ ์ด๋™ํ•  URL
.invalidateHttpSession(true)// ๋กœ๊ทธ์•„์›ƒ ์‹œ ์„ธ์…˜ ๋ฌดํšจํ™”
.deleteCookies("JSESSIONID") // ๋กœ๊ทธ์•„์›ƒ ์‹œ ์ฟ ํ‚ค ์‚ญ์ œ (JSESSIONID)
.and()
.exceptionHandling() //์ธ์ฆ(Authentication) ๋˜๋Š” ๊ถŒํ•œ ๋ถ€์—ฌ(Authorization) ๊ด€๋ จ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
.accessDeniedPage("/accessDenied"); //์ ‘๊ทผ๊ถŒํ•œ์ด ์—†๋Š” ์š”์ฒญ์€ accessDenied ํŽ˜์ด์ง€๋กœ ์ด๋™
return http.build();
}
}

 

 

 

 

 

ํ•ด๋‹น ํ˜•ํƒœ๋ฅผ ์‹œํ๋ฆฌํ‹ฐ์—๊ฒŒ ์ „๋‹ฌํ•ด์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ์ฒด ๊ตฌํ˜„์„ ๋จผ์ €ํ•ด์ค˜์•ผํ•œ๋‹ค.

 

 

 

4. UserDetailsService ๊ตฌํ˜„ (์ธ์ฆ๊ฐ์ฒด ๋งŒ๋“ค๊ธฐ) 

 

 

package com.project.tobe.serviceimpl;
import com.project.tobe.entity.Employee;
import com.project.tobe.repository.EmployeeRepository;
//import com.project.tobe.security.EmployeeDetails;
import com.project.tobe.security.EmployeeDetails;
import lombok.RequiredArgsConstructor;
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;
@Service
@RequiredArgsConstructor
//loginProcessingUrl()๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด loadUserByUsername์ด ์ž๋™ ํ˜ธ์ถœ๋จ
public class PrincipalDetailsService implements UserDetailsService {
private final EmployeeRepository employeeRepository;
/**
* ์‚ฌ์šฉ์ž์˜ ์•„์ด๋””(username)๋ฅผ ํ†ตํ•ด ์ธ์ฆ ์ •๋ณด๋ฅผ ๋กœ๋“œํ•˜๋Š” ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.
* ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ๋กœ๊ทธ์ธ ์š”์ฒญ ์‹œ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ username์„ ๊ธฐ์ค€์œผ๋กœ Employee ๊ฐ์ฒด ์กฐํšŒ
Employee employee = employeeRepository.findById(username)
.orElseThrow(() -> {
// username ์— ํ•ด๋‹นํ•˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์—†์œผ๋ฉด ์˜ˆ์™ธ ๋ฐœ์ƒ
return new UsernameNotFoundException("ํ•ด๋‹น ์œ ์ €๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");
});
// ์กฐํšŒ๋œ ์‚ฌ์šฉ์ž ๊ฐ์ฒด๋ฅผ EmployeeDetails (์œ ์ € ๊ฐ์ฒด) ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ฐ˜ํ™˜
// ์‹œํ๋ฆฌํ‹ฐ์„ธ์…˜( new Authentication(new MyUserDetails()) ) ํ˜•์‹์œผ๋กœ ์ „๋‹ฌ ๋จ
return new EmployeeDetails(employee);
}
}

 

 

 

5.UserDetails ๊ตฌํ˜„ (์œ ์ € ๊ฐ์ฒด ๋งŒ๋“ค๊ธฐ)

 

package com.project.tobe.security;
import com.project.tobe.dto.EmployeeDTO;
import com.project.tobe.entity.Employee;
import com.project.tobe.dto.UserRole;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
public class EmployeeDetails implements UserDetails {
private Employee employee;
private EmployeeDTO employeedto;
public EmployeeDetails(Employee employee) {
this.employee = employee;
}
// ๊ถŒํ•œ ๊ด€๋ จ ์ž‘์—…์„ ํ•˜๊ธฐ ์œ„ํ•œ role return
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
try {
UserRole role = UserRole.valueOf(employee.getAuthorityGrade()); // authorityGrade๋ฅผ UserRole Enum์œผ๋กœ ๋ณ€ํ™˜
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.name())); // ROLE_ ์ ‘๋‘์‚ฌ ๋ถ™์ด๊ธฐ
} catch (IllegalArgumentException e) {
// authorityGrade ๊ฐ’์ด UserRole Enum์— ์ •์˜๋˜์–ด ์žˆ์ง€ ์•Š์€ ๊ฒฝ์šฐ์˜ ์ฒ˜๋ฆฌ
System.err.println("Invalid authorityGrade value: " + employee.getAuthorityGrade());
}
return authorities;
}
// get Password ๋ฉ”์„œ๋“œ
@Override
public String getPassword() {
return employee.getEmployeePw();
}
// get Username ๋ฉ”์„œ๋“œ (์ƒ์„ฑํ•œ User์€ loginId ์‚ฌ์šฉ)
@Override
public String getUsername() {
return employee.getEmployeeId();
}
public String getNickname() {
System.out.println(employee.toString());
System.out.println(employee.getEmployeeName());
return employee.getEmployeeName();
}
public String getUserAuthorityGrade() {
return employee.getAuthorityGrade();
}
// public String getUserAuthorityName() {
// System.out.println(employeedto.getAuthorityName());
// return employeedto.getAuthorityName();
// }
// ๊ณ„์ •์ด ๋งŒ๋ฃŒ ๋˜์—ˆ๋Š”์ง€ (true: ๋งŒ๋ฃŒX)
@Override
public boolean isAccountNonExpired() {
return true;
}
// ๊ณ„์ •์ด ์ž ๊ฒผ๋Š”์ง€ (true: ์ž ๊ธฐ์ง€ ์•Š์Œ)
@Override
public boolean isAccountNonLocked() {
return true;
}
// ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋งŒ๋ฃŒ๋˜์—ˆ๋Š”์ง€ (true: ๋งŒ๋ฃŒX)
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// ๊ณ„์ •์ด ํ™œ์„ฑํ™”(์‚ฌ์šฉ๊ฐ€๋Šฅ)์ธ์ง€ (true: ํ™œ์„ฑํ™”)
@Override
public boolean isEnabled() {
return true;
}
}

 

 

6. ์ธ์ฆ ๊ฐ์ฒด ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ

 

 

์ปจํŠธ๋กค๋Ÿฌ

//ํ˜„์žฌ ๋กœ๊ทธ์ธ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
@GetMapping("/user-info")
public ResponseEntity<?> employeeUserInfo(Authentication authentication) {
if (authentication == null) { // ์ธ์ฆ ๊ฐ์ฒด๊ฐ€ ์—†๋‹ค๋ฉด
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Unauthorized");
}
//์ธ์ฆ๊ฐ์ฒด ์•ˆ์— principal๊ฐ’์„ ์–ป์œผ๋ฉด ์œ ์ €๊ฐ์ฒด๊ฐ€ ๋‚˜์˜ด
EmployeeDetails user = (EmployeeDetails)authentication.getPrincipal();
System.out.println("๋กœ๊ทธ์ธ ์ •๋ณด (์ด๋ฆ„):" + user.getNickname());
System.out.println("๋กœ๊ทธ์ธ ์ •๋ณด (์•„์ด๋””):" + user.getUsername());
System.out.println("๋กœ๊ทธ์ธ ์ •๋ณด (๋น„๋ฐ€๋ฒˆํ˜ธ) :" + user.getPassword());
System.out.println("๋กœ๊ทธ์ธ ์ •๋ณด (๊ถŒํ•œ) :" + user.getUserAuthorityGrade());
// ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๊ถŒํ•œ์„ ๋ฐ˜ํ™˜
Map<String, Object> response = new HashMap<>();
response.put("userName", user.getNickname());
response.put("userId", user.getUsername());
response.put("userPw", user.getPassword());
response.put("userAuthorityGrade", user.getUserAuthorityGrade());
return ResponseEntity.ok(response);
}

 

 

 

 

 

view (ํƒ€์ž„๋ฆฌํ”„)

 

<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
.
.
.
<!-- ์ธ์ฆ ๋˜์—ˆ๋‹ค๋ฉด true, ์•„๋‹ˆ๋ฉด false๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. if๋ฌธ์ฒ˜๋Ÿผ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. -->
<div sec:authorize="isAuthenticated()">
<span>[[${#authentication.principal.getUsername}]]</span><br/>
<span>[[${#authentication.principal.getNickname}]]</span><br/>
<span>[[${#authentication.principal.getUserAuthorityGrade}]]</span><br/>
</div>

 

 

 

view (๋ฆฌ์•กํŠธ)

 

useEffect(() => {
const fetchUserId = async () => {
try {
const response = await axios.get('/employee/user-info');
console.log("์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด" + response.data);
} catch (error) {
console.error("Error fetching user ID:", error);
}
};
fetchUserId();
}, []);

 

 

 

7. ์„ธ์…˜ - ์ฟ ํ‚ค ๋ฐฉ์‹