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 으로 요청을 해보자
plaintext 가 null 이라고 한다
그 이유는 isvalid() 메소드의 매개변수 userNmae password 가 null 이야는 뜻이야
이런경우 쿼리스트링 방식을 이용하자
성공적으로 출력이된다
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 |