- 시큐리티를 사용 할 수 있는 디펜선스 추가
- 모든 페이지를 방문하기 위해선 반드시 인증이 필요!
- 프로젝트 성향에 맞춰 인증, 인가에 대한 설정을 우리가 코드로 구현
- 로그인 기능을 구현
- 로그인은 우리가 구현하는 게 아니라 시큐리티에게 위임함.
- 로그인에 필요한 정보를 시큐리티한테 전달만 해주면 됨.
ㅤ
- 기존 코드
<!-- 로그인 --> <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>
ㅤ
- 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"; } }
ㅤ
- 로그인 정보를 받아오기 위해서 매개변수에 Authentication 자료형 추가
- 로그인 정보 받아오기
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개)를 받아옴
ㅤ
- 스프링 시큐리티가 제공하는 로그인 기능을 구현하는 클래스
- 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; } }
ㅤ
@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); } }
ㅤ
- 스프링 시큐리티 인증, 인가에 대한 프로세스를 정의
- 이 클래스가 시큐리티 설정 파일임을 인지 시켜주는 역할
- @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(); } }
ㅤ