배움 __IL/TIL 1기

TIL : 102번째- 230503 [5-1-수]

Mo_bi!e 2023. 5. 3. 18:12

I. 들어가며

1)

signle sign on -> 한번의 로그인으로 한번에,,, 한때 유행했어 그런데 보안문제 때문에 안쓰게 됨

지금은 open Authentication 

 

II. Rland로 인증권한 구현하기

1. front단 (Vue)

(1) Rland로 Vue 기본세팅

1) composiotnAPI -  Vue 기본루틴

 

App.vue() [main.js의 객체생성]

-> index.html [main.js의 엘리먼트에 마운트]

-> main.js [세팅] 

-> Layout.vue[지도]

-> Index.vue[목적지] ( + Footer + Aside + Header)

 

2) 소스코드

<App.vue>

<script setup>
import Header from './components/inc/Header.vue'
import Footer from './components/inc/Footer.vue'

// rlan-vue3/src/components/Header.vue

</script>


<template>

    <router-view></router-view>

</template>

vue 관련 객체를 생성한다.

 

<index.html>

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>anonymous/index</title>

    <link rel="stylesheet" href="/css/reset.css" />
    <link rel="stylesheet" href="/css/style.css" />
    <link rel="stylesheet" href="/css/util.css" />

    <link rel="stylesheet" href="/css/anonymous/modal.css" />
    <link rel="stylesheet" href="/css/icon.css" />
    <link rel="stylesheet" href="/css/button.css" />
    <link rel="stylesheet" href="/css/component.css" />
    <link rel="stylesheet" href="/css/header.css" />
    <link rel="stylesheet" href="/css/footer.css" />
    <link rel="stylesheet" href="/css/aside.css" />
    <link rel="stylesheet" href="/css/anonymous/index.css" />
  </head>
  <body>
      <div id="app"></div>
      <script type="module" src="/src/main.js"></script>
  </body>
</html>

엘리먼트를 세팅한다

이 경우 vue 가 들어갈 부분을 지정한다.

 

<main.js>

import { createApp } from 'vue'
import { createRouter, createWebHashHistory } from 'vue-router'

import App from './App.vue'
import Layout from './components/inc/Layout.vue'
import Index from './components/Index.vue'
import Login from './components/Login.vue'

// 2. Define some routes
// Each route should map to a component.
// We'll talk about nested routes later.
const routes = [
    
  { path: '/', component: Layout, children:[
        { path: 'index', component: Index},
        { path: 'login', component: Login}
    ] },

    // { path: '/admin', component: AdminLayout, children:[
    //     { path: 'menus', component: MenuLaout, children:[
    //         {path: 'list' , component: MenuList},
    //         {path: 'detail' , component: MenuDetail}
    //     ] },
        
    // ] },

  ]
  
  // 3. Create the router instance and pass the `routes` option
  // You can pass in additional options here, but let's
  // keep it simple for now.
  const router = createRouter({
    // 4. Provide the history implementation to use. We are using the hash history for simplicity here.
    history: createWebHashHistory(),
    routes, // short for `routes: routes`
  })

// VueElement.createApp() //글로벌 라이브러리
createApp(App)
.use(router)
.mount('#app') //->ES6 라이브러리

App객체와 엘리먼트를 세팅한다.

그리고 이 부분에서는 레이아웃을 기본세팅해준다.

 

<Layout.vue>

<script setup>
import Header from './Header.vue'
import Footer from './Footer.vue'
import Aside from './Aside.vue'

</script>

<template>

    <Header />
    <Aside />
    <router-view></router-view>    
    
    <Footer />



</template>

컴포넌트 넣기전 두루두루 세티을한다.

 

<Index.vue>

<template>
    <!-- 메인 -->
    <main>
      <!-- 상단 환영인사&이미지 -->
      <section class="greeting">
        <div class="greeting-inner">
          <!-- 1 -->
          <div>
            <h1>알랜드에 오신 것을 환영합니다.</h1>
            <h2>부안에 오면 꼭 들리는 카페</h2>
            <div>
              <a href="./menu/list.html" class="btn btn-default">주문하기</a>
            </div>
          </div>
          <!-- 2 -->
          <div>
            <img src="/image/main-top.png" alt="Rland" />
          </div>
          <!-- 3 -->
          <div>
            <div>Rland Coffee</div>
            <div>
              <img src="/image/logo-badge.svg" alt="Rland" />
            </div>
          </div>
        </div>
      </section>

      <!-- rland를 추천합니다 -->
      <section class="recommend-rland">
        <h1>알랜드만의 특별함</h1>
        <section class="recommend-rland-fruit">
          <div>
            <img src="/image/main-fruit.png" alt="과일청" />
          </div>
          <h1>직접 만든 <strong>과일청</strong>을 맛보세요.</h1>
          <p>
            신선한 과일과 알랜드만의 레시피로 과일향의 풍미를 충분히 느낄 수
            있는 수제청을 드셔보세요.
          </p>
        </section>
        <section class="recommend-rland-cookie">
          <div>
            <img src="/image/main-cookie.png" alt="수제 쿠키" />
          </div>
          <h1>우리가 구운 <strong>수제 쿠키</strong>를 만나보세요.</h1>
          <p>
            신선한 버터 그리고 견과류를 이용하여 바삭함을 더해 매일마다 직접
            구운 맛난 쿠키를 만나보세요.
          </p>
        </section>
        <section class="recommend-rland-roasting">
          <div>
            <img src="/image/main-roasting.png" alt="로스팅" />
          </div>
          <h1>다양한 <strong>로스팅</strong>으로 다채로운 맛을 느껴보세요.</h1>
          <p>
            신선한 과일과 알랜드만의 레시피로 과일향의 풍미를 충분히 느낄 수
            있는 수제청을 드셔보세요.
          </p>
        </section>
      </section>

      <!-- 명소 찾기 -->
      <section class="tourist-spot">
        <h1>알랜드 주변의 명소를 찾아보세요.</h1>
      </section>
      <div class="recommend-coffe-spot">
        알랜드 주변의 30곳이 넘는 힐링 장소에서 맛나는 커피와 경치로 힐링을
        해보세요.
      </div>

      <!--  -->

      <!-- 메인페이지 가운데 빗금 문양 -->
      <div class="slash-background">
        <div></div>
        <div></div>
        <div></div>
      </div>
    </main>
</template>

index 관련 컴포넌트 세팅을 함

 

(2) 로그인 넣기

1)

CSS 는 import 로 (CSS 기능)

 

<Login.vue>

<template>
       <!-- 메인 -->
    <main>
      <div class="sign-in">
        <div class="sign-in-logo">
          <img src="/image/logo-black.svg" alt="Rland" />
        </div>
        <form class="sign-in-form">
          <div class="sign-in-form-input">
            <div>
              <input
                type="text"
                class="input-bottom-line"
                placeholder="아이디"
                required
              />
            </div>
            <div>
              <input
                type="password"
                class="input-bottom-line"
                placeholder="비밀번호"
                required
              />
            </div>
          </div>

          <div class="sign-in-form-button">
            <div class="wd-100">
              <input type="submit" value="로그인" class="btn btn-default" />
            </div>
            <div class="font-14">또는</div>
            <div class="wd-100">
              <a href="" class="deco icon-logo-google btn btn-outline"
                >구글로 로그인</a
              >
            </div>
          </div>
        </form>
        <div class="sign-in-find-user font-14">
          <a href="./sign-up.html">회원가입</a> |
          <a href="">비밀번호 찾기</a>
        </div>
      </div>
    </main>

</template>

<style scoped>
    @import url("/css/sign-in.css");

</style>

즉 @import url() 을 해준다

그래서 여기서 별도로 안만들고 외부에서 들고온다.

2. back

(1) 스프링 시큐리티에서 인증 및 권한 부여 과정

1) Bcrypt 의 필요성과 필터체인 세팅

package kr.co.rland.rlandapiboot3.service;

import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import kr.co.rland.rlandapiboot3.entity.Member;
import kr.co.rland.rlandapiboot3.repository.MemberRepository;

@Service
public class DefaultMemberService implements MemberService{

    @Autowired
    private MemberRepository repository;

    @Override
    public boolean isValid(String userName, String password) {
        // TODO Auto-generated method stub
        
        //empty 인경우를 위한 optional 이 가능해
        // Optional<Member> member = repository.findById(1);

        // 없는경우는 직접 만들어서 가능해 -> 그런데 도로 마이바티스가 돼!
        Member member = repository.findByUserName("newlec");
        System.out.println(member.toString());

        if(member == null)
            return false;
        else if(member.getPwd().equals(member))

        // if(!member.isEmpty())
        //     System.out.println(member.toString());
        return false;
    }
}

순수텍스트 패스워드가 아니라, Bcrypt 으로 인코딩해야함

이런경우 spring security 를 import 해주자

 

package kr.co.rland.rlandapiboot3.auth;

//인증필터를 여기서 만든다.
public class RlandSecurityConfig {
    
}

그리고 인증필터를 만들어주자

인증필터를 콩자루에 담는거야 콩자루에 담는 내용을 가지있다.

 

2) 필터체인 세팅

package kr.co.rland.rlandapiboot3.auth;

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.web.SecurityFilterChain;

//인증필터를 여기서 만든다.
@Configuration
public class RlandSecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{

        http.csrf().disable()
        .authorizeHttpRequests(authorize -> authorize
                    .requestMatchers("/admin/**").hasAnyRole("ADMIN")
                    .requestMatchers("/member/**").hasAnyRole("ADMIN", "MEMBER")
                    .anyRequest().permitAll());

        return http.build();
    }

}

인증관련 전체 규정을 지정하는 필터체인을 세팅한다.

 

인증필터관련 클래스가 스프링에 주입한다 (콩자루에 담기)

 

<코드설명>

csrf 방지기능을 끄고나서,

요청인가에 대한 설정을 하는 부분을 authorize 객체에 담아준다 (람다: 매개변수로 받은것을 객체자체로 반환하는 함수)

그리고 admin, member 의 각 케이스마다 허용 요청을 지정해주고

나머지는 다 허용해준다

그리고 설정관련 모든 부분에 대한 http 객체를 빌드하고 반환한다.

 

 

(2) 사용자 정보 저장과 인증에의 이용

1) 인터페이스 구현

package kr.co.rland.rlandapiboot3.auth;

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;

@Service
public class RlandUserDetailService implements UserDetailsService{

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'loadUserByUsername'");
    }
}

UserDetailsService 인터페이스를 오버라이드 해서 구현한다.

 

2) 전체의 흐름

Config <- RlandUserDetailsService(repo에서 받아오고 담아주기) <- RlandUserDetails (인증권한 관련 세팅)

일련의 다음 3단계가 고정이야!

 

 

3) RlandUserDetails 과 RlandUserDetailsService 세팅

package kr.co.rland.rlandapiboot3.auth;

import java.util.Collection;
import java.util.List;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

//사용자에 대한 그릇
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class RlandUserDetails implements UserDetails{

    private int id;
    private String username;
    private String password;
    private String email;
    private List<GrantedAuthority> authorize;



    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'getAuthorities'");
    }
    @Override
    public String getPassword() {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'getPassword'");
    }
    @Override
    public String getUsername() {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'getUsername'");
    }
    @Override
    public boolean isAccountNonExpired() {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'isAccountNonExpired'");
    }
    @Override
    public boolean isAccountNonLocked() {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'isAccountNonLocked'");
    }
    @Override
    public boolean isCredentialsNonExpired() {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'isCredentialsNonExpired'");
    }
    @Override
    public boolean isEnabled() {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'isEnabled'");
    }
    

}

RlandUserDetails커스터마이징하는거다 이 그릇에 담아서 반환하면 된다.

package kr.co.rland.rlandapiboot3.auth;

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;

@Service
public class RlandUserDetailService implements UserDetailsService{

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
        RlandUserDetails user = new RlandUserDetails();

        return user;
    
    }
}

user를 받았고, 이것을 return 한다.

 

정리하면

RlandUserDetails 클래스는 사용자 정보를 저장해서 인증과정에 이용하는거다

그렇다면 이러한 사용자 정보를 데이터베이스에서 가져오고 수행하기위한 RlandUserDetailService 가 존재한다.

 

(3) 패스워드 인식

1) 암호화 패스워드 비교 (service)

package kr.co.rland.rlandapiboot3.service;

import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import kr.co.rland.rlandapiboot3.entity.Member;
import kr.co.rland.rlandapiboot3.repository.MemberRepository;

@Service
public class DefaultMemberService implements MemberService{

    @Autowired
    private MemberRepository repository;

    @Override
    public boolean isValid(String userName, String password) {
        // TODO Auto-generated method stub
        
        //empty 인경우를 위한 optional 이 가능해
        // Optional<Member> member = repository.findById(1);

        // new BCryptPasswordEncoder()

        // 없는경우는 직접 만들어서 가능해 -> 그런데 도로 마이바티스가 돼!
        Member member = repository.findByUserName("newlec");
        System.out.println(member.toString());

        if(member == null)
            return false;

            //실제로는 이런방법을 쓰는게 아니야
        // else if(member.getPwd().equals(member))
                                //우측은 암호화 좌측은 순수텍스트
        else if(!BCrypt.checkpw(password, member.getPwd()))
            return false;

        // if(!member.isEmpty())
        //     System.out.println(member.toString());
        return true;
    }

    


}

이제 입력한 비밀번호가 실제로 같은지를 비교할 때

순수텍스트암호화텍스트 서로 비교를 해서 다르다면(!) false를 리턴한다

만약 이러한 과정을 다 거쳐도 메소드 끝까지 왔으면 return true 를 한다.

 

2) service 에서 받아서 controller 에서 받기

package kr.co.rland.rlandapiboot3.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import kr.co.rland.rlandapiboot3.service.MemberService;

@RestController
@RequestMapping("members")
public class MemberController {

    @Autowired
    private MemberService service;
    
    @GetMapping("isvalid")
    public ResponseEntity<Map<String, Object>> isValid(String userName, String password){

        Map<String, Object> dto = new HashMap<>();
        dto.put("result", false);

        if(service.isValid(userName, password))
            dto.put("result", true);

        return new ResponseEntity<Map<String,Object>>(dto, HttpStatus.OK);

    }
    
}

그러고 여기서 isvalid true 로 반환되면 된다.

 

그렇다면 postman 으로 요청을 해보자

plaintextnull 이라고 한다

그 이유는 isvalid() 메소드의 매개변수 userNmae passwordnull 이야는 뜻이야

이런경우 쿼리스트링 방식을 이용하자

성공적으로 출력이된다

 

3) 맵객체에 담아서 반환하기

package kr.co.rland.rlandapiboot3.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import kr.co.rland.rlandapiboot3.entity.Member;
import kr.co.rland.rlandapiboot3.service.MemberService;

@RestController
@RequestMapping("members")
public class MemberController {

    @Autowired
    private MemberService service;
    
    @GetMapping("login")
    public ResponseEntity<Map<String, Object>> login(String username, String password){


        Map<String, Object> dto = new HashMap<>();
        dto.put("result", null);

        if(service.isValid(username, password)){
            Member member = service.getByUsername(username);
            System.out.print("111"+ member);
            dto.put("result", member);
        }

        return new ResponseEntity<Map<String,Object>>(dto, HttpStatus.OK);

    }
    
}

성공적으로 입력이 된경우 맵객체에 담아서

응답 부분으로 반환하자


1. 보충

 

2. 회고 

1) 두번째로 인증을 다시 배우니 흐름이 드디어 이해가 되서 다행이다.

 

2) 이렇게 해서 프론트엔드에서 붙이는것을 느껴보고 싶다

'배움 __IL > TIL 1기' 카테고리의 다른 글

TIL : 104번째- 230508 [5-2-월]  (0) 2023.05.08
TIL : 103번째- 230504 [5-1-목]  (1) 2023.05.04
TIL : 101번째- 230501 [5-1-월]  (0) 2023.05.01
TIL : 100번째- 230428 [4-4-금]  (0) 2023.04.28
TIL : 99번째- 230427 [4-4-목]  (0) 2023.04.27