관리 메뉴

DeseoDeSeo

[Spring ] MVC06, 로그인에 보안 설정 본문

spring

[Spring ] MVC06, 로그인에 보안 설정

deseodeseo 2023. 9. 26. 18:49

이제 요청이 들어오면 dispatcher servlet으로 바로 가는 것이 아닌 보안을 한 번 거치고 들어온다.

이렇게 하면 개발자는 중간에서 비밀번호를 알 수 있는 방법이 없다.

SecurityConfig.java
protected void configure(HttpSecurity http) throws Exception {
	// 요청에 대한 보안 설정하는 곳
	// 교재 p.605
	CharacterEncodingFilter filter = new CharacterEncodingFilter();
	filter.setEncoding("UTF-8");
	filter.setForceEncoding(true);
	http.addFilterBefore(filter, CsrfFilter.class);
		
//09/26 화요일
	// 클라이언트가 요청을 했을 때 권한 설정
	//회원 인증부분
	// 모든 요청이 dispatcher servlet하기 전에 보안 httpsecurity를 한번 거쳐서 들어옴. 
	http
		.authorizeRequests() 
			.antMatchers("/") 
				.permitAll()  
		    		.and()   
				.formLogin()  
					.loginPage("/loginForm.do") 
					.loginProcessingUrl("/Login.do")  
				    .permitAll()  
				    .and()  
		        .logout()   
		             .invalidateHttpSession(true) 
			         .and()
			     .exceptionHandling().accessDeniedPage("/access-denied"); 
	}

 

. authorizeRequests( ) 요청에 따른 권한을 처리
. antMatchers(  " / " ) 메인페이지( "/")로 왔을 때, 권한처리를 한다.
. permitAll(  ) 누구나 접근가능 하도록 전체권한
( 누가 와도 다 볼 수 있도록)
. and(  ) 권한을 추가하겠다.
. formlogin(  )  로그인에 보안기능 추가한다.
. loginPage(" / loginForm.do" ) Spring Security에서 제공하는 로그인폼이 아닌 직접 만든 로그인폼을 사용한다.
. loginProcessingUrl("/Login.do") 해당 url로 요청이 들어오면 spring security 자체 로그인 기능을 수행하겠다.
. permitAll( ) 로그인도 누구나 사용가능하도록.
. logout(  )  로그아웃 기능
. invalidateHttpSession(true) Spring Security가 알아서 세션을 만료시킴.
logoutSuccessUrl(" / ") 로그아웃 시키면 메인페이지로 이동
. exceptionalHandling( ).
accessDeniedPage("
access-denied ") ;
로그인 안 하면 특정 페이지에 접근 불가하도록 한다.
@GetMapping("/access-denied")
: 잘못된 권한으로 접근 시 처리부분
@GetMapping("/access-denied")    //로그인을 안 하고 특정 페이지를 요청했을 때 요청되는 url.
public String showAccessDenied() {
	return "access-denied";
}

 

src > main > java 에 새로운 package 생성 " kr.spring.security "
" MemberUserDetailsService.java"

이 클래스는 Spring Security와 Mapper를 연결하는 Service 만드는 클래스.

○ 여기서 로그인에 필요한 회원인증 처리를 함.

 로그인 성공하면 username을 반환함( ➜ username은 id )

package kr.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import kr.spring.entity.Member;
import kr.spring.entity.MemberUser;
import kr.spring.mapper.MemberMapper;

public class MemberUserDetailsService implements UserDetailsService{

	@Autowired
	private MemberMapper mapper;
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		
		Member mvo = mapper.getMember(username)
		if(mvo != null) {
			// 해당 사용자가 有
			return new MemberUser(mvo);
		}else {
			//해당 사용자가 無
			throw new UsernameNotFoundException("user with username" + username + "does not exist.");
		}
	}	
}

로그인 성공하면 username을 반환함. username은 id.

loaduserbyUsername : user의 name을 통해서 user정보를 가져옴. 

여기서 user의 name은 id임.

id( user_name )를 기준으로 로그인 정보를 가져오는 메서드

내부적으로 보이지 않지만 Spring Security가 해당 아이디의 계정을 가져오고

암호화된 비밀번호 비교까지 해서 로그인을 체크하는 메서드.

 

 ➤ VO는 Spring Security 내부 보안 규정상 우리가 직접 만든 클래스 객체라서 바로 담을 수 없음.

그래서 내가 원하는 vo를 담을 수 있게 변환해주는 user class가 필요.

 

memberUser는 user를 상속,

user는 userdetails를 상속.

 

kr.spring.entity 패키지에
MemberUser.java 추가

  이 클래스에서 super안에 3가지 정보를 넣고 나머지 정보는 멤버에 담음. 

이 클래스는 Spring Security에 Member객체를 담을 수 있게 해주는 클래스임.

package kr.spring.entity;

import java.util.Collection;
import java.util.stream.Collectors;

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

import lombok.Data;

@Data
public class MemberUser extends User{
	
	private Member member;
	public MemberUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
                                                 
		super(username, password, authorities);
		//super는 부모에 있는 생성자.(=user안에 있음)
	}
	//실제로 우리가 사용할 생성자
	public MemberUser(Member mvo) {
	
		super(mvo.getMemID(), mvo.getMemPassword(), 
	
				mvo.getAuthList().stream() 
				.map(auth-> new SimpleGrantedAuthority(auth.getAuth()))
				.collect(Collectors.toList())
	
				);
		this.member = mvo;
   }
}

○ Memberuser 매개 변수에서 collection은 배열형태로 받아옴을 뜻함.

○ Memberuser 객체 생성 시 아이디, 비밀번호, 권한을 입력 받음.

➜ 실제로 사용하지 않지만 추상메서드라서 구현함.

 

< 하단의 memberuser 생성자 >

   user(부모) 클래스의 생성자를 사용해서 구현.

user클래스 생성자의 사용하는 권한 정보는  Collection<SimpleGrantedAuthority> 형태로 넣어야한다.

mvo.getAuthList().stream()  바이트로 변경
.map(auth-> new SimpleGrantedAuthority(auth.getAuth())) List<Auth> -> collection안에 들어갈 수 있게 변경함.
.collect(Collectors.toList()) 최종 collection 리스트로 변경함.

 

 

MemberMapper.java
: 로그인 시 아이디만 먼저 보낸다.
public Member login(String username);

 

MemberMapper.xml
	<select id ="login" resultMap="memberMap" >
		SELECT * FROM MEMBER mem LEFT OUTER JOIN Auth auth on  
		mem.MEMID = auth.MEMID WHERE mem.MEMID=#{username} 
	</select>

 ➤ Member를 mem, AUTH를 auth라고 함.

     ➜ 세션의 아이디와 권한의 아이디를 비교함(?!)

 

< 로그인 과정 > 

로그인을 클릭하면 . loginProcessingUrl("/Login.do")  ➜  MemberUSerDetailsService.java ➜ memberUser     로 이동. 

SecurityConfig.java
//!!!!! 마지막으로 여기와서 아래 두개를 실행함.
@Bean // 우리가 만들어놓은 MemberUserDetailsService메모리를 올려 사용하겠다.
public UserDetailsService memberUserDetailsService() {
		return new MemberUserDetailsService();
}
//AuthenticationManagerBuilder 얘가 맞는지 확인함.
//memberUserDetailsService :security와 mapper사이를 연결해주는 
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//내가 만든 MemberUserDetailsService와 암호화 및 복호화를 해주는 패스워드 인코더를 
//Spring security에 등록하는 메서드.
	auth.userDetailsService(memberUserDetailsService()).passwordEncoder(passwordEncoder());	
}
	//Spring Security 환경설정하는 클래스
	//websecurity~ : 요청에 대한 보안 설정을 해주는 클래스

 

회원 로그인 처리하기
loginForm.jsp
<tr>
	<td style="width:110px; vertical-align:middle;">아이디</td>
	<td><input type="text" name ="username" id="memID" class="form-control" maxlength="20" placeholder="아이디를 입력하세요."></td>
</tr>
<tr>
	<td style="width:110px; vertical-align:middle;">비밀번호</td>
	<td><input type="password" name ="password" id="memPassword" class="form-control" maxlength="20" placeholder="비밀번호를 입력하세요."></td>
</tr>

➤ 아이디와 비밀번호의 name을 바꿔준다. 

login.do,  logout.do를 주석처리함.(Spring security가 할거임)

 

로그인 실패하면 url의 마지막에 ?error가 출력된다. 그걸 통해서 실패메세지를 modal로 출력한다.

<script type="text/javascript">
		
		//html을 다 로딩할때 까지 기다리겠다.
		$(document).ready(function(){
			  //09/26 화요일
			 if(${param.error != null}){
			    	$("#messageType").attr("class","modal-content panel-warning");
			    	$(".modal-body").text("아이디와 비밀번호를 확인해주세요.");
			    	$(".modal-title").text("실패메세지");
			    	$("#myMessage").modal("show");
			    }
			if(${not empty msgType}){
				if(${msgType eq "실패메세지"}){
					$("#messageType").attr("class","modal-content panel-warning");
				}
				$("#myMessage").modal("show");
			}
		});
	</script>

 

header.jsp
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
	<c:set var="mvo" value="${SPRING_SECURITY_CONTEXT.authentication.principal}" />
	<c:set var="auth" value="${SPRING_SECURITY_CONTEXT.authentication.authorities}" />

< html> 태그 전에 추가함.

security 태그 라이브러리는 Spring Security에서 제공하는 계정정보 (SecurityContext안에 계정 정보 가져옴.)

변수 mvo : 로그인 한 계정정보 memberuserdetail~에서 memberuser를 가져와서 mvo에 저장함.

변수  auth : 권한 정보도 가져옴.

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
	<%@ taglib prefix ="c" uri="http://java.sun.com/jsp/jstl/core" %>
	<%@ taglib prefix ="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
	<!-- context-path값(=controller)을 내장객체 변수로 저장 -->
	<c:set var="contextPath" value="${pageContext.request.contextPath }" />
	<!-- 09/26 -->
	<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
	<!-- Spring Security에서 제공하는 계정정보(SecurityContext안에 계정 정보 가져오기 -->
	<!-- 로그인 한 계정정보 memberuserdetail~에서 memberuser를 가져와서 mvo에 저장함. -->
	<c:set var="mvo" value="${SPRING_SECURITY_CONTEXT.authentication.principal}" />
	<!-- 권한 정보도 가져옴. -->
	<c:set var="auth" value="${SPRING_SECURITY_CONTEXT.authentication.authorities}" />
	
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
 
 <nav class="navbar navbar-default">
  <div class="container-fluid">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#myNavbar">
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>                        
      </button>
      <a class="navbar-brand" href="${contextPath}/">뇽뇽이</a>
    </div>
    <div class="collapse navbar-collapse" id="myNavbar">
      <ul class="nav navbar-nav">
        <li class="active"><a href="${contextPath}/"> 메인 </a></li>
        <li><a href="boardMain.do">게시판</a></li>  <!--앞에 /없으면 /controller가 생략되어있음. 이건 contextPath임.  -->
      </ul>
      
      <!--⛧⛤ ⛧⛤ ⛧⛤ 이 부분 수정함 -->

      <security:authorize access="isAnonymous()">
      <ul class="nav navbar-nav navbar-right">
      
        		<li><a href="${contextPath}/loginForm.do"><span class="glyphicon glyphicon-globe"> 로그인</span> </a></li>
       			<li><a href="${contextPath}/joinForm.do"> 회원가입 </a></li>            
      </ul>
      </security:authorize>
      
      <security:authorize access="isAuthenticated()">
      <ul class="nav navbar-nav navbar-right">
                <li>
                <c:if test="${mvo.member.memProfile ne ''}">
                	<img class="img-circle"style="width:50px; height:50px;"src="${contextPath}/resources/upload/${mvo.member.memProfile}">    
                </c:if>
                  <c:if test="${mvo.member.memProfile eq ''}">
                	<img class="img-circle"style="width:50px; height:50px;"src="${contextPath}/resources/images/default.jpg">
                </c:if>
                ${mvo.member.memName}님 welcome.     
                [
                	<!-- 권한 정보 띄우기 -->
                	<security:authorize access="hasRole('ROLE_USER')">
                		U
                	</security:authorize>
                	<security:authorize access="hasRole('ROLE_MANAGER')">
                		M
                	</security:authorize>
                	<security:authorize access="hasRole('ROLE_ADMIN')">
                		A
                	</security:authorize>
                	 
                ]
                </li>      
        		<li><a href="${contextPath}/updateForm.do"> <span class="glyphicon glyphicon-heart ">회원정보수정</span> </a></li>
        		<li><a href="${contextPath}/imageform.do"><span class="glyphicon glyphicon-picture">프로필사진등록 </span>  </a></li>
        		<li><a href="${contextPath}/logout.do"> <span class="glyphicon glyphicon-log-out">로그아웃</span> </a></li>
        	</ul>
       
       <!--db에는 파일이름만 저장. 실제로는 resource폴더 안에 진짜 img를 저장.  -->
      </security:authorize>
      
    </div>
  </div>
</nav>
 
</body>
</html>

 

MemberUser.java
private Member member;
	public MemberUser(Member mvo) {
		//user(부모)클래스의 생성자를 사용해서 구현한다.
		// 생성자(아이디, 비밀번호, 권한을 넣어준다.)
		super(mvo.getMemID(), mvo.getMemPassword(), 
		// USer클래스의 생성자의 사용하는 권한 정보는 Collection<SimpleGrantedAuthority>형태로 넣어줘야한다.
				mvo.getAuthList().stream() //바이트로 변경
				.map(auth-> new SimpleGrantedAuthority(auth.getAuth()))
				//List<Auth> -> collection안에 들어갈 수 있게 변경함.
				.collect(Collectors.toList())
				//최종 collection리스트로 변경.
				);
		this.member = mvo; .
		
		
	}

this.member= mvo :  나머지 계정 정보를 넣기위해 만듬.

 ( 회원가입 성공시 MemberUser안에 mvo로 넣었으니. ${mvo.member.profile}이런식으로 가져와야한다. )

header.jsp
: MemberUser안에 member getter메서드를 통해 가져오게끔 변경
 <ul class="nav navbar-nav navbar-right">
     <li>
      <c:if test="${mvo.member.memProfile ne ''}">
      		<li>
           <img class="img-circle"style="width:50px; height:50px;"src="${contextPath}/resources/upload/${mvo.member.memProfile}">    
            </li>       
       </c:if>
       <c:if test="${mvo.member.memProfile eq ''}">
       		<li>
            <img class="img-circle"style="width:50px; height:50px;"src="${contextPath}/resources/images/default.jpg">
            </li>       
        </c:if>
          ${mvo.member.memName}님 welcome.
➤ <security:authorize access= " isAnonymous() " >   
 : 인증 절차를 물어보는 거임. true : 로그인 안 한거임.

➤ mvo.member.memProfile
   :  mvo를 거쳐서 member도 거쳐야지 정보가 있음.