20221011 [Spring Boot, JPA]

Yeoonnii·2022년 10월 11일
0

TIL

목록 보기
44/52
post-thumbnail

Spring Security 사용하여 로그인진행 했었다
지금은 Vue랑 Spring Security 연동 안됨

Vue랑 연동하는건 토큰까지 설정해줘야해서 방법이 좀 다르다

로그인후 권한별 이동페이지 지정

로그인 후 SecurityLoginService에서
사용자로부터 받아온 memberid를 통해 DB에서 사용자의 정보 = member정보가 조회된다

// 사용자로부터 받아온 memberid 를 통해 DB에서 member정보 꺼내기
Member member = mRepository.findById(username).orElse(null);

조회된 정보는 User타입에 담아 리턴해준다

// 조회된 정보에서 아이디, 암호, 권한정보를 가져온다
User user = new User(member.getUserid(), member.getUserpw(), role);

리턴되는 User타입에는 아이디, 암호, 권한정보가 담겨있다

SecurityConfig.java

로그인/로그아웃 성공시 홈화면으로 가게 설정되어있다
.defaultSuccessUrl("/") ➡️ 로그인 후 이동할 페이지 (권한상관없음)
.logoutSuccessUrl("/") ➡️ 로그아웃 후 이동할 페이지 (권한상관없음)

핸들러를 이용하여 사용자 로그인 후 권한별로 이동할 페이지를 지정해주기

CustomesuccessLoginHandler.java

  1. Spring Security의 AuthenticationSuccessHandlerimplements 구현상속 받아 사용
  1. SecurityLoginService에서 반환했던 User타입 꺼낸다
User user = (User) authentication.getPrincipal();
  1. 컬렉션 형태의 권한 꺼내기
    ⇒ 컬렉션 형태의 권한을 배열로 바꾼 후 배열에서 꺼내고 String으로 바꾸어 사용
    Collection<Auth...> => Array[0] => String
String role = user.getAuthorities().toArray()[0].toString();

package com.example.handler;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;

import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

@Component
public class CustomesuccessLoginHandler implements AuthenticationSuccessHandler{

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
            
            // DetailService에서 리턴했던 타입으로 꺼내기
            User user = (User) authentication.getPrincipal();

            // 컬렉션타입의 권한 꺼내기
            // Collection<Auth...>  => Array[0] => String
            // 컬렉션 형태의 권한을 배열로 바꾼 후 배열에서 꺼내고 String으로 바꾸어 사용
            String role = user.getAuthorities().toArray()[0].toString();
            if(role.equals("ADMIN")){ // 꺼낸 권한이 "ADMIN"인경우 이동할 위치 지정
                response.sendRedirect(request.getContextPath() + "/admin/home.do");

            } else if(role.equals("SELLER")){ // 꺼낸 권한이 "SELLER"인경우 이동할 위치 지정
                response.sendRedirect(request.getContextPath() + "/seller/home.do");
                
            } else if(role.equals("CUSTOMER")){ //꺼낸 권한이 "CUSTOMER"인경우 이동할 위치 지정
                response.sendRedirect(request.getContextPath() + "/customer/home.do");
                
            } else { //꺼낸 권한 데이터가 없는 경우 이동할 위치 지정
                response.sendRedirect(request.getContextPath() + "/");
            }
    }
}

@RequiredArgsConstructor 어노테이션

@RequiredArgsConstructor 어노테이션
@Getter, @Setter 어노테이션 처럼 @RequiredArgsConstructor어노테이션은
클래스에 선언된 final 변수들, 필드들을 매개변수로 하는 생성자를 자동으로 생성해주는 어노테이션이다

@RequiredArgsConstructor는 초기화 되지않은 final 필드나,
@NonNull 이 붙은 필드에 대해 생성자를 생성해 준다

➡️ 새로운 필드를 추가할 때 다시 생성자를 만들어 관리해야하는 번거로움을 없애준다
= @Autowired를 사용하지 않고 의존성 주입가능
= @RequiredArgsConstructor 어노테이션 사용시 원래 오토와이어드 자리에 final 붙여주면 된다

SecurityConfig.java

생성한 핸들러 사용하여 로그인 후 권한별 페이지 이동하기

@RequiredArgsConstructor 어노테이션` 사용, 원래 오토와이어드 자리에 final 붙여 사용한다

final SecurityLoginService securityLoginService;
final CustomesuccessHandler customesuccessHandler;

로그인시 핸들러 설정
.defaultSuccessUrl("/")
➡️ .successHandler(customesuccessLoginHandler)

로그아웃 성공시 핸들러 설정
.logoutSuccessUrl("/")
➡️ .logoutSuccessHandler(customesuccessLogoutHandler)


로그아웃 후 이동페이지 지정하기

고객페이지/customer에서 진행

CustomerController.java

package com.example.controller;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.dto.CustomUser;

@Controller
@RequestMapping(value = "/customer")
public class CustomerController {

    @GetMapping(value = "/home.do")
    public String getMethodName(Model model,@AuthenticationPrincipal CustomUser user){
        if(user == null){ //user정보가 없는경우 
            return "redirect:/member/login.do"; //로그인페이지로 리턴
        } // user정보가 있는경우 
        model.addAttribute("user", user); // user의 정보를 담아서 
        return "customer_home"; // /customer/home.do로 이동
    }
}

customer_home.html

로그아웃 버튼 만들어서 로그아웃도 진행

  • 로그아웃은 폼태그로 생성되어야 한다
  • _csrf 토큰이 포함되어야 한다
  • a태그로는 POST이동이 불가하다
  • get방식 사용시 로그아웃 불가 ➡️ POST 방식만 로그아웃 가능
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<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>홈화면</title>
</head>
<body>
    <h3>고객홈화면</h3>
    <hr />

    세션에 들어있는 내용들 : <p th:text="${user}"></p>
    아이디만 꺼내기 : <p th:text="${user.username}"></p>
    권한만 꺼내기 : <p th:text="${user.authorities[0]}"></p>

    <form th:action="@{/member/logout.do}" method="post">
        <input type="submit" value="로그아웃">
    </form>

    <form>
        
    </form>
</body>
</html>

🤯 status=403 오류 처리

SecurityConfig.java

// 403오류처리
// 접근불가 페이지일때 보여지는 url 주소
 http.exceptionHandling().accessDeniedPage("/page403.do");

HomeController.java

@GetMapping(value="/page403.do")
    public String getMethodName() {
        return "page403";
    }

User 상속받아 사용하기

user에 더 많은 정보담기 위해 상속받아 추가로 포함할 내용을 넣어 사용

조회된 정보에서 아이디, 암호, 권한정보를 가져온다
User user = new User(member.getUserid(), member.getUserpw(), role);

dto/CustomUser 생성

User(=세션)에 추가로 포함할 내용을 넣기위해 클래스 상속 받아 추가할 내용 추가하기

package com.example.dto;

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

// 세션에 포함할 내용을 추가하기 위한 클래스 상속
@Getter
@Setter
@ToString
public class CustomUser extends User {

    // 변수 생성
    String username;
    String password;
    Collection<GrantedAuthority> authorities;
    String phone; // 추가할 정보

    public CustomUser(String username, String password, Collection<GrantedAuthority> authorities, String phone) {
        super(username, password, authorities);
        this.username = username;
        this.password = password;
        this.authorities = authorities;
        this.phone = phone;
    }
    
}

SecurityLoginService.java

User반환시 새로 상속받아 생성한 CustomUser 반환
➡️ User user ⇒ CustomUser user

// 로그인 화면에서 전달되어 호출되는 서비스
@Service
public class SecurityLoginService implements UserDetailsService{

    @Autowired
    MemberRepository mRepository;

    
    // 로그인 화면에서 전달되어 호출되는 오버라이드 메서드
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("=====================username===================");
        System.out.println(username);

        // 위에서 memberid 를 통해 DB에서 member정보 꺼내기
        Member member = mRepository.findById(username).orElse(null);

        if(member != null){ // 조회된 회원정보가 있는 경우
            // 권한정보는 그냥 만들면 안된다! 배열형태로 만들어줘야함
            String[] str = { member.getRole() };
            Collection<GrantedAuthority> role = AuthorityUtils.createAuthorityList(str);
            
            // 조회된 정보에서 아이디, 암호, 권한정보를 가져온다
            // User user = new User(member.getUserid(), member.getUserpw(), role);
            // 상속받아 생성한 User로 반환
            CustomUser user = new CustomUser(member.getUserid(), member.getUserpw(), role, member.getPhone());
            return user;

        }
        else {  // 조회된 회원정보가 없는경우
            // 오류처럼 보이지 않게 하기 위해
            String[] str = { "_" };
            Collection<GrantedAuthority> role =  AuthorityUtils.createAuthorityList(str);
            // User user = new User("", "_", role );
            // 상속받아 생성한 User로 반환
            CustomUser user = new CustomUser("", "_", role, "");
            return user; 
        }
    }
}

CustomerController.java

받아오는 User값을 변경해준다
@AuthenticationPrincipal User user ➡️ @AuthenticationPrincipal CustomUser user

    @GetMapping(value = "/home.do")
    public String getMethodName(Model model,@AuthenticationPrincipal CustomUser user){
        if(user == null){ //user정보가 없는경우 
            return "redirect:/member/login.do"; //로그인페이지로 리턴
        } // user정보가 있는경우 
        model.addAttribute("user", user); // user의 정보를 담아서 
        return "customer_home"; // /customer/home.do로 이동
    }

Spring Security Logout

  1. script를 이용한 security logout 호출
  2. LogoutHandler를 이용한 security logout호출

1. script를 이용한 security logout호출

member_logout.html

script를 이용하여 로그아웃 처리를 하기 위한 로그아웃 버튼 생성
➡️ 화면이 로딩됨과 동시에 바로 로그아웃이 진행된다

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">

<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>로그아웃</title>
</head>
<body>
    <form th:action="@{/member/logout.do}" method="post" id="form" style="display:none">

    </form>

    <script>
        const form = document.getElementById('form');
        form.submit();
    </script>

</body>

</html>

MemberController.java

script를 이용한 security logout 호출하기 ➡️ return "redirect:/member/logout.do"
컨트롤러에서 로그아웃 사용

    @GetMapping(value = "logout.do")
    public String logoutGET(){
        return "member_logout";
    }

2. LogoutHandler를 이용한 security logout호출

member_logout.html

LogoutHandler를 이용하여 로그아웃 처리를 하기 위한 로그아웃1 버튼 생성

    <form th:action="@{/member/logout1.do}" method="get">
        <input type="submit" value="로그아웃1">
    </form>

MemberController.java

스크립트 없이 핸들러로만 로그아웃

    // 방법2. LogoutHandler를 이용한 로그아웃 처리
    @GetMapping(value = "/logout1.do")
    public String logout1GET(HttpServletRequest request, 
                HttpServletResponse response, 
                Authentication auth){
            new SecurityContextLogoutHandler().logout(request, response, auth);
        
        return "redirect:/";
    }

0개의 댓글