IT

스프링시큐리티 6장 분석

프로개발러 2023. 7. 21. 13:32
반응형

소스는

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만 가지고 오는 것은 아님...

반응형