[STUDY] 240307 | Spring | 시큐리티(Security) ②

Nimgnos·2024년 3월 18일
0

👾 STUDY

목록 보기
35/40
post-thumbnail

💻 Spring Security

  1. 시큐리티를 사용 할 수 있는 디펜선스 추가
  2. 모든 페이지를 방문하기 위해선 반드시 인증이 필요!
  3. 프로젝트 성향에 맞춰 인증, 인가에 대한 설정을 우리가 코드로 구현
  4. 로그인 기능을 구현
    • 로그인은 우리가 구현하는 게 아니라 시큐리티에게 위임함.
    • 로그인에 필요한 정보를 시큐리티한테 전달만 해주면 됨.

📌 Member-mapper - 수정

  • 기존 코드
<!-- 로그인 -->
    <select id="login" resultMap="member">
        SELECT MEMBER_ID
            , MEMBER_NAME
            , MEMBER_ROLL
        FROM SECURITY_MEMBER
        WHERE MEMBER_ID = #{memberId}
        AND MEMBER_PW = #{memberPw}
    </select>
  • 수정 코드
<!-- 시큐리티에게 로그인에 필요한 정보를 전달하기 위한 조회 -->
    <select id="login" resultMap="member">
        SELECT MEMBER_ID
            , MEMBER_PW
            , MEMBER_ROLL
        FROM SECURITY_MEMBER
        WHERE MEMBER_ID = #{memberId}
    </select>
  • 전체 코드
<mapper namespace="memberMapper">
    <resultMap id="member" type="com.green.BasicBoard.vo.MemberVO">
        <id column="MEMBER_ID"    property="memberId"/>
        <result column="MEMBER_NAME"    property="memberName"/>
        <result column="MEMBER_PW"    property="memberPw"/>
        <result column="MEMBER_ROLL"    property="memberRoll"/>
    </resultMap>
    <!-- 회원가입 -->
    <insert id="join">
        INSERT INTO SECURITY_MEMBER (
            MEMBER_ID
            , MEMBER_NAME
            , MEMBER_PW
        ) VALUES (
            #{memberId}
            , #{memberName}
            , #{memberPw}
        )
    </insert>
    <!-- 시큐리티에게 로그인에 필요한 정보를 전달하기 위한 조회 -->
    <select id="login" resultMap="member">
        SELECT MEMBER_ID
            , MEMBER_PW
            , MEMBER_ROLL
        FROM SECURITY_MEMBER
        WHERE MEMBER_ID = #{memberId}
    </select>
</mapper>

📌 MemberController - 수정

  • Session에 로그인 정보를 저장하여 구현하는 로그인/로그아웃 코드 삭제
@Controller
public class MemberController {
    @Resource(name = "boardService")
    private BoardService boardService;
    @Autowired
    private BCryptPasswordEncoder encoder;
    //회원가입 페이지 이동
    @GetMapping("/joinForm")
    public String joinForm(){
        return "join";
    }
    //회원가입
    //회원가입 후 로그인 페이지로 바로 이동
    @PostMapping("/join")
    public String join(MemberVO memberVO){
        //비밀번호 암호화
        String encodedPw = encoder.encode(memberVO.getMemberPw());
        memberVO.setMemberPw(encodedPw);
        boardService.join(memberVO);
        return "redirect:/loginForm";
    }
    //로그인 페이지 이동
    @GetMapping("/loginForm")
    public String loginForm(){
        return "login";
    }
}

📌 BoardController - 수정

  1. 로그인 정보를 받아오기 위해서 매개변수에 Authentication 자료형 추가
  2. 로그인 정보 받아오기
User user = (User) authentication.getPrincipal();
        //로그인한 회원의 아이디
        System.out.println(user.getUsername());
        //로그인한 회원의 비밀번호 //보안상 null 뜸
        System.out.println(user.getPassword());
        //로그인한 회원의 권한 정보 (여러개 -> 리스트 형태)
        //getAuthorities -> collection<GrantedAuthority : 리스트 형태
        List<GrantedAuthority> authList = new ArrayList<>(user.getAuthorities());
        //  (권한 정보 리스트    권한 1개)
        for(GrantedAuthority authority : authList){
            System.out.println(authority.getAuthority());
        }
@Controller
public class BoardController {
    // @Resource, @Autowired : 만들어진 객체를 가져와 의존성 주입하는 어노테이션
    @Resource(name = "boardService")
    private BoardServiceImpl boardService;
    //
    @Autowired //자료형으로 판단
    private BCryptPasswordEncoder encoder;
	//
    //게시글 목록 페이지로 이동
    @GetMapping("/")
    public String boardList(Model model){
        //목록 데이터 조회 후 HTML 전달
        List<BoardVO> boardList = boardService.selectBoardList();
        model.addAttribute("boardList", boardList);
        // 암호화 예제
        //encode : 매개변수로 전달된 문자열을 암호화
        String s1 = encoder.encode("java");
        String s2 = encoder.encode("java");
        System.out.println(s1);
        System.out.println(s1);
        //암호화 데이터를 기준 데이터
        boolean b1 = encoder.matches("java", s1);
        //암호화 된 데이터 s1이 "java"와 같냐 (true)
        System.out.println(b1);
        return "board_list";
    }
  	//
    //글쓰기 페이지로 이동
    @GetMapping("/boardWriteForm")
    public String boardWriteFrom(){
        return "board_write_form";
    }
	//
    //글 등록
    @PostMapping("/boardWrite")
    public String boardWrite(BoardVO boardVO){
        //게시글 insert
        boardService.insertBoard(boardVO);
        return "redirect:/";
    }
	//
    //게시글 상세보기 페이지로 이동
    @GetMapping("/boardDetail")
    public String boardDetail(BoardVO boardVO, Model model){
        //게시글 상세 정보 조회 + html로 전달
        BoardVO board = boardService.selectBoardDetail(boardVO.getBoardNum());
        model.addAttribute("board", board);
        return "board_detail";
    }
    @GetMapping("/deleteBoard")
    public String deleteBoard(BoardVO boardVO){
        //게시글 삭제
        boardService.deleteBoard(boardVO.getBoardNum());
        return "redirect:/";
    }
	//
    //수정 페이지로 이동
    @GetMapping("/updateBoardForm")
    public String updateBoardForm(BoardVO boardVO, Model model){
        //수정하고자 하는 게시글의 데이터를 조회 + html 전달
        BoardVO board = boardService.selectBoardDetail(boardVO.getBoardNum());
        model.addAttribute("board", board);
        return "update_form";
    }
	//
    //게시글 수정
    @PostMapping("/updateBoard")
    public String updateBoard(BoardVO boardVO){
        //게시글 수정
        boardService.updateBoard(boardVO);
        return "redirect:/boardDetail?boardNum=" + boardVO.getBoardNum();
    }
    @GetMapping("/manager")
    public String manager(){
        return "manager";
    }
    @GetMapping("/admin")
    public String admin(){
        return "admin";
    }
    @GetMapping("/deny")
    public String deny(){
        return "deny";
    }
    @GetMapping("/sample")
    public String sample(){
        return "security_sample";
    }
    @GetMapping("/sec")
    public String securitySample(Authentication authentication){
        //1. 로그인 정보를 받아오기 위해서 매개변수에 Authentication 자료형 추가
        //2. 로그인 정보 받아오기
        User user = (User) authentication.getPrincipal();
        //로그인한 회원의 아이디
        System.out.println(user.getUsername());
        //로그인한 회원의 비밀번호 //보안상 null 뜸
        System.out.println(user.getPassword());
        //로그인한 회원의 권한 정보 (여러개 -> 리스트 형태)
        //getAuthorities -> collection<GrantedAuthority : 리스트 형태
        List<GrantedAuthority> authList = new ArrayList<>(user.getAuthorities());
        //  (권한 정보 리스트    권한 1개)
        for(GrantedAuthority authority : authList){
            System.out.println(authority.getAuthority());
        }
        return "redirect:/";
    }
}

  • 로그인 정보 받아오기를 통해 회원의 아이디, 비밀번호(보안상 null), 권한 정보(리스트 중 1개)를 받아옴

📌 UserDetailsServiceImpl - 생성

  • 스프링 시큐리티가 제공하는 로그인 기능을 구현하는 클래스
  • UserDetailsService 인터페이스를 상속해서 구현
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
    @Resource(name = "boardService")
    private BoardServiceImpl boardService;
    //시큐리티가 알아서 로그인할 때 호출하는 매서드
    //매개변수로 전달되는 문자열은 로그인을 시도하는 회원의 id
    //그래서 로그인 html에서 입력하는 id input 태그의 name 속성값을
    //반드시 username으로 설정해야 id값이 넘어옴.
    //input 태그의 name 속성 값을 password로 지정하면
    //해당 input 태그의 테이터를 비밀번호로 간주
    //
    //로그인을 실행하는 url이 호출되면 아래의 메소드가 자동 실행
    //로그인 하려는 사람의 로그인에 필요한 정보를 조회해서
    //시큐리티에게 전달하는 코드를 작성
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //로그인 하려는 회원의 정보 조회
        MemberVO member = boardService.login(username);
        //
        //조회한 정보를 UserDetails 타입으로 변환
        UserDetails userInfo = User.builder()
                                .username(member.getMemberId())
                                //비밀번호를 시큐리티에게 전달하면
                                //기본적으로 시큐리티는 비밀번호가 암호화 되어 있다고 판단
                                //{noop}을 비밀번호 앞에 문자열로 추가하면
                                //암호화 되어 있지 않음을 알려줌.
                                .password(member.getMemberPw())
                                .roles(member.getMemberRoll())
                                .build();
        return userInfo;
    }
}

📌 BoardServiceImpl

@Service("boardService")
public class BoardServiceImpl implements BoardService{
    @Autowired
    private SqlSessionTemplate sqlSession;
    @Override
    public List<BoardVO> selectBoardList() {
        List<BoardVO> list = sqlSession.selectList("boardMapper.selectBoardList");
        return list;
    }
    @Override
    public void insertBoard(BoardVO boardVO) {
        sqlSession.insert("boardMapper.insertBoard", boardVO);
    }
    @Override
    public BoardVO selectBoardDetail(int boardNum) {
        BoardVO result = sqlSession.selectOne("boardMapper.selectBoardDetail", boardNum);
        return result;
    }
    @Override
    public void deleteBoard(int boardNum) {
        sqlSession.delete("boardMapper.deleteBoard", boardNum);
    }
    @Override
    public void updateBoard(BoardVO boardVO) {
        sqlSession.update("boardMapper.updateBoard", boardVO);
    }
    @Override
    public void join(MemberVO memberVO) {
        sqlSession.insert("memberMapper.join", memberVO);
    }
    @Override
    public MemberVO login(String memberId) {
        return sqlSession.selectOne("memberMapper.login", memberId);
    }
}

📌 SecurityConfig

  • 스프링 시큐리티 인증, 인가에 대한 프로세스를 정의
  • 이 클래스가 시큐리티 설정 파일임을 인지 시켜주는 역할
  • @EnableWebSecurity : 이 클래스가 시큐리티 설정 파일임을 인지 시켜주는 역할
  • @Configuration : 설정 파일
  • @Bean : 객체 생성 어노테이션. 메소드의 실행 결과 리턴되는 데이터를 객체로 생성
  • security.csrf(AbstractHttpConfigurer::disable)
    • csrf: csrf 공격에 어떻게 대처할 것인지 설정
    • AbstractHttpConfigurer::disable : csrf 비활성화(공부용이므로)
  • .authorizeHttpRequests() : 메소드 내에서 인증 및 인가 관리 - 권한 설정
  • .permitAll() : 위 url은 아무나 들어올 수 있도록 허용함
  • .requestMatchers(new AntPathRequestMatcher("/admin")
    : /admin에는 hasRole("ADMIN") 권한이 있는 사람만 접근 가능
  • .anyRequest().authenticated(); : 나머지는 인증을 받아야 함
  • .exceptionHandling(ex -> {ex.accessDeniedPage("/deny");}
    • 예외 발생 시 처리 해야 하는 내용 작성
    • url을 통한 접속 등 예외 상황 있으므로 예외 처리 하는게 좋음.
    • (ex -> {ex.accessDeniedPage("/deny");} : 엑세스 거부 되면 ("/deny")로 가라!
@EnableWebSecurity
//객체 생성 어노테이션 (@Controller, @Service)
@Configuration //설정 파일
public class SecurityConfig {
    //BCryptPasswordEncoder : 암호화에 사용하는 객체 생성
    @Bean
    public BCryptPasswordEncoder getEncoder(){
        return new BCryptPasswordEncoder();
    }
    //@Bean : 객체 생성 어노테이션
    //메소드의 실행 결과 리턴되는 데이터를 객체로 생성
    //     [  리턴 타입 고정  ]             [매개 변수 고정]
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity security) throws Exception {
                //csrf 공격에 대한 대응 -> 방어를 해지 하겠다. //:: 메소드 참조
        security.csrf(AbstractHttpConfigurer::disable)
                //authorizeHttpRequests 메소드 안에서 인증 및 인가 관리 - 권한 설정
                .authorizeHttpRequests(
                        c -> {
                            c.requestMatchers(
                                    new AntPathRequestMatcher("/"),
                                    new AntPathRequestMatcher("/loginForm"),
                                    new AntPathRequestMatcher("/joinForm"),
                                    new AntPathRequestMatcher("/join"),
                                    new AntPathRequestMatcher("/login"),
                                    new AntPathRequestMatcher("/sample")
                                    //new AntPathRequestMatcher("/member/**") // 컨트롤러 하위 요소 전부
                            ).permitAll() //위 url은 아무나 들어올 수 있도록 허용
                            .requestMatchers(
                                    new AntPathRequestMatcher("/admin")// /admin에는 hasRole("ADMIN") 권한이 있는 사람만 접근 가능
                            ).hasRole("ADMIN")
                            .requestMatchers(
                                    new AntPathRequestMatcher("/manager") // /manage에는 hasRole("MANAGER") 권한이 있는 사람만 접근 가능
                            ).hasRole("MANAGER")
                            .requestMatchers(
                                    new AntPathRequestMatcher("/boardWriteForm")
                            ).hasAnyRole("USER", "MANAGER") //권한이 "USER" 혹은 "MANAGER"인 경우만 글쓰기 게시판 접근 가능
                            .anyRequest().authenticated(); //나머지는 인증을 받아야 함
                        }
                )
                //로그인 form을 활용해서 할 것이고,
                //로그인 설정 내용도 포함
                .formLogin(
                        formLogin -> {
                            //로그인 페이지 url 설정
                            formLogin.loginPage("/loginForm")
                                    //로그인 시 전달되는 id 및 비밀번호의 name 속성값을 지정
                                    .usernameParameter("memberId")
                                    .passwordParameter("memberPw")
                                    //로그인 기능이 실행 되는 url
                                    .loginProcessingUrl("/login")
                                    //로그인 성공 시 이동할 url
                                    //두 번째 매개변수로 true를 넣으면 항상 지정한 url로 이동!
                                    //두 번째 매개변수가 없으면 이전 페이지로 이동
                                    //이전 페이지가 없다면 지정한 url로 이동
                                    .defaultSuccessUrl("/") //로그인 성공 시 갈 페이지, 가려던 페이지로 이동
                                    .failureUrl("/loginForm"); //로그인 실패 시 이동
                        }
                )
                //로그아웃
                .logout(
                        logout -> {
                            //해당 url 요청이 들어오면 시큐리티가 로그아웃 진행
                            logout.logoutUrl("/logout")
                                    //로그아웃 성공 시 이동할 url
                                    .logoutSuccessUrl("/")
                                    //로그아웃 성공 시 세션 데이터 삭제
                                    .invalidateHttpSession(true);
                        }
                )
                //예외 발생 시 처리 해야 하는 내용 작성
                //url을 통한 접속 등 예외 상황 있으므로 예외 처리 하는게 좋음.
                .exceptionHandling(
                        ex -> {
                            //엑세스 거부 되면 ()로 가라
                            ex.accessDeniedPage("/deny");
                        }
                );        

🌟 인증이 모두 풀린 초기화 설정 코드 - 프로젝트 시 활용 !

        .authorizeHttpRequests(
                c -> {
                    c.requestMatchers().permitAll();
                }
        )
        return security.build();
    }
}

profile
먹고 기도하고 코딩하라

0개의 댓글