소스는
https://github.com/wikibook/spring-security/tree/main/ssia-ch6-ex1
프로젝트 구조.
db스키마가 mysql기준으로 되어 있어서
postgresql 로 변경함
CREATE TABLE IF NOT EXISTS `spring`.`user` (
`id` INT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(45) NOT NULL,
`password` TEXT NOT NULL,
`algorithm` VARCHAR(45) NOT NULL,
PRIMARY KEY (`id`));
CREATE TABLE IF NOT EXISTS `spring`.`authority` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL,
`user` INT NOT NULL,
PRIMARY KEY (`id`));
CREATE TABLE IF NOT EXISTS `spring`.`product` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL,
`price` VARCHAR(45) NOT NULL,
`currency` VARCHAR(45) NOT NULL,
PRIMARY KEY (`id`));
---------------------postgresql------------------
CREATE TABLE spring.authority (
id int4 NOT NULL,
"name" varchar(45) NOT NULL,
"user" text NOT NULL,
CONSTRAINT authority_pk PRIMARY KEY (id)
);
CREATE TABLE spring.product (
id int4 NOT NULL,
"name" varchar(45) NOT NULL,
price varchar(45) NOT NULL,
currency varchar(45) NOT NULL,
CONSTRAINT product_pk PRIMARY KEY (id)
);
CREATE TABLE spring.users (
id int4 NOT NULL DEFAULT nextval('spring.user_seq'::regclass),
username varchar(45) NOT NULL,
"password" text NOT NULL,
algorithm varchar(45) NOT NULL,
CONSTRAINT pk_user PRIMARY KEY (id)
);
CREATE SEQUENCE spring.authority_seq
INCREMENT BY 1
MINVALUE 1
MAXVALUE 99999999999999
START 1
CACHE 1
NO CYCLE;
CREATE SEQUENCE spring.product_seq
INCREMENT BY 1
MINVALUE 1
MAXVALUE 99999999999999
START 1
CACHE 1
NO CYCLE;
CREATE SEQUENCE spring.user_seq
INCREMENT BY 1
MINVALUE 1
MAXVALUE 9223372036854775807
START 1
CACHE 1
NO CYCLE;
참고로 postgresql에는 auto_identity가 없어서 시퀀스 써야됨..
그리고 pom.xml 에 추가
<!-- postgresql 추가-->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
그리고 추가된 의존성들...
<!-- 스프링 데이터를 이용한 데이터베이스 연결-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 스프링 시큐리티 종속성 나열-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 웹페이지의 정의를 간소화하는 템플릿 엔진, 타임리프-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
특이한것이 이번에는 타임리프 프론트엔진을 사용함.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Products</title>
</head>
<body>
<h2 th:text="'Hello, ' + ${username} + '!'" />
<p><a href="/logout">Sign out here</a></p>
<h2>These are all the products:</h2>
<table>
<thead>
<tr>
<th> Name </th>
<th> Price </th>
</tr>
</thead>
<tbody>
<tr th:if="${products.empty}">
<td colspan="2"> No Products Available </td>
</tr>
<tr th:each="book : ${products}">
<td><span th:text="${book.name}"> Name </span></td>
<td><span th:text="${book.price}"> Price </span></td>
</tr>
</tbody>
</table>
</body>
</html>
실제로 타임리프 쓰는곳 많음.
https://velog.io/@dbsrud11/JSP-vs-Mustache-vs-Thymeleaf참고
JSP vs Mustache vs Thymeleaf
서버 템플릿 엔진 vs 클라이언트 템플릿 엔진
velog.io
Mustache
: JSP와 같이 html을 만들어주는 템플릿 엔진
로직 코드를 사용할 수 없어 View 역할과 서버의 역할을 명확히 분리한다.
Mustache.js와 Mustache.java 이렇게 두 가지가 존재하여 하나의 문법으로 클라이언트/서버 템플릿으로 모두 사용 가능하다.JSP vs Mustache
- JSP는 Java 언어를 지원하고, Mustache는 Java는 물론, 현존하는 대부분의 언어를 지원
- JSP는 로직 구현이 가능하고, Mustache는 로직 구현이 불가능 (view 역할에만 충실)
- JSP는 스프링부트에서는 권장하지 않는 템플릿 엔진, Mustache는 스프링부트에서 공식으로 지원하는 템플릿 엔진
그럼 김영한님 강의를 들을 때 사용하던 Thymeleaf는? 🤷🏻♀️
스프링 진영에서 적극적으로 밀고 있지만 문법이 어렵다는 단점이 있다.
html 태그에 속성으로 템플릿 기능을 사용하는 방식에 진입장벽이 높게 느껴질 수 있다.
(Vue.js 사용 경험이 있다면 태그 속성 방식에 익숙해 쉽게 느껴질 수 있다.)
로그인페이지
로그인 정상적일 때 화면
설정파일부터 설명하자면
ProjectConfig에서
빈형식으로 암호인코딩방식 추가
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SCryptPasswordEncoder sCryptPasswordEncoder() {
return new SCryptPasswordEncoder();
}
아래부분을 통해 /main은 누구나 호출 가능하도록 configure 설정
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.defaultSuccessUrl("/main", true);
http.authorizeRequests().anyRequest().authenticated();
}
그다음 엔티티 클래스 정의
@Entity
public class Users {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String username;
private String password;
@Enumerated(EnumType.STRING)
private EncryptionAlgorithm algorithm;
@OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
private List<Authority> authorities;
특이한 것은 onetomany 어노테이션
user가 여러개의 authrity를 가질수 있다.
INSERT IGNORE INTO `spring`.`authority` (`id`, `name`, `user`) VALUES ('1', 'READ', '1');
INSERT IGNORE INTO `spring`.`authority` (`id`, `name`, `user`) VALUES ('2', 'WRITE', '1');
그다음 fetch에 대한 설명
@OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
private List<Authority> authorities;
fetch
관계 Entity의 데이터 읽기 전략을 결정합니다.
FetchType.EAGER, FetchType.LAZY로 전략을 변경 할 수 있습니다. 두 전략의 차이점은 EAGER인 경우 관계된 Entity의 정보를 미리 읽어오는 것이고 LAZY는 실제로 요청하는 순간 가져오는겁니다.
참고로 유저가 아이디,패스워드 로그인하면
가장 먼저 타는게
@Service
public class AuthenticationProviderService implements AuthenticationProvider {
그다음 유저의 알고리즘을 가져와서 switch문 실행
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
CustomUserDetails user = userDetailsService.loadUserByUsername(username);
switch (user.getUser().getAlgorithm()) {
case BCRYPT:
return checkPassword(user, password, bCryptPasswordEncoder);
case SCRYPT:
return checkPassword(user, password, sCryptPasswordEncoder);
}
패스워드 체크는 matches함수 사용
private Authentication checkPassword(CustomUserDetails user, String rawPassword, PasswordEncoder encoder) {
if (encoder.matches(rawPassword, user.getPassword())) {
return new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities());
} else {
throw new BadCredentialsException("Bad credentials");
}
}
그다음 projectconfig에서
autowires 어노테이션 사용
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationProviderService authenticationProvider;
1. Autowired란
필요한 의존 객체의 “타입"에 해당하는 빈을 찾아 주입한다.
- 생성자
- setter
- 필드
비슷하게 AuthenticationProviderService도 autowired를 통해 의존성 주입
@Service
public class AuthenticationProviderService implements AuthenticationProvider {
@Autowired
private JpaUserDetailsService userDetailsService;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
private SCryptPasswordEncoder sCryptPasswordEncoder;
그래서 당연히 디버깅 걸어보면
로그인시에 메인페이지 컨트롤러보다
@Controller
public class MainPageController {
@Autowired
private ProductService productService;
@GetMapping("/main")
public String main(Authentication a, Model model) {
model.addAttribute("username", a.getName());
model.addAttribute("products", productService.findAll());
return "main.html";
}
}
authenticate함수를 탄다.
참고로 로그인시에
@GetMapping("/main")
public String main(Authentication a, Model model) {
model.addAttribute("username", a.getName());
model.addAttribute("products", productService.findAll());
return "main.html";
}
productService.findAll());
를 통해 모든 프로덕트를 가지고 오는 부분이 있는데
특정 user만 가지고 오는 것은 아님...
'IT' 카테고리의 다른 글
자바스크립트 preventDefault (0) | 2023.07.25 |
---|---|
RuntimeError: CUDA error: invalid device ordinal (0) | 2023.07.21 |
백준 2178 파이썬 (0) | 2023.07.20 |
파이썬3 큐 사용법 및 deque 소스코드 원형 c파일 (0) | 2023.07.20 |
파이썬 백준 1874번 (0) | 2023.07.13 |