- target: 스프링부트 시큐리티 & JWT 강의 1 ~ 5강 (섹션 0. 스프링 시큐리티 기본)
- method:
*시작 전 섹션0~섹션1 최신버전 업데이트 github 주소
(강사님 github 주소)
https://github.com/codingspecialist
(스프링 시큐리티 기본 V1)
https://github.com/codingspecialist/Sringboot-Security-Basic-V1
(구버전)
스프링부트 2.3.2
자바 1.8
https://github.com/codingspecialist/Springboot-Security-OAuth2.0-V2
(신버전)
스프링부트 2.5.7
자바 11
https://github.com/codingspecialist/-Springboot-Security-OAuth2.0-V3
* Section 0. 스프링 시큐리티 기본
1. 스프링부트 시큐리티 1강 - 환경설정
1.1) 실습을 위한 DB 계정 생성, 권한 설정 및 security DB 생성
1.2) 실습을 위한 Springboot 프로젝트 생성
1.3) application.yml에 기본적인 설정 - 서버포트 / 인코딩 / datasource / viewResolver / jpa
※ application.yml에서 jpa.hibernate.ddl-auto는 create로 하면 서버 기동 시, 스키마를 새로 생성
※ 최초 스키마 생성시에만 create로 생성
1.4) Controller 생성 (src/main/java/com/cos/security1/controller/IndexController.java)
※ 이하 {baseDir} = src/main/java/com/cos/security1
1.5) mustache(View템플릿) 기본 폴더는 (src/main/resources/)
1.6) viewResolver는 application.yml에서 prefix / suffix가 설정이 가능 (pom.xml에 mustache를 추가했기 때문에, suffix는 기본이 .mustache)
1.7) {baseDir}/config/WebMvcConfig.java에서 MustacheViewResolver를 통해 override 가능
// SecurityConfig.java
package com.cos.security1.config;
import org.springframework.boot.web.servlet.view.MustacheViewResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// TODO Auto-generated method stub
// WebMvcConfigurer.super.configureViewResolvers(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);
}
}
1.8) 서버를 실행하고 localhost:8080 으로 접속하여 로그인 (아이디 : user // 패스워드 : 서버 실행 시, 아래와 같이 나옴)
1.9) localhost:8080/logout을 통해 로그아웃
2. 스프링부트 시큐리티 2강 - 시큐리티 설정
2.1) IndexController에 "/user", "/admin", "/manager", "/loginForm", "/joinForm", "/join" 매핑 매소드 생성
// IndexController.java
package com.cos.security1.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.cos.security1.model.User;
import com.cos.security1.repository.UserRepository;
@Controller // View를 리턴하겠다
public class IndexController {
// localhost:8080/
// localhost:8080
@GetMapping({"", "/"})
public String index() {
// mustache default folder : src/main/resources/
// ViewResolver Setting : templates (prefix), .mustache (suffix)
// pom.xml에 dependency로 mustache를 추가한 경우, 생략 가능 (application.yml에 선언해주지 않아도 됨)
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";
}
// spring security에서 낚아챔!! - SecurityConfig 파일 생성 후, 작동 안함
@GetMapping("/loginForm")
public String loginForm() {
return "loginForm";
}
@GetMapping("/joinForm")
public String joinForm() {
return "joinForm";
}
@PostMapping("/join")
public String join(User user) {
return "join";
}
}
2.2) WebSecurityConfigurerAdapter를 상속한 SecurityConfig.java 생성
※ user, manager, admin에 대해 권한을 적용하고, 이외의 URL은 접근허용(permitAll())을 적용
★★스프링버전이 올라가면서 WebSecurityConfigurerAdapter가 deprecated되어 아래와 같이 변경 필요★★
// SecurityConfig.java
package com.cos.security1.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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity // Spring Security Filter가 Spring Filter chain에 등록됨
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeHttpRequests()
.requestMatchers("/user/**").authenticated()
.requestMatchers("/manager/**").hasAnyRole("ROLE_ADMIN", "ROLE_MANAGER")
.requestMatchers("/admin/**").hasRole("ROLE_ADMIN")
.anyRequest().permitAll();
return http.build();
}
}
// 예전 버전 spring security
// 현재는 WebSecurityConfigurerAdapter가 deprecated되어 사용 불가
// @Configuration
// @EnableWebSecurity // Spring Security Filter가 Spring Filter chain에 등록됨
// 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();
// }
// }
2.3) SecurityFilterChain을 적용하면서 기존에 "/login" 접근 시, 스프링 시큐리티에서 가로채서 로그인 페이지로 redirect 되던 부분이 미동작하게 되고, 자체 제작한 login 페이지로 접근이 가능하게 됨
2.4) 권한 적용으로 인하여 "/user", "/admin", "/manager"로 접근 시, 아래와 같은 403 에러 확인 가능
2.5) 아래와 같이 로그인하지 않은 경우, 로그인 페이지로 이동하도록 filterChain 추가
// SecurityConfig.java
package com.cos.security1.config;
...
(중략)
...
@Configuration
@EnableWebSecurity // Spring Security Filter가 Spring Filter chain에 등록됨
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
...
(중략)
...
.anyRequest().permitAll()
/************** 아래부분 추가 **************/
.and()
.formLogin()
.loginPage("/loginForm");
return http.build();
}
}
3. 스프링 시큐리티 3강 - 시큐리티 회원가입
3.1) src/main/resources/templates/loginForm.html 파일 생성
<!-- loginForm.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 페이지</title>
</head>
<body>
<h1>로그인 페이지</h1>
<hr/>
<form>
<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>
3.2) {baseDir}/model/User.java 생성 (Model을 통해 테이블 생성)
// User.java
package com.cos.security1.model;
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_ADMIN
@CreationTimestamp
private Timestamp createDate;
}
3.3) {baseDir}/repository/UserRepository.java 생성
// UserRepository.java
package com.cos.security1.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.cos.security1.model.User;
// CRUD 함수를 JpaRepository가 들고 있음
// JpaRepository를 상속했으므로 @Repository가 없어도 IoC 가능
public interface UserRepository extends JpaRepository<User, Integer> {
}
3.4) 회원가입 페이지 src/main/resources/templates/joinForm.html을 templates 디렉토리 안에 생성
<!-- 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="text" name="email" placeholder="Email"/><br/>
<button>회원가입</button>
</form>
</body>
</html>
3.5) 비밀번호를 암호화하여 저장하기 위한 {baseDir}/config/SecurityConfig.java에 BCryptPasswordEncoder 객체를 리턴하는 encodePwd() 메소드 생성
// SecurityConfig.java
package com.cos.security1.config;
...
(중략)
...
@Configuration
@EnableWebSecurity // Spring Security Filter가 Spring Filter chain에 등록됨
public class SecurityConfig {
/*************** 이 부분이 추가됨 ***************/
// 해당 메서드의 리턴되는 오브젝트를 IoC로 등록해준다.
@Bean
public BCryptPasswordEncoder encodePwd() {
return new BCryptPasswordEncoder();
}
/*********************************************/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
...
}
}
※ @Bean을 적용하였기에, 리턴되는 오브젝트를 IoC로 등록됨
3.6) IndexController에 "/join" 메소드를 아래와 같이 변경
// IndexController.java
@Autowired
private UserRepository userRepository;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
...
(중략)
...
@GetMapping("/joinForm")
public String joinForm() {
return "joinForm";
}
@PostMapping("/join")
public String join(User user) {
System.out.println(user);
user.setRole("ROLE_USER");
/*
* 패스워스 암호화
* SecurityConfig.bCryptPasswordEncoder()에서 @Bean 등록
*/
String rawPassword = user.getPassword();
String encPassword = bCryptPasswordEncoder.encode(rawPassword);
user.setPassword(encPassword);
userRepository.save(user); // 회원가입
return "redirect:/loginForm";
}
※ "/join"메소드는 @GetMapping을 @PostMapping임
4. 스프링부트 시큐리티 4강 - 시큐리티 로그
4.1) {baseDir}/config/SecurityConfig.java에 로그인 기능 구현
// SecurityConfig.java
package com.cos.security1.config;
...
(중략)
...
@Configuration
@EnableWebSecurity // Spring Security Filter가 Spring Filter chain에 등록됨
public class SecurityConfig {
...
(중략)
...
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
...
(중략)
...
.and()
.formLogin()
.loginPage("/loginForm")
/******************* 아래 추가 *******************/
.loginProcessingUrl("/login") // "/login" 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인 진행
.defaultSuccessUrl("/"); // 일반적인 경우, 로그인 후 메인으로 이동하지만, 권한이 필요한 페이지 요청한 경우, 로그인 후, 해당 페이지로 자동 redirect
/*************************************************/
return http.build();
}
}
※ Spring 시큐리티로 로그인을 진행하는 경우, 별도의 "/login" 관련 컨트롤러나 기능은 구현하지 않아도 가능
<!-- loginForm.html -->
<!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>
※ <form>의 method는 반드시 "POST" 방식
4.2) 로그인 계정 관련 사용자명/비밀번호/권한/ 기타 가능, 만료 여부 체크 기능 구현 {baseDir}/config/auth/PrincipalDetails.java 생성(auth 디렉토리 생성)
// PrincipalDetails.java
package com.cos.security1.config.auth;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.cos.security1.model.User;
/*
* 시큐리티가 "/login" 주소 요청이 오면 낚아채서 로그인을 진행시킨다.
* 로그인을 진행이 완료가 되면 session을 만들어줍니다. (Security ContextHolder)
* Session : Security ContextHolder에 생성되고, Session 객체는 Authentication 타입
* Authentication에는 User 정보를 보유
*
* Security Session => Authentication => UserDetails(PrincipalDetails)
*/
public class PrincipalDetails implements UserDetails {
private User user; // Composition
public PrincipalDetails(User user) {
this.user = user;
}
// 해당 User의 권한을 리턴하는 곳
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
Collection<GrantedAuthority> collect = new ArrayList<>();
collect.add(new GrantedAuthority() {
public String getAuthority() {
return user.getRole();
}
});
return collect;
}
@Override
public String getPassword() {
// TODO Auto-generated method stub
return user.getPassword();
}
@Override
public String getUsername() {
// TODO Auto-generated method stub
return user.getUsername();
}
// 유효기간 만료 여부 (false가 만료)
@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return true;
}
// 계정 잠긴 여부 (false가 잠김)
@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return true;
}
// 계정 비밀번호 만료 여부 (false가 만료 X)
@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return true;
}
// 계정 활성화 여부 (false가 비활성)
@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
/*
* 1년동안 회원이 로그인을 하지 않은 경우, 휴면 계정으로 처리하는 경우 (User.java 객체에 loginDate Field를 추가하여 최종 로그인 시간 기록)
* 현재시간 - 로긴시간 => 1년 초과시 return false 하는 형식으로 기능 구현 필요
*/
return true;
}
}
※ isEnabled에 휴면계정 기능을 추가하고자 하는 경우, 주석 참조하여 직접 기능 구현 필요
4.3) 로그인한 사용자의 정보를 객체에 담아서 Authentication 영역에 넘겨주는 서비스 기능 구현
{baseDir}/config/auth/PrincipalDetailsService.java 생성
// PrincipalDetailsService.java
package com.cos.security1.config.auth;
import org.springframework.beans.factory.annotation.Autowired;
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;
import com.cos.security1.model.User;
import com.cos.security1.repository.UserRepository;
/*
* 시큐리티 설정에서 loginProcessingUrl("/login");
* "/login" 요청이 오면 자동으로 userDetailsService 타입으로 IoC되어 있는 loadUserByUsername 함수가 실행
*/
@Service
public class PrincipalDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User userEntity = userRepository.findByUsername(username);
if(userEntity != null) {
return new PrincipalDetails(userEntity);
}
return null;
}
}
※ 메소드명 유지(loadUserByUsername)
★만약 loginForm 화면에서 username이라는 파라미터명을 사용하고 싶지 않은 경우, Securityconfig.java에 아래 추가
// Securityconfig.java
...
(중략)
...
@Configuration
@EnableWebSecurity // Spring Security Filter가 Spring Filter chain에 등록됨
public class SecurityConfig {
...
(중략)
...
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
...
(중략)
...
.and()
.formLogin()
.loginPage("/loginForm")
/********************* loginForm에서 사용자ID가 username2인 경우 *********************/
.usernameParameter("username2") // 화면에서 로그인 id정보를 가져오는 파라미터 정보가 "username2"인 경우, 추가
/************************************************************************************/
.loginProcessingUrl("/login") // "/login" 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인 진행
.defaultSuccessUrl("/"); // 일반적인 경우, 로그인 후 메인으로 이동하지만, 권한이 필요한 페이지 요청한 경우, 로그인 후, 해당 페이지로 자동 redirect
return http.build();
}
}
4.4) DB로부터 사용자 정보를 조회하는 메소드 생성
{baseDir}/repository/UserRepository.java에 findByUsername 메소드 추가
// UserRepository.java
package com.cos.security1.repository;
...
(중략)
...
// CRUD 함수를 JpaRepository가 들고 있음
// JpaRepository를 상속했으므로 @Repository가 없어도 IoC 가능
public interface UserRepository extends JpaRepository<User, Integer> {
// select * from user where username = ?
public User findByUsername(String username);
}
※ 네이밍 규칙 (Spring Data JPA - Query Method 참조)
※ https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#reference
4.5) 로그인 시도 및 정상동작 확인
5. 스프링부트 시큐리티 5강 - 시큐리티 권한처리
5.1) 특정 메소드에 권한 처리를 위해서 SecurityConfig.java에 @EnableGlobalMethodSecurity 추가
// SecurityConfig.java
package com.cos.security1.config;
...
(중략)
...
@Configuration
@EnableWebSecurity // Spring Security Filter가 Spring Filter chain에 등록됨
@EnableGlobalMethodSecurity(securedEnabled=true) // @Secured 활성화(단건 권한)
public class SecurityConfig {
...
}
※ EnableGlobalMethodSecurity(securedEnabled=true)를 적용함으로써 @Secured 어노테이션 사용 여부 활성화
5.2) IndexController에 로그인한 사용자만 볼 수 있는 "/info" 메소드 생성 및 @Secured를 이용한 권한 적용
// IndexController.java
package com.cos.security1.controller;
...
(중략)
...
@Controller // View를 리턴하겠다
public class IndexController {
...
(중략)
...
@Secured("ROLE_ADMIN") // 관리자만 접근 가능
@GetMapping("/info")
public @ResponseBody String info() {
return "개인정보";
}
}
5.3) 특정 메소드에 다중 권한 적용을 위해 SecurityConfig.java에 @prePostEnabled 추가
// SecurityConfig.java
package com.cos.security1.config;
...
(중략)
...
@Configuration
@EnableWebSecurity // Spring Security Filter가 Spring Filter chain에 등록됨
@EnableGlobalMethodSecurity(securedEnabled=true, prePostEnabled=true) // @Secured 활성화(단건 권한), @PreAuthorize/@PostAuthorize 활성화(다수 권한)
public class SecurityConfig {
...
}
5.4) IndexController에 "ROLE_MANAGER"와 "ROLE_ADMIN"만 접근 가능한 메소드 생성
// IndexController.java
package com.cos.security1.controller;
...
(중략)
...
@Controller // View를 리턴하겠다
public class IndexController {
...
(중략)
...
@PreAuthorize("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')") // data실행 전 체크
@GetMapping("/data")
public @ResponseBody String data() {
return "데이터정보";
}
}
다음 Section 1. 스프링 시큐리티 OAuth2.0
https://pu3vig.tistory.com/110
- warning:
개발/프로그래밍 > 백엔드 > 스프링부트 시큐리티 & JWT 강의
[2023.02.08 기준] - 해당 강의 무료 시청 가능
추후 강의 유/무료가 바뀌거나 강의 내용이 업데이트 될 수 있음
- source:
인프런 - 미래의 동료들과 함께 성장하는 곳 | IT 정보 플랫폼
프로그래밍, 인공지능, 데이터, 마케팅, 디자인, 엑셀 실무 등 입문부터 실전까지 업계 최고 선배들에게 배울 수 있는 곳. 우리는 성장 기회의 평등을 추구합니다.
www.inflearn.com
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#reference
Spring Data JPA - Reference Documentation
Example 119. Using @Transactional at query methods @Transactional(readOnly = true) interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") void del
docs.spring.io
'Dev > Spring.SpringBoot' 카테고리의 다른 글
[SpringBoot] [Inflearn - 무료] 스프링부트 시큐리티 & JWT 강의 (Section 2) (0) | 2023.02.22 |
---|---|
[SpringBoot] [Inflearn - 무료] 스프링부트 시큐리티 & JWT 강의 (Section 1) (0) | 2023.02.17 |
[SpringBoot] [Inflearn - 무료] 스프링부트 개념정리(이론) (0) | 2023.02.08 |
[DataSource] BasicDataSource Configuration Parameters 정보 (0) | 2022.11.10 |
JDBC Internal - 타임아웃의 이해 (0) | 2022.11.10 |