์ํ๋ฆฌํฐ ๋ฒ์ ๋ณ ํน์ฑ
: ์คํ๋ง์ ๋ฒ์ ์ ๋ฐ๋ผ ๊ตฌํ ๋ฐฉ์์ด ๋ณ๊ฒฝ๋๋๋ฐ ์ํ๋ฆฌํฐ์ ๊ฒฝ์ฐ ํนํ ์ธ๋ถ ๋ฒ์ ๋ณ๋ก ๊ตฌํ ๋ฐฉ๋ฒ์ด ๋ง์ด ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ ๋ฒ์ ๋ง๋ค ๊ตฌํ ํน์ง์ ํ์ธํด์ผ ํ๋ค
์๋ก์ด ๋ฒ์ ์ด ์ถ์๋ ๋๋ง๋ค GitHub์ Spring ๋ฆฌํฌ์งํ ๋ฆฌ์์ Security์ Release ํญ๋ชฉ์ ํตํด ๋ณ๊ฒฝ๋ ์ ์ ํ์ธํ ์ ์๋ค
Releases · spring-projects/spring-security (github.com)
์ฃผ์ ๋ฒ์ ๋ณ ๊ตฌํ
- ์คํ๋ง ๋ถํธ 2.X.X ~ 2.6.X (์คํ๋ง 5.X.X ~ 5.6.X)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").authenticated()
.anyRequest().permitAll();
}
}
- ์คํ๋ง ๋ถํธ 2.7.X ~ 3,0.X (์คํ๋ง 5.7.X M2 ~ 6.0.X)
public class SpringSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests()
.requestMatchers("/admin").hasRole("ADMIN")
.anyRequest().authenticated();
return http.build();
}
}
- ์คํ๋ง ๋ถํธ 3.1.X ~ (์คํ๋ง 6.1.X ~)
public class SpringSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/login", "/join").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}
** 3.1.X ๋ฒ์ ๋ถํฐ ๋๋คํ์ ํํ ํ์...
Config ์ค์ ํ ๋ก๊ทธ์ธ ํ์ด์ง
์คํ๋ง ์ํ๋ฆฌํฐ Config ํด๋์ค ์ค์ ํ ํน์ ๊ฒฝ๋ก์ ๋ํ ์ ๊ทผ ๊ถํ์ด ์๋ ๊ฒฝ์ฐ ์๋์ผ๋ก ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํ ๋์ง ์๊ณ ์ค๋ฅ ํ์ด์ง๊ฐ ๋ฐ์....
์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Config ํด๋์ค๋ฅผ ์ค์ ํ๋ฉด ๋ก๊ทธ์ธ ํ์ด์ง ์ค์ ๋ ์งํํด์ผ ํ๋ค.
์ปค์คํ ๋ก๊ทธ์ธ ํ์ด์ง : mustache
- login.mustache
- ๋ก๊ทธ์ธ : ์์ด๋, ๋น๋ฐ๋ฒํธ POST ์์ฒญ ๊ฒฝ๋ก
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
login page
<hr>
<form action="/loginProc" method="post" name="loginForm">
<input id="username" type="text" name="username" placeholder="id"/>
<input id="password" type="password" name="password" placeholder="password"/>
<input type="submit" value="login"/>
</form>
</body>
</html>
LoginController
package com.example.testsecurity.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
@GetMapping("/login")
public String loginP() {
return "login";
}
}
Security Config ๋ก๊ทธ์ธ ํ์ด์ง ์ค์ ๋ฐ ๋ก๊ทธ์ธ ๊ฒฝ๋ก
- admin ํ์ด์ง๋ก ๊ฐ๋ ๋ฐ๋ก ๋ก๊ทธ์ธ ํ์ด์ง๋ฅผ ๋์ธ ์ ์๋๋ก ํ๊ธฐ ์ํ ์ค์ ..
package com.example.testsecurity.config;
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.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/", "/login", "/loginProc").permitAll()
.requestMatchers("/admin").hasRole("ADMIN")
.requestMatchers("/my/**").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated()
);
// ๋ก๊ทธ์ธ ํ์ด์ง์ ๋ํ ์ ๊ทผ ์ถ๊ฐํ๊ธฐ
http
.formLogin((auth) -> auth.loginPage("/login")
.loginProcessingUrl("/loginProc")
.permitAll()
);
// ํ ํฐ์ ๋ณด๋ด์ง ์์ผ๋ฉด ๋ก๊ทธ์ธ์ด ์งํ์ด ์๋๊ธฐ ๋๋ฌธ์ ์ถํ์ ํผ๋ค๊ณ ํ์ฌ...
http
.csrf((auth) -> auth.disable());
return http.build();
}
}
๊ทผ๋ฐ mustache ์ ์??
=> https://velog.io/@qowl880/SpringBoot-Mustache
์ํ๋ฆฌํฐ ์ํธํ
์คํ๋ง ์ํ๋ฆฌํฐ๋ ์ฌ์ฉ์ ์ธ์ฆ(๋ก๊ทธ์ธ) ์ ๋น๋ฐ๋ฒํธ์ ๋ํด ๋จ๋ฐฉํฅ ํด์ ์ํธํ๋ฅผ ์งํํ์ฌ ์ ์ฅ๋์ด ์๋ ๋น๋ฐ๋ฒํธ์ ๋์กฐ๋๋ค.
๋ฐ๋ผ์ ํ์๊ฐ์ ์ ๋น๋ฐ๋ฒํธ ํญ๋ชฉ์ ๋ํด์ ์ํธํ๋ฅผ ์งํํด์ผ ํ๋ค
์คํ๋ง ์ํ๋ฆฌํฐ๋ ์ํธํ๋ฅผ ์ํด BCrypt Password Encorder๋ฅผ ์ ๊ณตํ๊ณ ๊ถ์ฅํ๋ค. ๋ฐ๋ผ์ ํด๋น ํด๋์ค๋ฅผ return ํ๋ ๋ฉ์๋๋ฅผ ๋ง๋ค์ด @Bean ์ผ๋ก ๋ฑ๋กํ์ฌ ์ฌ์ฉํ๋ฉด ๋๋ค
๋จ๋ฐฉํฅ ํด์ ์ํธํ
- ์๋ฐฉํฅ(๋์นญํค, ๋น๋์นญํค)
- ๋จ๋ฐฉํฅ(ํค์)
Security Config ์ํธํ Bean ์ถ๊ฐ
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
๋ฐ์ดํฐ๋ฒ ์ด์ค ์ข ๋ฅ์ ORM
ํ์ ์ ๋ณด๋ฅผ ์ ์ฅํ๊ธฐ ์ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ MYSQL ์์ง์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํ๋ค.
๊ทธ๋ฆฌ๊ณ ์ ๊ทผ์ Spring Date JPA ๋ฅผ ์ฌ์ฉํ๋ค
๋ฐ์ดํฐ๋ฒ ์ด์ค ์์กด์ฑ
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.mysql:mysql-connector-j'
}
๋ณ์ ์ค์
- application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://์์ดํผ:3306/๋ฐ์ดํฐ๋ฒ ์ด์ค?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true
spring.datasource.username=์์ด๋
spring.datasource.password=๋น๋ฐ๋ฒํธ
ํ์๊ฐ์ ๋ก์ง
ํ์์ ๋ณด๋ฅผ ํตํด ์ธ์ฆ ์ธ๊ฐ ์์ ์ ์งํํ๊ธฐ ๋๋ฌธ์ ์ฌ์ฉ์๋ก๋ถํฐ ํ์ ๊ฐ์ ์ ์งํํ ๋ค ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ์ ์ ๋ณด๋ฅผ ์ ์ฅํด์ผ ํ๋ค
ํ์๊ฐ์ ํ์ด์ง : mustache
- join.mustache
<form action="/joinProc" method="post" name="joinForm">
<input type="text" name="username" placeholder="Username"/>
<input type="password" name="password" placeholder="Password"/>
<input type="submit" value="Join"/>
</form>
- JoinDTO
package com.example.testsecurity.dto;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class JoinDTO {
private String username;
private String password;
}
- JoinController
package com.example.testsecurity.controller;
import com.example.testsecurity.dto.JoinDTO;
import com.example.testsecurity.service.JoinService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class JoinController {
@Autowired
private JoinService joinService;
@GetMapping("/join")
public String joinP() {
return "join";
}
@PostMapping("/joinProc")
public String joinProcess(JoinDTO joinDTO) {
System.out.println(joinDTO.getUsername());
joinService.joinProcess(joinDTO);
return "redirect:/login";
}
}
- JoinService
package com.example.testsecurity.service;
import com.example.testsecurity.dto.JoinDTO;
import com.example.testsecurity.entity.UserEntity;
import com.example.testsecurity.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class JoinService {
@Autowired
private UserRepository userRepository;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
public void joinProcess(JoinDTO joinDTO) {
//db์ ์ด๋ฏธ ๋์ผํ username์ ๊ฐ์ง ํ์์ด ์กด์ฌํ๋์ง?
UserEntity data = new UserEntity();
data.setUsername(joinDTO.getUsername());
data.setPassword(bCryptPasswordEncoder.encode(joinDTO.getPassword()));
data.setRole("ROLE_USER");
userRepository.save(data);
}
}
-UserEntity
package com.example.testsecurity.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;
@Entity
@Setter
@Getter
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private String role;
}
Table ์์ฑ : Hibernate ddl ์ค์
- application properties
spring.jpa.hibernate.ddl-auto=none // none์ผ๋ก ๋ฐ๊พธ๋ฉด ์ด๋ฏธ ๋ง๋ค์ด์ง ํ
์ด๋ธ์ ์์ ํ ์ ์์ง๋กฑ
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
- UserRepository
package com.example.testsecurity.repository;
import com.example.testsecurity.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<UserEntity, Integer> {
}
- SecurityConfig ์ ๊ทผ ๊ถํ
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/", "/login", "/loginProc", "/join", "/joinProc").permitAll()
.requestMatchers("/admin").hasRole("ADMIN")
.requestMatchers("/my/**").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated()
);