Springboot Maven ํ๊ฒฝ์์ ๊ฐ๋จํ๊ฒ ์ ์ฉํ๋๋ฒ์ ๋ ธํธํ์์ต๋๋ค.
1. pom.xml ์์ ์๋ ์ฝ๋๋ฅผ ์ถ๊ฐํด์ค๋ค(ํ์๋ security, jpa, mysql, lombok, Mustache ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํจ)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mustache</artifactId>
</dependency>
2. application.yml ํ์ผ์ ์๋ ์ค์ ์ ์ถ๊ฐํด์ค
server:
port: 8080
servlet:
context-path: /
encoding:
charset: UTF-8
enabled: true
force: true
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://๋ณธ์ธip:ํฌํธ/db๋ช
?serverTimezone=Asia/Seoul
username: user๋ช
password: ๋น๋ฐ๋ฒํธ
jpa:
hibernate:
ddl-auto: create #create update none # ์ฒซ ํ๋ก์ ํธ run์ create๋ก ํด์ Table์์ฑ ํ update๋ก ๋ณ๊ฒฝ
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
show-sql: true
3. cofing ํจํค์ง๋ฅผ ๋ง๋ค๊ณ WebMvcConfig ํด๋์ค๋ฅผ ์์ฑํด์ ์๋์ ์ฝ๋๋ฅผ ๋ฃ์ด์ค๋ค.(view๋ฅผ ์ค์ ํ๋ Config class)

@Configuration // ์ด ์๋ฐํ์ผ์ IOC๋ก ๋ฑ๋กํ๊ธฐ ์ํ ์ด๋
ธํ
์ด์
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
MustacheViewResolver resolver = new MustacheViewResolver();
resolver.setCharset("UTF-8");
resolver.setContentType("text/html; charset=UTF-8");
resolver.setPrefix("classpath:/templates/");
resolver.setSuffix(".html");
registry.viewResolver(resolver);
}
}
4. ๊ฐ์ฒด๋ก ์ธ Model ํด๋์ค๋ฅผ ์์ฑํ๋ค(User)
>> @Entity ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ์๊ธฐ๋๋ฌธ์ ์ฌ๊ธฐ๊น์ง ์์ฑํ๊ณ springboot App์ runํ๋ฉด DB์ User ํ ์ด๋ธ์ด ์์ฑ๋๊ณ , ์๋์ ๊ฐ์ฒด๋ค์ด column์ผ๋ก ์์ฑ๋๋ค.(์์ฑ๋ ๊ฒ ํ์ธ ํ, application.yml์์ jpa:hibernate:ddl-auto๋ฅผ create์์ update๋ก ๋ณ๊ฒฝ)

import java.sql.Timestamp;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.hibernate.annotations.CreationTimestamp;
import lombok.Data;
@Entity
@Data
public class User {
@Id //primary key
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private String email;
private String role; //ROLE_USER, ROLE_MANAGER, ROLE_ADMIN
@CreationTimestamp
private Timestamp createDate;
}
5. Jpa๋ฅผ ์ฌ์ฉํ์ฌ์ DB์ ํต์ ํ ๊ฒ์ด๊ธฐ๋๋ฌธ์ JpaRepository๋ฅผ ์์๋ฐ์ Repository๋ฅผ ์์ฑํ๋ค.(Mapper์ญํ ์ ํจ)

import org.springframework.data.jpa.repository.JpaRepository;
import com.cos.security1.model.User;
// CRUD ํจ์๋ฅผ JpaRepository๊ฐ ๋ค๊ณ ์์.
// @Repository๋ผ๋ ์ด๋
ธํ
์ด์
์ด ์์ด๋ IoC๊ฐ ๋จ. ์ด์ ๋ JpaRepository๋ฅผ ์์ํ๊ธฐ ๋๋ฌธ์(์๋ ๋น ๋ฑ๋ก๋จ)
public interface UserRepository extends JpaRepository<User, Integer> {
// findBy๊ท์น -> Username๋ฌธ๋ฒ
// select * from user where username =1?
public User findByUsername(String username); //Jpa Query methods
// select * from user where email = ?
// public User findByEmail();
}
6. ๋ก๊ทธ์ธ, ํ์๊ฐ์ ํ๋ฉด๋จ์ ๋ง๋ ๋ค(๋งค์ฐ ๊ฐ๋จํ๊ฒ)
index.html(์ด๊ธฐํ๋ฉด)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>์ธ๋ฑ์คํ์ด์ง</title>
</head>
<body>
<h1>์ธ๋ฑ์คํ์ด์ง์
๋๋ค.</h1>
</body>
</html>
loginForm.html(๋ก๊ทธ์ธ ํ๋ฉด)
> input name์ userRepository์ ๊ฐ์ฒด๋ช (ex. username)๊ณผ ๋ง์ถฐ์ผํจ
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>๋ก๊ทธ์ธ ํ์ด์ง</title>
</head>
<body>
<h1>๋ก๊ทธ์ธ ํ์ด์ง</h1>
<hr/>
<form action="login" method="POST">
<input type="text" name="username" placeholder="Username"/><br/>
<input type="password" name="password" placeholder="Password"/><br/>
<button>๋ก๊ทธ์ธ</button>
</form>
<a href="/joinForm">ํ์๊ฐ์
์ ์์ง ํ์ง ์์ผ์
จ๋์?</a>
</body>
</html>
joinForm.html(ํ์๊ฐ์ ํ๋ฉด)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ํ์๊ฐ์
ํ์ด์ง</title>
</head>
<body>
<h1>ํ์๊ฐ์
ํ์ด์ง</h1>
<hr/>
<form action="/join" method="POST">
<input type="text" name="username" placeholder="Username"/><br/>
<input type="password" name="password" placeholder="Password"/><br/>
<input type="email" name="email" placeholder="Email"/><br/>
<button>ํ์๊ฐ์
</button>
</form>
</body>
</html>
7. Controller๋ฅผ ํ๋ ๋ง๋ค์ด์ ์๋์ ์ฝ๋๋ฅผ ์ถ๊ฐํด์ค๋ค(๋ก๊ทธ์ธ, ํ์๊ฐ์ ํ์ด์ง ๋นผ๊ณ ๋ html ํ์ด์ง๋ ๋ง๋ค์ง ์์์ @ResponseBody๋ก Return String๊ฐ์ Json๋ฐ์ดํฐ๋ก๋ง ์ถ๋ ฅ๋๊ฒ ํ์๋ค)
@Controller
public class IndexController {
@Autowired
private UserRepository userRepository;
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
public IndexController(BCryptPasswordEncoder bCryptPasswordEncoder) {
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
@GetMapping({"","/"})
public String index() {
// ๋จธ์คํ
์น ๊ธฐ๋ณธํด๋ src/main/resources/
// ๋ทฐ๋ฆฌ์กธ๋ฒ ์ค์ : templates (prefix), .mustache(suffix) ์๋ต๊ฐ๋ฅ!! -> pom.xml์ mustache ์์กด์ฑ ์ฃผ์
ํ๊ธฐ ๋๋ฌธ์
return "index"; // src/main/resources/tmplates/index.mustache
}
@GetMapping("/user")
public @ResponseBody String user() {
return "user";
}
@GetMapping("/admin")
public @ResponseBody String admin() {
return "admin";
}
@GetMapping("/manager")
public @ResponseBody String manager() {
return "manager";
}
// ์คํ๋ง์ํ๋ฆฌํฐ๊ฐ ํด๋น์ฃผ์๋ฅผ ๋์์ฑ - SecurityConfig ํ์ผ ์์ฑ ํ ์๋์ํจ -> formLogin๋ถ๋ถ ์ถ๊ฐํด์คฌ์
@GetMapping("/loginForm")
public String loginForm() {
return "loginForm";
}
@GetMapping("/joinForm")
public String joinForm() {
return "joinForm";
}
@PostMapping("/join")
public String join(User user) {
user.setRole("ROLE_USER"); //ํ์๊ฐ์
ํ ๋๋ง๋ค ROLE_MANAGER, ROLE_ADMIN์ผ๋ก ์์ ํ๋ ์์ผ๋ก ํ
์คํธ
String rawPassword = user.getPassword();
String encPassword = bCryptPasswordEncoder.encode(rawPassword);
user.setPassword(encPassword);
userRepository.save(user);
return "redirect:/loginForm";
}
๊ทธ๋ฆฌ๊ณ BCryptPasswordEncoder๋ฅผ spring bean์ ๋ฑ๋กํ๊ณ , ์ฃผ์ ๋ฐ๊ธฐ ์ํด ํด๋์ค๋ฅผ ํ๋ ์์ฑํด์ค๋ค.
@Component
public class CustomBCryptPasswordEncoder extends BCryptPasswordEncoder {
}
8. ์ฌ๊ธฐ๊น์ง ์งํํ์ผ๋ฉด, ๋๋ง์ securityConfig(์ํ๋ฆฌํฐ ์ค์ ๊ด๋ จ) ํด๋์ค๋ฅผ ์์ฑํด์ค๋ค.
@Configuration
@EnableWebSecurity // ์คํ๋ง ์ํ๋ฆฌํฐ ํํฐ๊ฐ ์คํ๋ง ํํฐ์ฒด์ธ์ ๋ฑ๋ก์ด ๋จ
public class SecurityConfig {
// @Bean์ด๋
ธํ
์ด์
์ ํด๋น ๋ฉ์๋์ ๋ฆฌํด๋๋ ์ค๋ธ์ ํธ๋ฅผ IoC๋ก ๋ฑ๋กํด์ค๋ค.
// security ์ํธํ ๊ด๋ จ ๋ฉ์๋
@Bean
public BCryptPasswordEncoder encodePwd() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/user/**").authenticated() // ์ธ์ฆ๋ง ๋๋ฉด ๋ค์ด๊ฐ ์ ์๋ ์ฃผ์
.antMatchers("/manager/**").access("hasAnyRole('ROLE_MANAGER','ROLE_ADMIN')")
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/loginForm")
.loginProcessingUrl("/login") // /login ์ฃผ์๊ฐ ํธ์ถ์ด ๋๋ฉด ์ํ๋ฆฌํฐ๊ฐ ๋์์ฑ์ ๋์ ๋ก๊ทธ์ธ์ ์งํํด์ค
.defaultSuccessUrl("/");
return http.build();
}
}
9. ์ถ๊ฐ์ฌํญ!! PrincipalDetails, PrincipalDetailsService ํด๋์ค๋ฅผ ๋ง๋ค์ด์ค์ผํ๋ค.
์ด์ ๋ ์ํ๋ฆฌํฐ๊ฐ /login ์ฃผ์ ์์ฒญ์ด ์ค๋ฉด ๋์์ฑ์ ๋ก๊ทธ์ธ์ ์งํ์ํค๋๋ฐ,
๋ก๊ทธ์ธ ์งํ์ด ์๋ฃ๊ฐ ๋๋ฉด ์ํ๋ฆฌํฐ session์ ๋ง๋ค์ด์ค๋ค. (Security ContenxtHolder๋ผ๋ ํค๊ฐ์ ์ธ์ ์ ๋ณด๋ฅผ ์ ์ฅ)
์ํ๋ฆฌํฐ๊ฐ ๊ฐ์ง๊ณ ์๋ ์ธ์ ์ ๋ค์ด๊ฐ ์ ์๋ ์ค๋ธ์ ํธ๋ ์ ํด์ ธ์๋๋ฐ, ๊ทธ ์ค๋ธ์ ํธ๊ฐ Authentication ํ์ ๊ฐ์ฒด์ด๋ค!
๊ทธ Authentication ์์๋ User์ ๋ณด๊ฐ ์์ด์ผ ๋๋ค.
User์ค๋ธ์ ํธ์ ํ์ ์ UserDetails ํ์ ๊ฐ์ฒด์ด๊ณ , PrincipalDetailsํด๋์ค์์ UserDetails๋ฅผ implements ํ์ฌ Authentication ๊ฐ์ฒด์ ๋ฃ์ด์ผํ๋ค.
// ์ํ๋ฆฌํฐ๊ฐ /login์ ๋์์ฑ์ ๋ก๊ทธ์ธ์ ์งํ์ํจ๋ค.
// ๋ก๊ทธ์ธ์ ์งํ์ด ์๋ฃ๊ฐ ๋๋ฉด ์ํ๋ฆฌํฐ session์ ๋ง๋ค์ด์ค๋๋ค. (Security ContextHolder์ ์ธ์
์ ๋ณด ์ ์ฅ)
// ์ค๋ธ์ ํธ ํ์
=> Authentication ํ์
๊ฐ์ฒด
// Authentication ์์ User ์ ๋ณด๊ฐ ์์ด์ผ ๋จ.
// User์ค๋ธ์ ํธํ์
=> UserDetails ํ์
๊ฐ์ฒด
// Security Session => Authentication => UserDetails(PrincipalDetails)
@Data
public class PrincipalDetails implements UserDetails {
private User user; //์ฝคํฌ์ง์
private Map<String, Object> attributes;
public PrincipalDetails(User user) {
this.user = user;
}
public PrincipalDetails(User user, Map<String, Object> attributes) {
this.user = user;
this.attributes = attributes;
}
// ํด๋น User์ ๊ถํ์ ๋ฆฌํดํ๋ ๊ณณ!!
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collect = new ArrayList<>();
collect.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole();
}
});
return collect;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
// 1๋
๋์ ํ์์ด ๋ก๊ทธ์ธ์ ์ํ๋ฉด!! ํด๋จผ ๊ณ์ ์ผ๋ก ํ๊ธฐ๋ก ํ ๊ฒฝ์ฐ์
// ํ์ฌ์๊ฐ - ๋ก๊ธด์๊ฐ => 1๋
์ ์ด๊ณผํ๋ฉด return false;
return true;
}
@Override
public String getName() {
// TODO Auto-generated method stub
return null;
}
}
๋ ์ฌ๊ธฐ์, Authentication ๊ฐ์ฒด๋ฅผ ๋ง๋๋ ํด๋์ค๊ฐ ํ์ํ๋ฐ, ์ด๊ฒ์ด PrincipalDetailsService ํด๋์ค์ด๋ค.
// ์ํ๋ฆฌํฐ ์ค์ ์์ loginProcessingUrl("/login");
// /login ์์ฒญ์ด ์ค๋ฉด ์๋์ผ๋ก UserDetailsService ํ์
์ผ๋ก IoC๋์ด ์๋ loadUserByUsername ํจ์๊ฐ ์คํ
@Service
public class PrincipalDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
// ์ํ๋ฆฌํฐ session(๋ด๋ถ Authentication(๋ด๋ถ UserDetails))
// ํจ์ ์ข
๋ฃ์ @AuthenticationPrincipal ์ด๋
ธํ
์ด์
์ด ๋ง๋ค์ด์ง๋ค.
@Override // ์ค์!! html์ input name์ username์ผ๋ก ํด์ค์ผํจ!!!!
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User userEntity = userRepository.findByUsername(username);
if(userEntity != null) {
return new PrincipalDetails(userEntity);
}
return null;
}
}
์ฌ๊ธฐ๊น์ง ์ค์ ํ์ผ๋ฉด, ๋ค์ springboot-app์ runํด์ ํ์๊ฐ์ ํ ํ ์คํธ ์งํ ํ๋ฉด๋๋ค. ์ฒซ ํ์๊ฐ์ ์ ๊ถํ์ด ROLE_USER๊ธฐ ๋๋ฌธ์, userํ์ด์ง๋ง ์ ์๊ฐ๋ฅํ๊ณ , /logout url ํธ์ถ์ security๊ฐ ์๋์ผ๋ก ๋ก๊ทธ์์ ์์ผ์ค๋ค.
**๋ค์ ํฌ์คํ ์์๋ OAuth2๋ฅผ ํตํ ์์ ๋ก๊ทธ์ธ(๊ตฌ๊ธ)์ ์์๋ณด๊ฒ ๋ค!