728x90
반응형
주의 : Spring Security 5.7+ (Spring Boot 2.7+)부터는 WebSecurityConfigurer이 Deprecated 되었다.
2022.09.05 - [Spring/Security] - [Security] WebSecurityConfigurerAdapter Deprecated
- 목표 : 어드민 계정의 역할로 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
- Config
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은 어드민 페이지에 접근할 수 있습니다.
@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에서 구분하여 권한을 넣어줍니다.
- http.csrf().disable() : csrf를 비활성화 시킵니다.
@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>
테스트
- admin 계정으로 로그인을 실행한다.
- admin 페이지를 확인한다.
- superadmin 페이지로 접근해본다.(주소창에서 /superadmin 입력)
- superadmin 계정으로 로그인을 실행한다.
- superadmin 페이지를 확인한다.
- admin 페이지로 접근하여 페이지를 확인한다.
Spring Security를 활용하여 간단한 인증, 권한을 만들어 보았습니다. Spring Security를 이용해 복잡한 인증과 권한 절차를 쉽게 만들어 낼 수 있었습니다.
Spring Security에 대한 아키텍처도 정리하였습니다. 같이보시면서 흐름을 이해하시는데 도움이 될거 같습니다.
2021.03.25 - [Spring/Security] - [Security] 스프링 시큐리티의 아키텍처(구조) 및 흐름
JWT를 이용한 로그인 방식
2022.10.28 - [Spring/Security] - [JWT] 회원가입 / 로그인 / 토큰 발급 (1)
파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음
반응형
'Spring > Security' 카테고리의 다른 글
[Security] 스프링 시큐리티의 아키텍처(구조) 및 흐름 (0) | 2022.11.01 |
---|---|
[JWT] JWT 인증, 인가 필터 생성 / 회원 정보 가져오기 / 인증 객체로 로그인 처리 (2) (0) | 2022.10.28 |
[JWT] 회원가입 / 로그인 / 토큰 발급 (1) (0) | 2022.10.28 |
[Security] WebSecurityConfigurerAdapter Deprecated (0) | 2022.09.05 |
Spring Security 기능을 제거하는 간단한 방법 (0) | 2021.09.16 |