DeseoDeSeo
[Spring ] MVC06, 로그인에 보안 설정 본문
이제 요청이 들어오면 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도 거쳐야지 정보가 있음.
'spring' 카테고리의 다른 글
[Spring] mvc06, Security 로그아웃, 회원정보수정, 프로필 사진 변경 (1) | 2023.10.16 |
---|---|
[Spring] 3 tier architecture (1) (1) | 2023.10.04 |
[Spring] mvc04, 로그인 modal메세지 (0) | 2023.09.26 |
[Spring] (보안1)db의 회원 비밀번호 보안 설정하기 (0) | 2023.09.25 |
[Spring] (보안2)내 정보 수정에서 권한 수정하기 & 회원가입 시, 비밀번호암호 (0) | 2023.09.25 |