1.1 환경 설정
- 디펜던시에 시큐리티를 추가하면 자동으로 localhost:8080/login, logout 페이지가 자동으로 생성된다.
- https://github.com/codingspecialist/Sringboot-Security-Basic-V1
- 쿼리를 복사 sql 적용
- dependency
- mustache: jsp 대신에 머스태치를 이용 가능하다.
- yml 파일
- mvc: view: ... 부분은 삭제한다.
- 디펜던시에 mustache를 등록하면 디폴트로 mvc에 경로가 잡힌다. 따라서 삭제 가능
<hide/>
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://localhost:3306/security?serverTimezone=Asia/Seoul
username: cos
password: cos1234
mvc:
view:
prefix: /templates/
suffix: .mustache
jpa:
hibernate:
ddl-auto: update #create update none
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
show-sql: true
- IndexController
- @컨트롤러 파일에 @ResponseBody를 붙이고 String을 반환하면 @RestController 처럼 동작한다.
- 따라서 join 페이지에 접속하면 "join"이라고 뜬다.
- @컨트롤러 파일에 @ResponseBody를 붙이고 String을 반환하면 @RestController 처럼 동작한다.
<hide/>
@Controller
public class IndexController {
@GetMapping("/")
public String index(){
return "index";
}
@GetMapping("/user")
public @ResponseBody String user(){
return "user";
}
@GetMapping("/admin")
public @ResponseBody String admin(){
return "admin";
}
@GetMapping("/manager")
public @ResponseBody String manager(){
return "manager";
}
@GetMapping("/login")
public @ResponseBody String login(){
return "login";
}
@GetMapping("/join")
public @ResponseBody String join(){
return "join";
}
@GetMapping("/joinProc")
public @ResponseBody String joinProc(){
return "회원가입 완료!";
}
}
- index
- preffix는 html로 간다.
- mustache로 하는 경우?
- mustache는 스프링 부트에서 공식으로 지원하는 템플릿 엔진이다. Java를 포함한 여러 가지 언어를 지원한다.
- 다른 서버 템플릿 엔진은 JSP, Velocity, Thymeleaf 등이 있다.
- 로직 코드가 없어서 view 역할에 충실하다. (서버와 클라이언트의 코드가 섞이는 문제가있는 JSP와 다르다.)
- Webconfig
- Webconfig는 WebMvcConfigurer 인터페이스를 implement
- WebMvcConfigurer을 이용하면 ViewResolver를 커스텀할 수 있다.
- 메소드 "configureViewResolvers"를 오버라이드해서 내가 만든 뷰에 대해 설정한다.
- "classpath:"는 내 프로젝트 경로
- setSuffix("html"): html로 만들어도 mustache라고 인식한다.
- Webconfig는 WebMvcConfigurer 인터페이스를 implement
1.2 시큐리티 설정
- securityConfig
- @EnableWebSecurity를 붙이면 스프링 시큐리티 필터가 스프링 필터 체인에 등록된다.
- 웹 보안을 활성화 시킨다.
- configure 오버라이드
- csrf(사이트 간 위조 요청)는 비활성화한다.
- "manager"로 시작하는 페이지는 admin, manager만 허용하고 "admin" 페이지는 admin만 허용한다.
- "/user"로 들어로면 인증이 필요한 페이지다.
- 403 에러는 접근 권한이 없는 경우에 나는 에러이다.
- @EnableWebSecurity를 붙이면 스프링 시큐리티 필터가 스프링 필터 체인에 등록된다.
<hide/>
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/user/**").authenticated()
.antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll(); // 다른 요청은 전부 허용한다.
}
}
- 위와 같이 설정하고 나면 로그인 페이지에 접속했을 때 아래와 같이 뜬다.
- 아까 처럼 스프링 시큐리티가 이 페이지를 낚아채지 않아서 컨트롤러에서 매핑한대로 페이지가 뜨는 것이다.
Ex) 권한 없는 페이지에 접속한 경우 login 페이지로 이동시키는 방법
- configure() 메서드의 뒤쪽에 아래 코드를 추가한다.
- 추가하고 나면 권한이 없는 페이지 user, admin, manager 같은 페이지에 접속할 때 login 페이지로 이동한다.
.formLogin()
.loginPage("/login");
- 추가 후
<hide/>
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/user/**").authenticated()
.antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll() // 다른 요청은 전부 허용한다.
.and()
.formLogin()
.loginPage("/login");
}
1.3 시큐리티 회원가입
- login()
<hide/>
@GetMapping("/login")
public String login(){
return "loginForm";
}
<hide/>
<!doctype html>
<html lang="en">
<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>로그인 페이지</title>
</head>
<body>
<h1>로그인 페이지입니다.</h1>
<hr/>
<form>
<input type="text" name="username" placeholder="Username"/></br>
<input type="text" name="password" placeholder="Password"/></br>
<button>로그인</button>
</form>
</body>
</html>
Note) 실행 결과
- DESC USER
Ex) 회원 가입
<hide/>
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/user/**").authenticated()
.antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll() // 다른 요청은 전부 허용한다.
.and()
.formLogin()
.loginPage("/loginForm");
}
Note) 실행 결과 - localhost: 8080/manager
- 로그인 창이 나온다.
- 로그인에 하이퍼링크 추가
<a href="/joinForm">회원 가입을 아직 하지 않으셨나요?</a>
- 회원 가입 창
- 가입 폼을 전송할 거니까 method ="post"
- "/join"으로 이동한다.
- User
- @CreationTimeStamp: 객체가 생성될 때 자동으로 시간이 채워진다.
<hide/>
@Entity
@Setter
@Getter
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private String email;
private String role;
@CreationTimestamp
private Timestamp createDate;
}
- 컨트롤러
- 매개변수로 User을 넣으니까 입력한 회원 가입 창에 입력한 정보가 자동으로 저장된다?
- view 페이지에서 사용자가 입력하는 값은 @PostMapping이 붙은 메서드의 매개변수로 통해 전달된다.
- 아래의 user에 해당한다.
<hide/>
@GetMapping("/joinForm")
public String joinForm(){
return "joinForm";
}
// 가입 시킨다.
@PostMapping("/join")
public @ResponseBody String join(User user){
System.out.println(user);
return "join";
}
Note) 실행 결과
- 다음과 같이 출력된다.
- com.example.security.model.User@2bfdbb41
- repository
- CRUD 함수를 JPA repo가 가지고 있다.
- JpaRepo를 상속받았기 때문에 UserRepo에 @Repository를 붙이지 않아도 자동으로 IoC가 적용된다.
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
}
- 시큐리티로 로그인하려면 패스워드가 암호화 되어 있어야한다. => BCryptPasswordEncoder을 반환하는 encodePwd() 빈을 만든다.
- 보안 설정 파일에 추가한다.
- @Bean을 추가하면 해당 메서드의 반환 객체를 IoC로 등록한다. 이 빈을 어디서든 쓸 수 있다.
- BCryptPasswordEncoder는 시큐리티에서 제공하는 클래스로 비밀번호를 암호화(해시)하는데 이용한다.
@Bean
public BCryptPasswordEncoder encodePwd(){
return new BCryptPasswordEncoder();
}
- 컨트롤러
- join()에 암호화를 적용해서 다음과 같이 바꾼다.
<hide/>
@PostMapping("/join")
public String join(User user){
System.out.println(user);
user.setRole("ROLE_USER");
String rawPassword = user.getPassword();
String encPassword = bCryptPasswordEncoder.encode(rawPassword);
user.setPassword(encPassword);
userRepository.save(user);
return "redirect:/loginForm";
}
- 회원 가입하면 아래와 같이 정보가 잘 들어간다.
1.4 시큐리티 로그인
1.5 시큐리티 권한 처리
확인 사항
- implement와 extend 차이는?
- extend를 통해 abstract class를 상속하면 부모 클래스의 멤버 변수까지 이용 가능하다. 부모 클래스의 특징을 연장해서 사용한다.
- 자식 객체는 오버라이딩할 필요없이 부모의 변수, 메서드를 그대로 사용 가능
- implement는 부모의 특징을 도구로 사용해서 새로운 특징을 만들어 사용한다.
- 부모 객체(인터페이스)는 메서드 선언만 할 수 있고 구현을 자식 객체에서 구현이 가능하다.
- 부모 객체에 선언된 메서드를 반드시 자식 객체에서 구현해야한다.
- extend를 통해 abstract class를 상속하면 부모 클래스의 멤버 변수까지 이용 가능하다. 부모 클래스의 특징을 연장해서 사용한다.
- prefix(접두사), suffix(접미사)
'Spring Framework > [인프런] Spring Security & OAuth 2.0 & JWT' 카테고리의 다른 글
Chapter 03. 스프링 시큐리티 웹 보안 이해 (0) | 2022.12.05 |
---|---|
Chapter 02. 스프링 시큐리티 OAuth 2.0 (2) | 2022.10.14 |