SpringSecurity & CRUD & AWS 5 로그인

kik·2023년 12월 6일
0

일단 Member 엔티티에서 memberId가 중복되지 않도록 수정했다.

@Column(unique = true)
    private String memberId;

다음 MemberRepository에서 memberId로 Member를 찾아오는 코드를 만들어주었다.

public Member findByMemberId(String memberId) {
        return em.createQuery("select m from Member m where m.memberId= :memberId", Member.class)
                .setParameter("memberId", memberId)
                .getSingleResult();
    }

SecurityConfig도 수정했다.


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(AbstractHttpConfigurer::disable) // 사이트 위변조 요청 방지
                .authorizeHttpRequests((authorizeRequests) -> { // 특정 URL에 대한 권한 설정.
                    authorizeRequests.requestMatchers("/user/**").authenticated();
                    authorizeRequests.requestMatchers("/admin/**")
                            .hasRole("ADMIN"); // ROLE_은 붙이면 안 된다. hasRole()을 사용할 때 자동으로 ROLE_이 붙기 때문이다.
                    authorizeRequests.anyRequest().permitAll();
                })

                .formLogin((formLogin) -> {
                    formLogin
                            .loginPage("/loginForm") // 권한이 필요한 요청은 해당 url로 리다이렉트
                            .usernameParameter("memberId") // PrincipalDetailsService에서 userName 대신 memberId 받도록 수정.
                            .loginProcessingUrl("/login") // login 주소가 호출되면 시큐리티가 낚아채서 대신 로그인을 해준다.
                            .defaultSuccessUrl("/"); //로그인 성공시 /주소로 이동
                })
                .build();
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
.formLogin((formLogin) -> {
                    formLogin
                            .loginPage("/loginForm") // 권한이 필요한 요청은 해당 url로 리다이렉트
                            .usernameParameter("memberId") // PrincipalDetailsService에서 userName 대신 memberId 받도록 수정.
                            .loginProcessingUrl("/login") // login 주소가 호출되면 시큐리티가 낚아채서 대신 로그인을 해준다.
                            .defaultSuccessUrl("/"); //로그인 성공시 /주소로 이동
                })

이 부분이 추가됐다.

.loginPage : user 혹은 admin 등의 권한이 필요한 페이지에서 해당 권한을 가지지 않은 사용자가 접속하려 할때 돌려보낼 페이지이다.

.usernameParameter("memberId") : PrincipalDetailsService를 만들어줄건데 여기서 userName을 가지고 로그인할 때 userName 대신 memberId를 받도록 수정해주는 코드이다.

.loginProcessingUrl : /login api로 가려고 할 때 security가 데이터를 낚아채서 대신 로그인을 해준다.

.defaultSuccessUrl : 로그인 성공시 이동할 url이다.

다음 security패키지의 auth패키지 안에 PrincipalDetails를 만들었다.
PrincipalDetails는 로그인을 도와주는 클래스다.
로그인이 완료되면 session이 만들어지는데
session안에는 Authentication 타입만 들어갈수 있고
Authentication 타입 안에는 UserDetails 타입만 들어갈 수 있는데
UserDetails에는 로그인하는 Member 객체가 들어가야 하기 때문에
UserDetails 를 implements 해주는 것이다.

import com.pr.boardproject.member.Member;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;

//시큐리티가 /login 주소 요청이 오면 낚아채서 로그인을 진행시킨다
//로그인이 진행이 완료가 되면 session을 만들어 준다. (Security ContextHolder라는 키값에 세션정보를 저장시킨다. )
// Security ContextHolder에 들어갈 수 있는 객체는 Authentication 타입의 객체만 들어갈 수 있다.
//Authentication 안에는 Member 정보가 있어야 한다.
//Member 오브젝트 타입은 UserDetails 타입의 객체 여야 한다.
//Security Session > Authentication > UserDetails
// PrincipalDetails 클래스에 UserDetails를 implements하면 Authentication에 PrincipalDetails를 넣을 수 있게 된다.
// PrincipalDetails이 UserDetails가 되기 때문
public class PrincipalDetails implements UserDetails {

    private Member member;

    //일반 로그인 시 생성자
    public PrincipalDetails(Member member) {
        this.member = member;
    }

    public Member getMember() {
        return member;
    }

    //해당 유저의 권한을 리턴하는 곳
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //getAuthorities 에는 user의 auth가 들어가야 하는데
        //리턴 타입이 Collection<? extends GrantedAuthority>이기 때문에
        //Collection<? extends GrantedAuthority> 객체를 생성하고 거기에 user.getAuth()를 넣어주었다.

        Collection<GrantedAuthority> collect = new ArrayList<>();
        collect.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return member.getAuth();
            }
        });
        return collect;
    }

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

    @Override
    public String getUsername() {
        return member.getName();
    }

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

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

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

    @Override
    public boolean isEnabled() {
        //일년 동안 로그인을 안하면 휴면 계정으로 하기로 함
        // 현재시간 - 마지막 로그인 시간
        //해서 1년이 지났다면 false 이런식으로 사용할 수 있다.
        return true;
    }

}

auth패키지 안에 PrincipalDetailsService도 만들었다.
PrincipalDetailsService는 PrincipalDetails에서 사용할
로그인 하는 Member 객체를 찾아주기 위한 기능을 한다.


import com.pr.boardproject.member.Member;
import com.pr.boardproject.member.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

// security config 에서 loginProcessingUrl("login"); 되어있는 코드는
// login 요청이 오면 자동으로 UserDetailsService 타입으로 IoC 되어있는
// loadUserByUsername 함수가 실행된다.

@Service
@RequiredArgsConstructor
public class PrincipalDetailsService implements UserDetailsService {

    private final MemberRepository memberRepository;

    // 여기서 리턴하면 Authentication안에 UserDetails가 들어가고 security session에 그 Authentication이 들어가게 된다.
    // security session(내부 Authentication(내부 UserDetails))
    @Override
    public UserDetails loadUserByUsername(String memberId) throws UsernameNotFoundException {
        Member memberEntity = memberRepository.findByMemberId(memberId);
        if(memberEntity != null) {
            return new PrincipalDetails(memberEntity);
        }

        return null;
    }
}
profile
신생아 개발자

0개의 댓글