스프링 시큐리티로 로그인과 회원가입을 하는 방법에  대해 알아보겠습니다.

 

먼저 시큐리티 의존성을 추가해줘야 됩니다.

 

※ bulid.gradle

implementation 'org.springframework.boot:spring-boot-starter-security'

의존성을 추가해준 뒤 실행시켜보면

이런 화면이 나옵니다. 이거는 시큐리티에서 제공하는 로그인 페이지입니다.

 

※ 회원가입과 로그인을 하기 위한 User 엔티티

 

import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.sql.Timestamp;

@Entity
@Data
public class User {
    @Id
    @GeneratedValue
    private int id; // 기본키
    private String username; // 사용자 아이디
    private String password; // 사용자 비밀번호 
    private String email; // 사용자 이메일
    private String role; // 사용자 권한
    
    @CreationTimestamp
    private Timestamp createDate; // 회원가입한 날짜
}

※ Controller, Repository

public interface UserRepository extends JpaRepository<User, Integer> {

}


@Controller
@RequiredArgsConstructor

public class UserController {

    private final UserRepository userRepository;
   
    @GetMapping({"", "/"})
    public String index() {
        return "index";
    }

    @GetMapping("/user")
    public @ResponseBody String user() {
        return "user";
    }

    @GetMapping("/admin")
    public @ResponseBody String admin() {
        return "admin";
    }

    @GetMapping("/manager")
    public @ResponseBody String manager() {
        return "manager";
    }

    @GetMapping("/login")
    public String loginForm() {
        return "login";
    }


    @GetMapping("/joinForm")
    public String joinForm() {
        return "joinForm";
    }

    @PostMapping("/join")
    public String join(User user) {
        return "redirect:/login";
    }



}

 

▲ 참고 : UserRepository에 JpaRepository를 상속받아서 @Repository는 생략해도 스프링 빈으로 등록이 됩니다.

 

 

※ 시큐리티 설정

 

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests()
                .antMatchers("/user/**").authenticated()
                .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
                .anyRequest().permitAll()
                .and()
                .formLogin()
                .loginPage("/loginForm")
                .loginProcessingUrl("/login") 
                .defaultSuccessUrl("/");
               
    }
}

 

  • @Configuration : 시큐리티에 대한 설정을 우선 스프링 빈으로 등록합니다.
  • @EnableWebSecurity : 스프링 시큐리티 필터가 스프링 필터 체인에 등록됩니다.
  • WebSecurityConfigurerAdapter을 상속받은 뒤 configure 메서드를 재정의 합니다.
  • http.authorizeRequests() : 인가 요청을 받습니다.
  • antMatchers("url주소"). authenticated()  : url 주소를 입력했을 때 인증된 사용자만 url 주소를 이용할 수 있습니다.
  • access("권한") : 사용자의 권한이 access에 정해놓은 권한이 있다면 해당 url주소를 이용할 수 있습니다.
  • anyRequest.permitAll() : 위에 설정해놓은 url주소 말고 다른 url 주소들은 아무 조건 없이 url에 접속하는 것을 허용합니다.
  • formLogin() :  폼 로그인을 이용할 수 있습니다.
  • loginPage("로그인 페이지 경로") : 만든 로그인 페이지 경로
  • loginProcessingUrl("시큐리티 로그인 경로") : 시큐리티 로그인 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행합니다.
  • defaultSuccessUrl("로그인성공 시 디폴트 경로") : 로그인 성공시 디폴트 화면으로 이동을 하고 만약 user url로 접속을 시도했으면 로그인 성공 후 user url로 이동됩니다. 
  • BCryptPasswordEncoder : 비밀번호를 암호화합니다. controller에 빈으로 등록된 BCryptPasswordEncoder를 자동 주입을 해줍니다.

 

※ 사용자 인증

public class PrincipalDetails implements UserDetails {
    private User user;

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

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

    @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이고 값은 Authentication 타입의 객체입니다. Authentication 타입 객체에는 User 정보가 담겨 있어야 하는데 그 User정보는 UserDetails 타입 객체에 담겨 있습니다.
  • UserDetails : 시큐리티에서 제공하는 인터페이스입니다.
  • getAuthorities() : User의 권한을 리턴을 합니다.
  • getPassword()  : User의 비밀번호를 리턴을 합니다.
  • getUsername() :  User의 아이디를 리턴을 합니다.
  • isAccountNonExpired() : 계정이 만료되었는지 여부를 리턴합니다.
  • isAccountNonLocked() : 계정이 잠겼는지 여부를 리턴합니다.
  • isCredentialsNonExpired()  : 계정의 비밀번호가 오래 사용했는지에 대한 여부를 리턴합니다.
  • isEnabled() : 계정의 활성화 여부를 리턴합니다.
@Service
public class PrincipallDetailsService implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;

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

 

  • 시큐리티 설정에서 loginProcessingUrl("/login")으로 요청이 오면 자동으로 UserDetailsService 타입으로 Ioc가 되어있는 loadUserByUsername함수가 실행이 됩니다.
  • loadUserByUsername의 함수에서 매개 변수인 username은 html일 경우 name ="username"과 동일해야 됩니다.

 

비밀번호 암호화

// Controller 코드

@PostMapping("/join")
public String join(User user) {
    String rawPassword = user.getPassword();
    String encPassword = bCryptPasswordEncoder.encode(rawPassword);
    user.setPassword(encPassword);
    userRepository.save(user);
  }
}
  • 비밀번호를 1234로 하였을 경우 알 수 없는 문자들로 데이터베이스에 저장이 됩니다.

 

결과

사용자 등급이 user일 경우 /user/**에 대한 url에 접근이 가능하고 manager일 경우 /manager/**, /admin/**, /user/**에 접근이 가능하고 admin일 경우 /user/**, /admin/**에 접근이 가능합니다. 만약 인증이 안된 사용자는 방금 설명드린 url를 제외한 부분에만 접근이 가능합니다.