본문 바로가기
Spring/Security

[Security] 로그인과 권한 설정

by 행운의나무 2022. 11. 1.
728x90
반응형

주의 : Spring Security 5.7+ (Spring Boot 2.7+)부터는 WebSecurityConfigurer이 Deprecated 되었다.

2022.09.05 - [Spring/Security] - [Security] WebSecurityConfigurerAdapter Deprecated

 

[Security] WebSecurityConfigurerAdapter Deprecated

버전 Java 11 Spring boot 2.7.3 Deprecated Spring Security 5.7.0-M2부터 Deprcated 되었다. (SpringBoot 기준 2.7 이후) WebSecurityConfigurerAdapter : WebSecurityConfigurer의 인스턴스를 생성하여 Abstrac..

twer.tistory.com

  • 목표 : 어드민 계정의 역할로 superadmin, admin을 구분합니다..
    • superadmin은 superadmin 페이지와 admin 페이지를 액세스할 수 있습니다.
    • admin은 admin 페이지만 엑세스 할 수 있습니다.
  • 생성 파일
    • Config
      • SecurityConfig.java
    • Controller
      • AdminController.java
      • LoginController.java
    • Model
      • Role.java : AdminUser의 역할을 Enum으로 관리
      • AdminUser.java
      • AdminUserRepository.java
    • Service
      • LoginService.java
    • Templates(화면)
      • loginForm.mustache
      • admin.mustache
      • superadmin.mustache
      • accessDenied.mustache

Model 생성

Role.java

  • 사용자에게 권한을 부여하기 위해 Enum타입으로 관리합니다.
  • 나중에 만들 Config파일에 hasRole()에 관련된 역할을 정의합니다.
    • SUPERADMIN과 ADMIN으로 구분하였습니다.
    • SUEPRADMIN은 슈퍼 어드민 페이지와 어드민 페이지에 접근할 수 있도록 Config에서 설정할것입니다.
@AllArgsConstructor
@Getter
public enum Role {
    SUPERADMIN("ROLE_SUPERADMIN,ROLE_ADMIN"),
    ADMIN("ROLE_ADMIN");

    private String value;
}

AdminUser.java

  • UserDetails를 구현한 객체입니다.
    • UserDetails는 Spring Security에서 UserDetailsService가 권한 정보를 알 수 있도록 설정해주는 인터페이스 입니다.
    • UserDetailsService는 LoginService가 구현합니다.
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class AdminUser implements UserDetails {

    @Id
    @GeneratedValue(strategy =  GenerationType.IDENTITY)
    private Long id;

    private String adminId;

    private String password;

    private String adminName;

    private String role;

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();

        for(String role : role.split(",")){
            authorities.add(new SimpleGrantedAuthority(role));
        }
        return authorities;
    }

    @Override
    public String getUsername() {
        return adminId;
    }

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

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

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

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

AdminUserRepository.java

  • JPA를 사용하여 데이터베이스에 접근할 수 있도록 Repository를 구성했습니다.
  • UserDetails에서 데이터베이스의 정보를 가져오게 하기 위해 기본적으로 findByUsername()을 사용하지만, 저는 findByAdminId()를 만들어 사용했습니다.
@Repository
public interface AdminUserRepository extends JpaRepository<AdminUser, Long> {
    Optional<AdminUser> findByAdminId(String adminId);
}

Service 생성

LoginService.java

  • UserDetailsService를 구현하였습니다.
  • loadUserByname으로 데이터베이스에서 정보를 가져와 UserDetailsService에게 전달합니다. (DaoAuthenticationProvider 방식)
@Service
@Slf4j
public class LoginService implements UserDetailsService {
      private AdminUserRepository adminUserRepository;

    public LoginService(AdminUserRepository adminUserRepository) {
        this.adminUserRepository = adminUserRepository;
    }

  @Override
    public UserDetails loadUserByUsername(String adminId) throws UsernameNotFoundException {

        //adminUser 정보 조회
        Optional<AdminUser> adminUser = adminUserRepository.findByAdminId(adminId);

        if(adminUser.isPresent()) {
            AdminUser admin = adminUser.get();

            AdminUser authAdmin = AdminUser.builder()
                    .id(admin.getId())
                    .adminId(admin.getAdminId())
                    .password(admin.getPassword())
                    .role(admin.getRole())
                    .adminName(admin.getAdminName())
                    .createdAt(admin.getCreatedAt())
                    .updatedAt(admin.getUpdatedAt())
                    .build();

            log.info("authAdmin : {}", authAdmin);
            return authAdmin;
        }
        return null;
    }
}

Controller 생성

LoginController.java

@Slf4j
@Controller
public class LoginController {
    @GetMapping({"/", ""})
    public String loginForm(){
        return "loginForm";
    }
}

AdminController.java

@Controller
@RequestMapping("")
public class AdminController {


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

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

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

Config 생성

SecurityConfig.java

  • Spring Security를 설정하는 파일입니다.
    • http.csrf().disable() : csrf를 비활성화 시킵니다.
      • csrf란 의도하지 않은 POST, PUT 등으로 사이트가 공격받는 것을 말합니다.(웹사이트와 관련된 부분으로 이 글에서는 자세한 설명은 넘어가겠습니다.)
    • authorizeRequests() : 권한을 설정하는 부분입니다.
      • .antMatchers("/").permitAll() : "/" 페이지에 대해 모든 접근을 허용합니다.
      • .antMatchers("/superadmin", "/admin").hasRole("SUPERADMIN") : SUPERADMIN은 슈퍼 어드민페이지와 어드민 페이지에 접근할 수 있습니다.
      • .antMatchers("/admin").hasRole("ADMIN") : ADMIN은 어드민 페이지에 접근할 수 있습니다.
      => 여기서 hasRole()은 Model에서 정의한 Role의 값입니다. Spring Security에서는 "ROLE_SUPERADMIN,ROLE_ADMIN" 처럼 ROLE_XXX로 정의해야하며, hasRole에서는 ROLE_을 제외한 나머지 부분을 넣어줍니다.
      @Override
      public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
      
        for(String role : role.split(",")){
          authorities.add(new SimpleGrantedAuthority(role));
        }
        return authorities;
      }
    • => SUPERADMIN의 경우 두 가지 권한을 가지고 있는데, 이것을 구분하는 코드는 AdminUer.java에서 구분하여 권한을 넣어줍니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private LoginService loginService;

    public SecurityConfig(LoginService loginService) {
        this.loginService = loginService;
    }

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

    @Override
    public void configure(HttpSecurity http) throws Exception {

        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/superadmin", "/admin").hasRole("SUPERADMIN")
                .antMatchers("/admin").hasRole("ADMIN")
                .and()
                .formLogin()
                .loginPage("/")
                .defaultSuccessUrl("/admin")
                .usernameParameter("adminId")
            .and()
                .logout()
                .logoutSuccessUrl("/")
                .invalidateHttpSession(true) //세션 날리기
            .and()
                .exceptionHandling()
                .accessDeniedPage("/accessDenied");

    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(loginService).passwordEncoder(passwordEncoder());
    }
}

화면 만들기

loginForm.mustache

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8"/>
    <title>Login Page</title>
</head>
<body>
<h1> Index Page </h1>
<hr/>
<form action="/" method="post">
    <input type="text" name="adminId" placeholder="myadmin"/>
    <input type="password" name="password"/>
    <button type="submit">Login</button>
</form>
</body>
</html>

admin.mustache

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8"/>
    <title>Admin Page</title>
</head>
<body>
<h1> Admin Page </h1>
<form action="/logout">
<button type="submit">logout</button>
</form>
</body>
</html>

superadmin.mustache

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8"/>
    <title>Super Page</title>
</head>
<body>
<h1> Super Page </h1>
<form action="/logout">
    <button type="submit">logout</button>
</form>
</body>
</html>

accessDenied.mustache

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8"/>
    <title>AccessDenied Page</title>
</head>
<body>
<h1> AccessDenied Page </h1>
<form action="/logout">
    <button type="submit">logout</button>
</form>
</body>
</html>

테스트

  1. admin 계정으로 로그인을 실행한다.

    1. admin 페이지를 확인한다.
    2. superadmin 페이지로 접근해본다.(주소창에서 /superadmin 입력)
  2. superadmin 계정으로 로그인을 실행한다.
    1. superadmin 페이지를 확인한다.
    2. admin 페이지로 접근하여 페이지를 확인한다.

1. admin 로그인
1-1 Admin페이지 확인
1-2 superadmin접근 시 액세스 거부화면 출력
2-1 superadmin 계정으로 로그인 시 admin 접근
2-1 superadmin 계정으로&nbsp; superadmin 접근

Spring Security를 활용하여 간단한 인증, 권한을 만들어 보았습니다. Spring Security를 이용해 복잡한 인증과 권한 절차를 쉽게 만들어 낼 수 있었습니다.

Spring Security에 대한 아키텍처도 정리하였습니다. 같이보시면서 흐름을 이해하시는데 도움이 될거 같습니다.

2021.03.25 - [Spring/Security] - [Security] 스프링 시큐리티의 아키텍처(구조) 및 흐름

 

[Security] 스프링 시큐리티의 아키텍처(구조) 및 흐름

Spring Security 스프링 시큐리티리란? 어플리케이션의 보안(인증 및 권한)을 담당하는 프레임워크 Spring Security를 사용하지 않으면 자체적으로 세션을 체크해야 한다. redirect를 일일이 설정해주어야

twer.tistory.com

JWT를 이용한 로그인 방식

2022.10.28 - [Spring/Security] - [JWT] 회원가입 / 로그인 / 토큰 발급 (1)

 

[JWT] 회원가입 / 로그인 / 토큰 발급 (1)

목표 회원가입 기능 로그인 기능 로그인 시 JWT 토큰 발급 JWT 인증, 인가 필터 생성 JWT의 회원 정보 가져오기 환경 java 17 spring boot 2.7 gradle 7.5 환경 설정 1. build.gradle - Web, Security, Lombok, JP..

twer.tistory.com

 

쿠팡으로 연결 클릭

 

제주삼다수 그린

COUPANG

www.coupang.com

파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음

반응형