어렵게 성공함
db는 postgre로 적용
build.gradle, application.properties (특이한 부분은 없음...)
id 'java'
id 'org.springframework.boot' version '3.1.0'
id 'io.spring.dependency-management' version '1.1.0'
}
group = 'spring.aop'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'org.postgresql:postgresql'
runtimeOnly 'com.mysql:mysql-connector-j'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
tasks.named('test') {
useJUnitPlatform()
}
bootJar{
archivesBaseName = 'aop-test-sec'
archiveFileName = 'app-sec-aop-test.jar'
archiveVersion = "0.6.2"
}
# default super user
spring.datasource.username=postgres
spring.datasource.password=xxxxxxx
spring.datasource.driver-class-name=org.postgresql.Driver
# initial sql file
spring.sql.init.mode=never
# create, create-drop, validate, update, none
spring.jpa.hibernate.ddl-auto=none
#JPA config 출시 시에는 false
spring.jpa.show-sql=true
#spring.jpa.defer-datasource-initialization=true
# 출시 시에는 true
spring.thymeleaf.cache=false
# this set by windows system > env variable (시스템>시스템변수>환경변수> 새값:
# key(원하는 변수명) > envVersionNum , 원하는 값 > 컴파일하면 윈도우 값을 기본으로 가저옮
version=local_postgre_Db_0.2.3
#version=${envVersionNum}
# log setting
logging.level.root = WARN
logging.level.com.example.aop_after_aws = DEBUG
#logging.level.org.springframework = ERROR
logging.level.org.springframework = INFO
logging.file.name=app.log
logging.pattern.dateformat=yy-MM-dd H:m
데이터베이스에 table 생성(데이터 없음)
============================
CREATE SEQUENCE IF NOT EXISTS users_seq;
CREATE TABLE IF NOT EXISTS users (
user_id BIGINT NOT NULL DEFAULT nextval('users_seq') PRIMARY KEY,
username varchar(255) NOT NULL,
email varchar(255) NOT NULL,
password varchar(255) NOT NULL,
role varchar(255),
enabled boolean NOT NULL
);
=========================================
UserAccounts entities
import jakarta.persistence.*;
@Entity
@Table(name = "user_accounts")
public class UserAccounts {
@Id
@SequenceGenerator(name = "user_accounts_seq", sequenceName = "user_accounts_seq", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_accounts_seq")
private long userId;
@Column(name = "username")
private String userName;
private String password;
private String email;
private boolean enabled = true;
public UserAccounts() {
}
public String getPassword() {
return password;
}
.....
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
주의 사항 id 생성시
@SequenceGenerator(name = "user_accounts_seq", sequenceName = "user_accounts_seq", allocationSize = 1)
이 거 안해주면 애러남... 이유는 모름 아마도 postgre 특성인 듯...
=======================================
SecurityConfiguration => 이전 spring security는 WebSecurityConfigurerAdapter 를 상속 받아서 override해서 사용하는 방식이나 여기서 많이 바뀜.... 고생, 고생
@Configuration
public class SecurityConfiguration {
@Autowired
BCryptPasswordEncoder passwordEncoder;
@Autowired
private ProjectRepository projectRepository;
@Autowired
DataSource dataSource;
// authentication(login 사용자 인증)
@Bean
public UserDetailsManager userDetailsService() {
JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);
users.setUsersByUsernameQuery(
"select username, password, enabled from user_accounts where username= ?"
);
users.setAuthoritiesByUsernameQuery(
"select username, role from user_accounts where username = ?"
);
return users;
}
//authorization(사용자별 권한 설정)
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/projects/new").hasRole("ADMIN")
.requestMatchers("/projects/save").hasRole("ADMIN")
.requestMatchers("/employees/new").hasRole("ADMIN")
.requestMatchers("/employees/save").hasRole("ADMIN")
.requestMatchers("/", "/**").permitAll()
)
.formLogin(in -> in
.loginProcessingUrl("/").permitAll())
.logout(logout -> logout
.deleteCookies("remove")
.invalidateHttpSession(false)
.permitAll()
)
.httpBasic(withDefaults())
.headers(headers -> headers
.frameOptions(frameOptions -> frameOptions
.disable()
// .sameOrigin()
)
);
return http.build();
}
}
기본적으로 scrf가 적용되어 있음...
DB 접속해서 사용자 인증, 권한을 갖고 오기 위해서는 sql 작성 필요(적용 방법 찾는데... 사례가 없어서...)
@Autowired
DataSource dataSource;
// authentication(login 사용자 인증)
@Bean
public UserDetailsManager userDetailsService() {
JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);
users.setUsersByUsernameQuery(
"select username, password, enabled from user_accounts where username= ?"
);
users.setAuthoritiesByUsernameQuery(
"select username, role from user_accounts where username = ?"
);
return users;
}
===============================================
필터 거는 방법 (위에서 부터 아래로 적용됨)
//authorization(사용자별 권한 설정)
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/projects/new").hasRole("ADMIN") // 위의 db설정(ROLE_ADMIN)과 관련..
.requestMatchers("/projects/save").hasRole("ADMIN")
.requestMatchers("/employees/new").hasRole("ADMIN")
.requestMatchers("/employees/save").hasRole("ADMIN")
.requestMatchers("/", "/**").permitAll()
)
.formLogin(in -> in
.loginProcessingUrl("/").permitAll())
.logout(logout -> logout
.deleteCookies("remove")
.invalidateHttpSession(false)
.permitAll()
)
.httpBasic(withDefaults())
.headers(headers -> headers
.frameOptions(frameOptions -> frameOptions
.disable()
// .sameOrigin()
)
);
return http.build();
}
}
headers 부분은 => security http response header
X-Frame-Options
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.headers(headers -> headers
.frameOptions(frameOptions -> frameOptions
.sameOrigin()
)
);
return http.build();
}
==================================================
사용자 등록을 위한 html 작성(thymeleaf)
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="layout :: header"><title>new employee</title></head>
<body>
<nav th:replace="layout :: navbar"></nav>
<div class="container-md pt-3">
<h1>Register User Account</h1>
<form th:action="@{/register/save}" th:object="${userAccount}" method="post">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text" id="" style="width: 220px">username</span>
</div>
<input type="text" class="form-control" th:field="*{userName}" placeholder="userName">
</div>
<div class="input-group mb-3 mt-2">
<div class="input-group-prepend">
<span class="input-group-text" id="inputGroup-sizing-default" style="width: 220px">Email</span>
</div>
<input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" th:field="*{email}" placeholder="email">
</div>
<div class="input-group mb-3 mt-2">
<div class="input-group-prepend">
<span class="input-group-text" id="inputGroup-sizing-default3" style="width: 220px">password</span>
</div>
<input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" th:field="*{password}" placeholder="password">
</div>
<input type="hidden" name="_csrf" th:value="${_csrf.token}">
<div class="container">
<div class="row">
<div class="col text-center ">
<button type="submit" style="width: 260px" class="btn btn-dark">Register User Account</button>
</div>
</div>
</div>
</form>
</div>
</body>
</html>
<form th:action="@{/register/save}" th:object="${userAccount}" method="post">
action에서 @{/경로} 를 지정하면 thymeleaf에서 자동으로 csrf.token을 전달함
일반 action으로 하면 별도로
<input type="hidden" name="_csrf" th:value="${_csrf.token}"> 을 지정해야 함
csrf 설정 안하면 접속 권한 없다고 나오(403) 신경써야함
============================================
controller 생성
public class SecurityController {
@Autowired
BCryptPasswordEncoder passwordEncoder;
@Autowired
UserAccountRepository accountRepository;
@GetMapping("/register")
public String register(Model model) {
UserAccounts userAccount = new UserAccounts();
model.addAttribute("userAccount", userAccount);
return "security/register";
}
@PostMapping("/register/save")
public String saveUser(Model model, UserAccounts userAccount) {
userAccount.setPassword(passwordEncoder.encode(userAccount.getPassword()));
accountRepository.save(userAccount);
return "redirect:/";
}
}
여기서 password를 enconding 해서 db에 저장함
userAccount.setPassword(passwordEncoder.encode(userAccount.getPassword()));
form 경로로 접속해서 사용자 생성
DB에 접속해서 사용자 권한 생성
===============================================
update user_accounts
set role = 'ROLE_USER'
where username = 'user';
update user_accounts
set role = 'ROLE_ADMIN'
where username = 'admin';
========================================================
==> 주의 사항 role을 그냥 USER, ADMIN으로 하면
SecurityFilterChain에서 hasAthority("ADMIN")으로 해야 동작하고
ROLE_ADMIN 으로 하면 hasRole("ADMIN")으로 해도 동작함... (위의 security config 관련)
'Spring' 카테고리의 다른 글
spring security 6 (0) | 2023.06.08 |
---|---|
RESTful 아키텍처 (0) | 2023.01.25 |
Spring boot 구성 시 버전 관련 (0) | 2023.01.25 |
Spring boot 개요 (0) | 2023.01.20 |
Spring 용어 정리 (0) | 2023.01.20 |