티스토리 뷰

반응형

 

 

 

 

JavaSpring BootGradle

 

 

 

보통 로그인부를 직접 만들어 써도 되지만

Spring 에는 security 라는 아주 좋은 인증 기능이 있기 때문에 보통 이것을 사용해왔다.

 

최근 security 쪽 코드를 개선하다가 사용법을 남겨보려고 한다.

 

 

 

 

 

Springboot gradle 에 관련 라이브러리를 작성합니다.

설정에 필요한 라이브러리만 일단 작성해줍시다.

implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

 

 

이제 securityConfig 관련 클래스를 작성해야하는데

기존에는 WebSecurityConfigurerAdapter 를 상속받아 사용했었는데...

해당 클래스가 deprecated 되었고,

spring-security 5.x 버전인가 부터 SecurityFilterChain 을 Bean으로 등록해서 사용하라고 변경되었습니다.

오히려 간편해진 것 같습니다. (제 체감상...)

 

공식문서 참조
 

Spring | Home

Cloud Your code, any cloud—we’ve got you covered. Connect and scale your services, whatever your platform.

spring.io

 

 

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig {

  	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
		  .csrf().disable()
		  .headers().frameOptions().sameOrigin()
		  .and()
			.authorizeRequests()
			.antMatchers("/", "/profile").permitAll()
			.antMatchers("/images/**", "/js/**", "/css/**").permitAll()
			.anyRequest().authenticated()
		  .and()
			.formLogin()
			.loginPage("/")
			.loginProcessingUrl("/login-Proc")
			.usernameParameter("id")
			.defaultSuccessUrl("/home")
			.permitAll()
		  .and()
			.logout()
			.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
			.logoutSuccessUrl("/")
			.invalidateHttpSession(true);
		return http.build();
	}
 }

 

라인들을 간략히 설명해보자면,

@Configuration  //-> 해당 클래스를 빈으로 수동 등록
@EnableWebSecurity //-> security로 사용
// 본인 환경에 맞춰 작성하시면 됩니다.

.csrf().disable() //-> csrf 사용안함 (지금 단계에서 불필요)
.headers().frameOptions().sameOrigin() //-> 동일 도메인 iframe 접근 허용

.antMatchers("/", "/profile").permitAll() //-> 해당 주소 접근 모두 허용
.anyRequest().authenticated() //-> 그 외 요청 인증필요

.formLogin() //-> 폼 로그인 사용
.loginPage("/") //-> 커스텀 로그인 페이지 (이거 없으면 security default 폼 나옴)
.loginProcessingUrl("/login-Proc") //-> 로그인 프로세스 url (form action 값 지정)
.usernameParameter("id") //-> form의 name 값 지정 (default가 username임)
.defaultSuccessUrl("/home") //-> 성공시 이동할 url (이외에 추가 처리사항이 있으면 핸들러로 보내도 됨)

 

 

 

 

 


위와 같이 설정한대로 html 에서 form 하나를 작성 합니다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
    layout:decorate="~{layout/layout}">
<head>
<title>LOGIN</title>
<link rel="stylesheet" href="/css/login.css" />
</head>
<body>
 <div layout:fragment="content">
  <div id="wrap" class="back04">
   <div class="login_box">
	<div class="login_group">
	 <form method="post" action="/login-Proc">
	  <ul class="login_form">
	   <li class="form_box">
		<h2 class="id">아이디</h2>
		<input type="text" name="id" placeholder="ID를 입력하세요." />
	   </li>
	   <li class="form_box">
		<h2 class="pw">패스워드</h2>
		<input type="text" name="password" placeholder="비밀번호를 입력하세요."/>
	   </li>
	   <li>
		<button type="submit" class="btn_login">로그인</button>
	   </li>
	  </ul>
	 </form>
    </div>
   </div>
  </div>
 </div>
</body>
</html>

 

security config 클래스 설정에 따라 action값과 name 값을 맞춰주면 됩니다.

디폴트 설정을 이용해도 상관없습니다.

<form method="post" action="/login-Proc">
<input type="text" name="id" placeholder="사번을 입력하세요." />

 

 

 

 

 

 


이제 사용자 화면에서 값을 받아 왔으니 인증처리 부를 구현해줘야합니다.

UserDetailsService 인터페이스를 구현해줘야 합니다.

 

 

가입자 데이터를 조회할 엔티티와 DTO 클래스를 만들어 줍시다.

엔티티는 DB 테이블 명세대로 작성했고

@Entity
@Table(name="MEMBER")
@Getter
@NoArgsConstructor
public class MemberEntity {
   
   @Id
   @Column(name="SABUN")
   private String id;
   
   @Column(name="PASSWORD")
   private String password;
   
   @Column(name="MEMBER_NAME")
   private String memberName;
   
   @Column(name="EMAIL")
   private String email;
   
   @Column(name="REGDATE")
   private Date regDate;
    
   @Column(name="STATE")
   private String state;

   @OneToMany(mappedBy = "member")
   private Set<GroupUserEntity> groups = new HashSet<>();

   /**
    * 
    * @description 패스워드 저장시 자동 암호화
    */
   public MemberEntity encodePassword(PasswordEncoder passwordEncoder) {
      this.password = passwordEncoder.encode(this.password);
      return this;
   }

}

 

DB데이터를 조회 후 DTO에 담아 인증받기 위해 UserDetails 인터페이스를 구현해줬습니다.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemberDTO implements UserDetails {

   private static final long serialVersionUID = -8360090276325048821L;
   
   private String id;
   private String password;
   private String memberName;
   private String email;
   private Date regDate;
   private String state;
   private List<Roles> Roles;
   
   @Override
   public Collection<? extends GrantedAuthority> getAuthorities() {
      return Roles.stream()
            .map(role -> new SimpleGrantedAuthority(role.getRoleKey()))
            .collect(Collectors.toCollection(ArrayList::new));
   }

   @Override
   public String getPassword() { return this.password; }

   @Override
   public String getUsername() { return this.id; }

   @Override
   public boolean isAccountNonExpired() { return true; }

   @Override
   public boolean isAccountNonLocked() { return true; }

   @Override
   public boolean isCredentialsNonExpired() { return true; }

   @Override
   public boolean isEnabled() { return true; }
}

 

위에 DTO에서 Roles는 뭔데 저렇게 넣었어? 라고 하실까봐 

저는 DB에 권한값이 숫자로 구분되어 있어서  Roles를 enum 클래스로 작성하여 관리하려 합니다.

(이건 안따라하셔도 됨...)

 

@Getter
@AllArgsConstructor
public enum Roles {
    ADMIN("ROLE_ADMIN", "관리자", "10"),
    NOMAL("ROLE_NOMAL", "일반", "20"),
    GUEST("ROLE_GUEST", "게스트", "30"),
    ;

    private String roleKey;
    private String roleName;
    private String roleValue;

    public static Roles findByRoleValue(String value) {
        return Arrays.stream(Roles.values())
                .filter(role -> role.getRoleValue().equals(value))
                .findFirst()
                .orElse(GUEST);
    }
}

 

이제 JPA를 통해 사용자 정보를 조회하는 repository를 작성하고

public interface LoginRepository extends JpaRepository<MemberEntity, String> {
   @Query("select m from MemberEntity m where m.id = :sabun")
   Optional<MemberEntity> findUsers(@Param("sabun") String sabun);
}

 

UserDetailsService 인터페이스를 구현한 Service를 작성합니다.

조회된 값을 DTO에 Mapstruct mapper로 변환해서 넣어주고 

권한값을 enum 클래스의 메소드를 통해 담아주도록 하였습니다.

 

@Service
public class LoginService implements UserDetailsService {
   
   @Autowired private PasswordEncoder passwordEncoder;
   @Autowired private LoginToMapper loginToMapper;
   @Autowired private LoginRepository loginRepository;
      
   /**
    * @description spring-security user check
    */
   @Override
   public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException {
      Optional<MemberEntity> memberData = loginRepository.findUsers(id);

      if(memberData.isPresent()) {
         MemberDTO member = loginToMapper.toDTO(memberData.get());
         List<Roles> roles = new ArrayList<>();

		// 여기서 각자에 맞게 권한리스트를 set 해주면 됩니다.
         memberData.get().getGroups().forEach(list -> {
            roles.add(Roles.findByRoleValue(list.getGroupUserId().getGrpCd()));
            member.setRoles(roles);
         });

         return member;
      } else {
         // member state false 처리
         throw new UsernameNotFoundException("User is not Auth");
      }
   }
}

 

 

 

 

 

 


여기까지 진행해서 로그인을 해보면

MemberDTO 클래스에 로그인 유저정보가 담겨있는 디버깅해보면 알 수 있고

해당 값을 사용해야 할때 @AuthenticationPrincipal 어노테이션으로 가져와서 사용하면 됩니다.

@GetMapping("/home")
public String home(Model model, @AuthenticationPrincipal MemberDTO member) {
   model.addAttribute("member", member);
   return "main/main";
}

 

 

이렇게만 설정하면 서버 세션방식으로 인증하는 상태이고

securityConfig 에 jwt 를 붙여서 토큰으로 인증받게끔 커스텀해도 되고

redis 연결해서 세션을 redis 에 담게 처리해도 됩니다.

 

사용자 인증을 어떻게 할 것이냐에 따라서 다양한 방법들이 있기 때문에...

 

일단 여기까지 제가 개발중인 프로젝트 기준으로 작성해서 아마 기본적인 것보다 쫌 더 뭔가 들이 더 들어가 있는데

빼고 하셔도 무방합니다.

 

 

 

 

 

반응형
댓글
반응형
최근에 올라온 글
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Total
Today
Yesterday