[SpringBoot] 스프링부트 프로젝트 생성 후 spring-security 사용 및 테스트하기4 login 설정~로그인 설정~(세팅 스프링 시큐리티 세션 스프링부트3.25 )_IntelliJ IDEA 2024.1JDK17 spring-boot test session http--++
* 초기 세팅 (개발환경) 및 설정
https://rockbottomdevbus.blogspot.com/2024/04/springboot-1-325-intellij-idea-20241.html
* 이전글
[SpringBoot] 스프링부트 프로젝트 생성 후 spring-security 사용 및 테스트하기1 login 설정~로그인 설정~(세팅 스프링 시큐리티 세션 스프링부트3.25 )_IntelliJ IDEA 2024.1JDK17 spring-boot test session http--++
https://rockbottomdevbus.blogspot.com/2024/05/springboot-spring-security-1-login-325.html
[SpringBoot] 스프링부트 프로젝트 생성 후 spring-security 사용 및 테스트하기2 login 설정~로그인 설정~(세팅 스프링 시큐리티 세션 스프링부트3.25 )_IntelliJ IDEA 2024.1JDK17 spring-boot test session http--++
https://rockbottomdevbus.blogspot.com/2024/05/springboot-spring-security-2-login-325.html
[SpringBoot] 스프링부트 프로젝트 생성 후 spring-security 사용 및 테스트하기3 login 설정~로그인 설정~(세팅 스프링 시큐리티 세션 스프링부트3.25 )_IntelliJ IDEA 2024.1JDK17 spring-boot test session http--++
https://rockbottomdevbus.blogspot.com/2024/05/springboot-spring-security-3-login-325.html
1. Custom403Handler.java 생성 ( Accesed Denied 오류 핸들링 )
1. handler패키지 생성 >> 경로 : /main/java/{pakage name}/security/handler/
-- Custom403Handler.java
package org.zerock.b01.security.handler;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import java.io.IOException;
@Log4j2
public class Custom403Handler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
log.info("----------------Accesed Denied--------------");
response.setStatus(HttpStatus.FORBIDDEN.value());
//JSON 요청이었는지 확인
String contentType = request.getHeader("Content-Type");
log.info("Content-Type: " + contentType);
boolean jsonRequest = contentType.startsWith("application/json");
log.info("isJSON" + jsonRequest);
//일반 request
if (!jsonRequest) {
response.sendRedirect("/member/login?error=ACCESS_DENIED");
}
}
}
2. CustomSecurityConfig.java에 exceptionHandler 설정 추가
1. CustomSecurityConfig.java의 filterChain메서드에 설정추가
-CustomSecurityConfig.java
//exceptionHandler 설정
http.exceptionHandling(httpSecurityExceptionHandlingConfigurer -> {
httpSecurityExceptionHandlingConfigurer.accessDeniedHandler(accessDeniedHandler());
});
2. CustomSecurityConfig.java에 Bean등록
-CustomSecurityConfig.java
//AccessDeniedHandler 빈등록
@Bean
public AccessDeniedHandler accessDeniedHandler(){
return new Custom403Handler();
}
3. 로컬 화면에서 테스트 동일한
권한없는 계정으로 modify에 강제로 접근해서 수정기능 접근
http://localhost:8090/board/modify?bno=126&page=8&size=10&keyword=&continue
4. Member.java 생성
1. Member.java 파일 생성 >> 경로 : /main/java/{pakage name}/domain
-- Member.java
package org.zerock.b01.domain;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import lombok.*;
import java.util.HashSet;
import java.util.Set;
@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString(exclude = "roleSet")
public class Member extends BaseEntity {
@Id
private String mid;
private String mpw;
private String email;
private boolean del;
//열거형 처리 roleSet
@ElementCollection(fetch = FetchType.LAZY)
@Builder.Default
//중복되면안되는이유로 set사용
private Set<MemberRole> roleSet = new HashSet<>();
//소셜 로그인 처리
private boolean social;
//패스워드 변경 (세터 대신)
public void changePassword(String mpw) {
this.mpw = mpw;
}
//이메일 변경
public void changeEmail(String email) {
this.email = email;
}
//삭제 여부 변경 (바로삭제 x)
public void changeDel(boolean del) {
this.del = del;
}
//addRoll 역할 추가
public void addRole(MemberRole role) {
this.roleSet.add(role);
}
//역할 삭제를 위해서
public void clearRoles(){
this.roleSet.clear();
}
//소셜 여부 변경을 위해서
public void changeSocial(boolean social) {
this.social = social;
}
}
5. MemberRole.java 생성 ( 열거형으로 생성 public enum : 상수형 배열의 개념으로 생각하면됨)
1. MemberRole.java 파일 생성 >> 경로 : /main/java/{pakage name}/domain
-- MemberRole.java
package org.zerock.b01.domain;
//상수형 배열이라고 생각하면 됨
public enum MemberRole {
USER, ADMIN;
}
6. MemberRepository.java 생성 (소셜을 사용하지 않는 사용자의 권한 세팅)
1. MemberRepository.java 파일 생성 >> 경로 : /main/java/{pakage name}/repository
-- MemberRepository.java
package org.zerock.b01.repository;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.zerock.b01.domain.Member;
import java.util.Optional;
public interface MemberRepository extends JpaRepository<Member, String> {
// 소셜 사용하지 않는 사용자의 권한 로딩
@EntityGraph(attributePaths = "roleSet") //조인을 위해서 사용한 어노테이션
@Query("select m from Member m where m.mid = :mid and m.social = false ")
Optional<Member> getWithRole(String mid);
}
8. MemberRepositotyTests.java 생성 ( Repository 테스트 )
1. MemberRepositotyTests.java 파일 생성 >> 경로 : /test/java/{pakage name}/repository
-- MemberRepositotyTests.java
package org.zerock.b01.repository;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.zerock.b01.domain.Member;
import org.zerock.b01.domain.MemberRole;
import java.util.Optional;
import java.util.stream.IntStream;
@SpringBootTest
@Log4j2
public class MemberRepositotyTests {
@Autowired
private MemberRepository memberRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Test
public void insertMember(){
IntStream.rangeClosed(1,100).forEach(i -> {
Member member = Member.builder()
.mid("member"+i)
.mpw(passwordEncoder.encode("1111"))
.email("email"+i + "@test.com")
.build();
member.addRole(MemberRole.USER);
if(i >= 90){
member.addRole(MemberRole.ADMIN);
}
memberRepository.save(member);
});
}
@Test
public void testRead(){
Optional<Member> result = memberRepository.getWithRole("member100");
Member member = result.orElseThrow();
log.info("11111111111111111");
log.info(member);
log.info("22222222222222222222");
log.info(member.getRoleSet());
log.info("333333333333333333");
member.getRoleSet().forEach(memberRole -> log.info(memberRole.name()));
}
}
9. 쿼리 확인
select * from member;
select * from member_role_set where role_set = 0;
select * from member_role_set where role_set = 1;
10. CustomUserDetailsService 파일 수정
( 권한세팅 및 DB에 저장된 USER로 로그인 하기 위한 설정 )
1. 기존에 강제로 로그인처리했던 user1에 관련된 설정 주석처리
2. db 사용자정보 저장 및 유효성 처리
3. dto를 생성하여 값을 세팅, UserDetails 타입으로 리턴함
-CustomUserDetailsService.java
@Log4j2
@Service
@RequiredArgsConstructor
class CustomUserDetailsService implements UserDetailsService {
//private PasswordEncoder passwordEncoder; // 주입하면 순환구조 발생 final 제거
private final MemberRespository memberRespository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("loadUserByUsername : " + username);
//userDeatils 객체로 반환하는 userDetails를 생성 (모든 로그인처리가 user1로 처리됨)
/*UserDetails userDetails = User.builder().username("user1")
.password(passwordEncoderConfig.passwordEncoder().encode("1234")).authorities("ROLE_USER").build();
return userDetails;*/
// DB에 등록된 사용자 정보를 불러옴....
Optional<Member> result = memberRespository.getWithRoles(username);
// 결과가 없는 경우에... UserDetails에 있는 예외 처리 클래스를 호출....
if(result.isEmpty()) {
throw new UsernameNotFoundException("username not found.....");
}
Member member = result.get();
//UserDetails 객체로 반환하는 userDetails를 생성...
MemberSecurityDTO memberSecurityDTO = new MemberSecurityDTO(
member.getMid(),
member.getMpw(),
member.getMname(),
member.getMnick(),
member.getMemail(),
member.getMpno(),
member.isDel(),
false, // 소셜로 로그인 처리하지 않는 상황
member.getRoleSet().stream().map(memberRole ->
new SimpleGrantedAuthority("ROLE_"+memberRole.name()))
.collect(Collectors.toList())
);
log.info("memberSecurityDTO : ");
log.info(memberSecurityDTO);
return memberSecurityDTO;
}
11. db에 저장된 사용자로 로그인 되는지 테스트
http://localhost:8090/member/login
댓글
댓글 쓰기