모르는게 많은 개발자

[SpringBoot] spring-security 회원가입/로그인/권한 예제 본문

스프링

[SpringBoot] spring-security 회원가입/로그인/권한 예제

Awdsd 2020. 10. 2. 22:54
반응형

최근 인턴을 통해 PHP Laravel을 이용해 개인 프로젝트를 진행했고, 이것을 Spring Boot로 Migration하는 작업을 진행하고 있다. 그리고 회원 기능이 필요하여 Spring Security를 공부하여 간단하게 회원기능을 구현했다.

이번 글에서 spring security 간단 구현을 통해 어떠한 방식으로 구현되는지 정리해보려 한다. spring-security에 대한 자세한 개념은 추후 더 공부하여 정리하려한다. 이번 글에서는 구현자체에 포커스를 맞춘다.


1. 완성 상태

먼저 예제 결과부터 보자

회원가입 페이지

위처럼 간단한 회원가입을 진행할 수 있는 페이지가 있다.

로그인 페이지

로그인 페이지는 spring security에서 제공하는 로그인 페이지를 사용한다.

회원가입한 아이디로 로그인시 유저 권한을 얻은 사람만 접속가능한 페이지에 로그인한 이메일을 출력한다.

유저 페이지

전체 프로젝트 구조는 다음과 같다.

프로젝트 구조


2. gradle, application.yml

gradle

gradle위와 같다. Spring Security, JPA, Thymeleaf을 사용하고 DB는 Mysql을 사용한다.

Mysql 설정과 JPA 설정을 해준다. ddl-auto는 JPA를 통해 테이블을 테이블을 자동으로 생성하는 설정이다.


3. HTML 구성

회원가입 페이지와 로그인시 유저 페이지는 다음과 같다.

<!--회원가입 페이지-->
<!--signup.html-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <meta charset="UTF-8">
</head>
<body>
    <form method="post" action="/signUp">
        email : <input type="email" name="email">
        password : <input type="password" name="password">
        <button>회원가입</button>
    </form>
</body>
</html>
<!--유저 페이지-->
<!--user_access.html-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <meta charset="UTF-8">
    <title>user access</title>
</head>
<body>
<span>환영합니다</span>
<p th:text="${info}"></p>
</body>
</html>

4. Entity

JPA를 사용하므로 User Entity를 만든다.

@Entity
@Getter
@Setter
public class User {
	@Id
	@GeneratedValue
	private Long id;
	private String email;    //이메일
	private String password; //패스워드
	private String role;     //권한
}

권한값인 role에는 "USER"라는 값이 들어가게된다.(추후 ADMIN등 다른 권한도 추가가능)


5. Security Config 작성

spring security를 설정하기 위한 클래스를 작성해야한다. 기본적으로 WebSecurityConfigurerAdapter라는 클래스를 상속받아 여러가지 설정을 할 수 있다.

WebSecurityConfigurerAdapter클래스는 spring security의 filter chain을 적용한 WebSecurity 클래스를 Custom할 수 있는 WebSecurityConfigurer클래스를 편리하게 생성할 수 있는 클래스다.

다시 말해 WebSecurityConfigurerAdapter -> WebSecurityConfigurer->WebSecurity순대로 클래스를 사용한다.

우린 WebSecurityConfigurerAdapter를 사용해 Spring Security를 설정하고 적용한다.

package com.cjw.security.auth;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;

import com.cjw.security.service.ExService;
import lombok.RequiredArgsConstructor;


@EnableWebSecurity		//spring security 를 적용한다는 Annotation
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	private final ExService exService;
	/**
	 * 규칙 설정
	 * @param http
	 * @throws Exception
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
    	    http.authorizeRequests()
            .antMatchers("/userAccess").hasRole("USER")
            .antMatchers("/signUp").anonymous()
            .and()
            .formLogin()
            .and()
            .csrf().disable();		//로그인 창
	}

	/**
	 * 로그인 인증 처리 메소드
	 * @param auth
	 * @throws Exception
	 */
	@Override
	public void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(exService).passwordEncoder(new BCryptPasswordEncoder());
	}
}

WebSecurityConfigurerAdapter에는 다양한 메소드를 override를 통해 재정의를 할 수 있다.

이번 글에서는 configure(HttpSecurity http), configure(AuthenticationManagerBuilder auth) 두개의 메소드를 이용해 security를 적용한다.

  • configure(HttpSecurity http)
    이 메소드는 HttpSecurity 객체를 이용해 각 요청을 먼저 intercept하여 URL별 인증 여부, login 처리, logout아웃 처리등 다양한 처리를 할 수 있다.

    - antMachers : 각 URL 요청에 대한 접근 여부를 설정한다. 위 같은 경우 /userAccess에 접근할 경우 hasRole()을 통해 USER라는 권한을 가진 유저만 접근할 수 있다. anonymous()은 인증되지 않은 즉, 로그인 되지 않은 사용자만 접근 가능하다.

    - formLogin() : spring security에서 제공하는 login form을 이용한다는 뜻. 로그인 성공시 '/'로 리다이렉트

    - csrf() : 웹 사이트의 취약점을 이용한 의도치 않은 요청을 통한 공격을 의미한다. 이 기능을 disable한 상태이다.

  • configure(AuthenticationManagerBuilder auth)
    이 메소드는 AuthenticationManagerBuilder 객체를 통해 인증 객체 생성을 기능을 제공한다. exService는 뒤에서 다룰 UserDetails인터페이스를 상속받은 객체이고 이것을 이용해 로그인 된 사용자의 데이터를 관리한다. 
    passwordEncoder()는 로그인때 입력한 패스워드를 암호화 처리한다.

6. UserDetail, UserDetailService

UserDetails

위에서 exService객체는 UserDetails인터페이스를 상속받는다 했다.

Spring Security는 사용자 정보를 UserDetails라는 인터페이스 구현체로 사용한다. 즉, UserDetails는 사용자 정보 VO라고 생각하면 된다.

public class MyUserDetail implements UserDetails {
	private String email;
	private String password;
	private String auth;

	public MyUserDetail(User user) {
		this.email = user.getEmail();
		this.password = user.getPassword();
		this.auth = "ROLE_" + user.getRole();
	}

	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		return Collections.singletonList(new SimpleGrantedAuthority(this.auth));
	}

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

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

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

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

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

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

 UserDetails구현체인 MyUserDetail은 밑에 구현할 UserDetailService인터페이스 구현체를 통해 생성된다. 생성자에는 User Entity를 인자로 받아 유저 정보를 세팅한다.

각 Override Method는 다음과 같은 기능을 한다.

  • getAuthorities() : 계정이 가지고 있는 권한 목록 반환
  • getPassword() : 계정의 비밀번호 반환
  • getUsername() : 계정의 이름 반환
  • isAccountNonExpired() : 계정 만료 여부(false = 만료)
  • isAccountNonLocked() : 계정 잠김 여부(false = 잠김)
  • isCredentialNonExpired() : 계정 비밀번호 만료 여부(false = 만료)
  • isEnabled() : 계정 활성화 여부(false = 비활성)
UserDetailService

UserDetailService 인터페이스는 DB에서 유저 정보를 불러오는 메소드인 loadUserByUsername()이 있다. 이 메소드 구현을 통해 DB에서 유저 정보를 불러온다.

@Service
@RequiredArgsConstructor
@Slf4j
public class ExService implements UserDetailsService {
	private final ExRepository repository;

	@Transactional
	public void joinUser(User user){
		BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
		user.setPassword(passwordEncoder.encode(user.getPassword()));
		repository.saveUser(user);
	}

	@Override
	public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
		//여기서 받은 유저 패스워드와 비교하여 로그인 인증
		User user = repository.findUserByEmail(email);
		return new MyUserDetail(user);
	}
}

JPA를 이용하기 때문에 loadUserByUsername 메소드에서 User Entity 객체를 가져와 MyUserDetail객체를 반환한다.

이 메소드는 login을 시도 했을 때 실행된다. email인자는 loginForm에서 입력 받은 email을 받는다.


7. 회원가입

spring security적용전에 유저 회원가입부터 구현해보자.

회원가입 창에서 이메일과 비밀번호를 입력하고 회원가입 버튼을 누르게 되면

@Controller
@RequiredArgsConstructor
@Slf4j
public class ExController {
    private final ExService service;

    /**
     * 회원가입 폼
     * @return
     */
    @GetMapping("/signUp")
    public String signUpForm() {
        return "signup";
    }

    /**
     * 회원가입 진행
     * @param user
     * @return
     */
    @PostMapping("/signUp")
    public String signUp(User user) {
        user.setRole("USER");
        service.joinUser(user);
        return "redirect:/login";
    }
}

 입력받은 email과 password가 User entity에 저장되고 권한을 USER로 설정하고 joinUser를 통해 DB에 등록된다.
service 객체는 위의 UserDetailService이다.

간단 예제를 위해 예외처리는 따로 구현하지 않고 진행한다. 회원가입이 완료되면 login페이지로 리다이렉트한다.


8. Controller

이제 security에 대한 기본적인 구현을 모두 맞췄으므로 로그인시 이동할 userAccess를 구현해보자

@Controller
@RequiredArgsConstructor
@Slf4j
public class ExController {
    private final ExService service;

    /**
     * 회원가입 폼
     * @return
     */
    @GetMapping("/signUp")
    public String signUpForm() {
        return "signup";
    }

    /**
     * 회원가입 진행
     * @param user
     * @return
     */
    @PostMapping("/signUp")
    public String signUp(User user) {
        user.setRole("USER");
        service.joinUser(user);
        return "redirect:/login";
    }

    /**
     * 유저 페이지
     * @param model
     * @param authentication
     * @return
     */
    @GetMapping("/")
    public String userAccess(Model model, Authentication authentication) {
        //Authentication 객체를 통해 유저 정보를 가져올 수 있다.
        MyUserDetail userDetail = (MyUserDetail)authentication.getPrincipal();  //userDetail 객체를 가져옴
        model.addAttribute("info", userDetail.getUsername());      //유저 이메일
    	return "user_access";
    }
}

서버를 실행하면 JPA를 통해 mysql 테이블이 만들어지고
http://localhost:8080/signUp을 통해 회원가입을 진행해 login페이지에서 로그인을 하면 userAccess를 통해 user페이지로 이동하는 결과를 볼 수 있다. 
 

 

 

만약, spring security의 인증 과정을 자세히 알고싶으면 아래 글을 참고하길 바란다.

cjw-awdsd.tistory.com/45

 

[스프링] Spring Security 인증은 어떻게 이루어질까?

예전 포스팅에서 security 관련 예제를 다룬적 있다. 당시에는 내부 프로세스를 모르고 예제를 보면서 UserDetails, UserDetailsService, Authentication만 커스텀해서 인증을 다뤘다. 이번 토이프로젝트에서 OAu

cjw-awdsd.tistory.com


Github 전체 코드

github.com/CJW23/Spring_Security_JPA_Example

 

참고

to-dy.tistory.com/86

galid1.tistory.com/576

postitforhooney.tistory.com/entry/SpringSecurity-%EC%B4%88%EB%B3%B4%EC%9E%90%EA%B0%80-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-Spring-Security-%ED%8D%BC%EC%98%B4

반응형
Comments