Spring Boot 프로젝트 진행 중 두개의 로그인 페이지(User, Admin)를 각각 구현해야 하는 이슈가 발생해서 정리하고자 합니다.
1. Maven Dependencies 추가
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>5.7.2</version>
</dependency>
- 마지막 버전 확인
2. SecurityConfig 설정
@Configuration
@EnableWebSecurity
public class SecurityConfig {
... // 내부클래스로 User와 Admin 관련 클래스를 작성할 것이다.
}
- @Configuration
- 스프링 설정 클래스를 선언하는 어노테이션. xml 설정을 대체할 수 있는 Java Config로 여러 설정들을 자바 클래스 파일로 사용하겠다는 의미
- @EnableWebSecurity
- 해당 어노테이션을 작성해야지 Security를 활성화 시킬 수 있습니다.
- 해당 어노테이션을 자세히 보면(아래 코드) WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, HttpSecurityConfiguration.class들을 import해서 실행시켜주는 것을 알 수 있습니다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
HttpSecurityConfiguration.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
/**
* Controls debugging support for Spring Security. Default is false.
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;
}
이제 Admin 사용자에 대한 Security 코드입니다. 위에서 만든 SecurityConfig.java 안에 작성합니다.
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.WebSecurityConfigurerAdapter;
@Configuration
@Order(1)
public class SpringBootAdminSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("{noop}password").roles("ADMIN");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/admin/**")
.authorizeRequests().anyRequest().authenticated()
.and().formLogin().loginPage("/admin/login")
.defaultSuccessUrl("/admin/dashboard", true)
.permitAll()
.and().logout().logoutUrl("/admin/logout").logoutSuccessUrl("/admin/login");
http.csrf().disable();
}
}
configurer 메소드안에 코드는 사용자 권한이 없는 상태에서 /admin으로 시작하는 url로 요청이 올 경우 /admin/login 페이지로 이동 후, 로그인 성공 시 /admin/dashboard로 이동합니다.
User 사용자에 대한 코드도 SecurityConfig.java 안에 작성합니다.
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.WebSecurityConfigurerAdapter;
@Configuration
@Order(2)
public class SpringBootUserSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("{noop}password").roles("USER");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**")
.authorizeRequests().anyRequest().authenticated()
.and().formLogin().loginPage("/login")
.defaultSuccessUrl("/dashboard", true)
.permitAll()
.and().logout().logoutSuccessUrl("/login");
http.csrf().disable();
}
}
사용자 권한이 없는 상태에서 의에 설정한 /admin을 제외한 모든 url로 요청이 올 경우 /login 페이지로 이동 후, 로그인 성공 시 /dashboard로 이동합니다.
각 정적 클래스에 @Order 주석을 배치하여 URL이 요청될 때 일치하는 패턴을 기반으로 두 클래스가 고려되는 순서를 지정합니다. 두 구성 클래스는 동일한 순서를 가질 수 없습니다.
3. Controller
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping
public class TestController {
@RequestMapping("/")
public ModelAndView defaultHome() {
return new ModelAndView("login");
}
@RequestMapping("/login")
public ModelAndView login() {
return new ModelAndView("login");
}
@RequestMapping("/dashboard")
public ModelAndView userDashboard() {
return new ModelAndView("dashboard");
}
@RequestMapping("/admin/")
public ModelAndView admin() {
return new ModelAndView("admin/login");
}
@RequestMapping("/admin/login")
public ModelAndView adminlogin() {
return new ModelAndView("admin/login");
}
@RequestMapping("/admin/dashboard")
public ModelAndView admindashboard() {
return new ModelAndView("admin/dashboard");
}
}
4. Jsp File
<!-- User Login Form -->
<form method="POST" action="/login">
User Name : <input type="text" name="username" value="user"/><br><br>
Password : <input type="password" name="password" value="password"/><br><br>
<input type="submit" name="submit"/>
</form>
<!-- Admin Login Form -->
<form method="POST" action="/admin/login">
User Name : <input type="text" name="username" value="admin"/><br><br>
Password : <input type="password" name="password" value="password"/><br><br>
<input type="submit" name="submit"/>
</form>
주의점
- 사용자 분류를 anonymous(public), user(private), admin(admin)으로 구분하고자 하면 각 권한별로 WebSecurityConfigurerAdapter파일을 구현해야한다. user와 admin은 위의 예제와 같고, anonymous는 http 설정만 해주면 됩니다.
- 위의 예제 코드는 http.antMatcher("/url")로 구현하고 있는데, http.authorizeRequests().antMatchers()로 구현하게 되면 모든 http.antMatcher() 설정을 무시하고 http.authorizeRequests().antMatchers() 설정만 작동합니다.
- 사용자 페이지와 관리자 페이지가 다른 경우, 설정을 따로 하기보다 프로젝트를 구분하는 것이 관리 측면에서 더 나은 방법인 것 같습니다.
'Programming > Spring Boot' 카테고리의 다른 글
[Spring Security] 수동 인증(강제 로그인) (0) | 2023.05.16 |
---|---|
Spring AOP를 활용한 모든 Request에 Log 남기기 (0) | 2023.03.29 |
[Jasypt] Spring Boot 애플리케이션의 프로퍼티 암호화 (0) | 2023.03.24 |
[Maven] 라이브러리 Dependency 충돌 해결하기(Maven Tree) (0) | 2023.03.02 |
[Mybatis] PostgreSQL ilike 구현하기(feat. Criteria 커스텀하기) (0) | 2022.08.20 |