[Spring]Spring Security_4

김피자·2023년 3월 10일
0

Spring

목록 보기
19/30
post-thumbnail

이전 글에서 시큐리티를 적용하여 사용자가 입력한 암호를 인코딩하는 작업을 진행하였다. 이번 글에서는 이제 이 암호화 된 암호로 로그인하는 코드를 구현해보자!!


SecurityConfig 수정

@Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests()
                .antMatchers("/user/**").authenticated()
                .antMatchers("/manager/**").access("hasAnyRole('ROLE_MANAGER','ROLE_ADMIN')")
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
                .anyRequest().permitAll()
                .and()
                .formLogin()
                .loginPage("/loginForm")
                // 추가
                .loginProcessingUrl("/login") 
                .defaultSuccessUrl("/");

        return http.build();

// "/login"이라는 url이 호출되면 시큐리티가 낚아채 대신 로그인을 진행해준다.
그래서 Controller에 /login을 호출하는 메소드를 만들지않아도 된다.


loginForm.html 수정


<h1>로그인 페이지</h1>
<hr/>
<form action="/login" method="POST"> // 추가
    <input type="text" name="username" placeholder="Username"/> <br/>
    <input type="password" name="password" placeholder="Password"/> <br/>
    <button>로그인</button>
</form>

입력한 아이디, 패스워드를 가지고 /login으로 이동한다.


config 패키지 안 auth 패키지 생성

security01 > config > auth

PrincipalDetails 생성

package com.cos.security01.config.auth;

import com.cos.security01.model.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;

public class PrincipalDetails implements UserDetails {

    private User user; //콤포지션

    public PrincipalDetails(User user){
        this.user = user;
    }

    // 해당 유저의 권한을 리턴한다.
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // 우리가 지정한 Role은 String타입이라 user.getRole(); 하여 지정할 수 없다.

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


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

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

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

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

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

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

앞선 설정을 통해 시큐리티가 /login이라는 요청을 낚아 채 로그인을 진행할 수 있도록 했다.

로그인이 완료되면 세션이 만들어지는데 시큐리티는 시큐리티 자신만의 세션 저장공간을 가지고있다.
이때, Security ContextHolder라는 Key값에 세션 정보를 저장시키는데 여기에 들어갈 수 있는 정보는 오브젝트 타입은 Authentication타입으로 정해져있다.

또, 이 Authentication 안에는 User정보가 있어야하는데 그 유저 오브젝트의 타입은 UserDetails타입이어야한다.

위에 true true라고 적어놓은 것들은 일단 신경 안쓰기위해 true로 지정하였는데 false가 되는 경우를 설명하면, 우리 사이트에 1년동안 로그인 하지않은 회원을 휴먼계정 처리 하기로했다면? 저기에 현재시간 - 최종로그인 시간을 해 일년을 초과하면 false를 리턴하게 로직을 짤 수 있다.


PrincipalDetailsService 생성

시큐리티 설정에서 loginProcessingUrl을 ("/login")으로 지정해놧는데 이 요청이 오면 자동으로 UserDetailsService로 IoC되어있는 loadUserByUsername 함수가 실행된다.

package com.cos.security01.config.auth;

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;

@Service
public class PrincipalDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
        return null;
    }
}

UserDetails에 보면 넘어오는 파라미터 이름이 username인 것이 보인다.
loginForm에서 name으로 지정햇던 username과 이 이름은 동일해야하고 만약 이가 다르면 인식하지 못한다.
그럼에도 loginForm에서 name을 굳이 다른 이름으로 지정하고자한다면??

// SecurityConfig에 추가
	.usernameParameter("newName");

PrincipalDetailsService까지의 과정을 살펴보면

  1. loginForm에서 로그인 버튼을 눌러 /login으로 이동
  2. Spring은 IoC 컨테이너에서 UserDetailsService로 작성된 클래스가 있는지 찾음
  3. 있다면 거기서 loadUserByUsername메소드를 호출하는데 이때 넘어온 파라미터 username을 가지고온다.

PrincipalDetailsService 수정


@Service
public class PrincipalDetailsService implements UserDetailsService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User userEntity = userRepository.findByUsername(username);
        return null;
    }
}

파라미터로 넘어온 username을 가지고 그 이름이 DB에 존재하는지 확인하기위해 repository에 findByUsername을 호출한다.

UserRepository 수정

public interface UserRepository extends JpaRepository <User, Integer> {

    public User findByUsername(String username);
}

findBy까지는 규칙이고 Username 문법인데(자세한건 JPA QueryMethods 검색) 아무튼 이렇게 하면 호출 시 아래 쿼리문이 실행된다.

select * from user where username = ? (username)

PrincipalDetailsService 수정

@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
        User userEntity = userRepository.findByUsername(username);
        
        if(userEntity!=null){
        
        	return new PrincipalDetails(userEntity);
        }
    }

repository에서 username을 검색하고 해당 값이 null이 아니라면 PrincipalDetails로 간다.

아까 앞에서 설명할 때
시큐리티에 세션 정보를 저장시키기 위해서는 Authentication타입이 들어가야하고, 또 이 Authentication 안에는 User정보가 있어야하는데, 그 타입은 UserDetails타입이라 했다.

위에서처럶 PrincipalDetails(userEntity)를 리턴하게되면 리턴 된 값이 Authentication내부에 들어가고, 세션 내부에는 다시 Authentication가 들어가게된다.

SecuritySession(Authentication(UserDetails))


실행

이제 회원가입과 패스워드 인코딩 그리고 로그인이 잘 되는지 확인해보자.

localhost:8080/user url을 입력하면

로그인 폼 페이지로 이동한다.

콘솔

User(id=0, username=posasac, password=1234, email=posasac@aaa.com, role=null, createDate=null)
Hibernate: 
    insert 
    into
        User
        (createDate, email, password, role, username) 
    values
        (?, ?, ?, ?, ?)
success
User(id=3, username=posasac, password=$2a$10$6Gd6rjqwSgcV5FG3qKXAgurKsFfzDOC0ExonoGLXpDTm7VvpKNVZW, email=posasac@aaa.com, role=ROLE_USER, createDate=2023-03-10 13:21:56.211)

회원가입과 패스워드 인코딩 처리가 잘 된 것을 볼 수 있다.

가입시 입력한 이름과 비밀번호로 로그인을 시도하면

정상적으로 로그인 성공@!!!!@!@

profile
제로부터시작하는코딩생활

0개의 댓글