🤽 '일반 사용자'는 관리자 페이지에 접속이 인가되지 않아야 함!
1. Spring Security에 대한 "권한(Authority)" 설정방법
- 회원 상세정보(UserDetailsImpl)를 통해 "권한(Authority)" 설정 가능
- 권한을 1개 이상 설정 가능
- "권한 이름" 규칙
- "ROLE_" 로 시작해야함
- 예시
- "ADMIN" 권한 부여 ➡️ "ROLE_ ADMIN"
- "USER" 권한 부여 ➡️ "ROLE_ USER"
public enum UserRoleEnum { USER(Authority.USER), // 사용자 권한 ADMIN(Authority.ADMIN); // 관리자 권한 private final String authority; UserRoleEnum(String authority) { this.authority = authority; } public String getAuthority() { return this.authority; } public static class Authority { public static final String USER = "ROLE_USER"; public static final String ADMIN = "ROLE_ADMIN"; } }
public class UserDetailsImpl implements UserDetails { // ... @Override public Collection<? extends GrantedAuthority> getAuthorities() { SimpleGrantedAuthority adminAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); Collection<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(adminAuthority); return authorities; } }
new SimpleGrantedAuthority("ROLE_ADMIN");
- 예시 코드는
ROLE_ADMIN
으로 고정되어 있지만 아래와 같이 실제 코드에서는 사용자에 저장되어있는 role의 authority값을 사용하여 동적으로 저장됨UserRoleEnum role = user.getRole(); String authority = role.getAuthority(); SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority);
- UserDetailsImpl 저장된
authorities
값을 사용하여 간편하게 권한 제어 가능
2. Spring Security를 이용한 API 별 권한 제어 방법
Controller에 "@Secured" 애너테이션으로 권한 설정 가능
@Secured(" 권한 이름 ") 선언
- 권한 1개 이상 설정 가능
@Secured(UserRoleEnum.Authority.ADMIN) // 관리자용 @GetMapping("/products/secured") public String getProductsByAdmin(@AuthenticationPrincipal UserDetailsImpl userDetails) { System.out.println("userDetails.getUsername() = " + userDetails.getUsername()); for (GrantedAuthority authority : userDetails.getAuthorities()) { System.out.println("authority.getAuthority() = " + authority.getAuthority()); } return "redirect:/"; }
"@Secured" 애너테이션 활성화 방법
@Configuration @EnableWebSecurity // 스프링 Security 지원을 가능하게 함 @EnableGlobalMethodSecurity(securedEnabled = true) // @Secured 애너테이션 활성화 public class WebSecurityConfig {
스프링 시큐리티 설정을 이용해 일반 사용자가 '관리자용 상품조회 API에 접속 시도 시 접속 불가 페이지가 뜨도록 구현해보자!
프론트엔드 개발자 작업 ➡️ Forbidden 페이지 적용
WebSecurityConfig 파일 수정
package com.sparta.springauth.config;
import com.sparta.springauth.jwt.JwtAuthorizationFilter;
import com.sparta.springauth.jwt.JwtAuthenticationFilter;
import com.sparta.springauth.jwt.JwtUtil;
import com.sparta.springauth.security.UserDetailsServiceImpl;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity // Spring Security 지원을 가능하게 함
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig {
private final JwtUtil jwtUtil;
private final UserDetailsServiceImpl userDetailsService;
private final AuthenticationConfiguration authenticationConfiguration;
public WebSecurityConfig(JwtUtil jwtUtil, UserDetailsServiceImpl userDetailsService, AuthenticationConfiguration authenticationConfiguration) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
this.authenticationConfiguration = authenticationConfiguration;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil);
filter.setAuthenticationManager(authenticationManager(authenticationConfiguration));
return filter;
}
@Bean
public JwtAuthorizationFilter jwtAuthorizationFilter() {
return new JwtAuthorizationFilter(jwtUtil, userDetailsService);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// CSRF 설정
http.csrf((csrf) -> csrf.disable());
// 기본 설정인 Session 방식은 사용하지 않고 JWT 방식을 사용하기 위한 설정
http.sessionManagement((sessionManagement) ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
http.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources 접근 허용 설정
.requestMatchers("/api/user/**").permitAll() // '/api/user/'로 시작하는 요청 모두 접근 허가
.anyRequest().authenticated() // 그 외 모든 요청 인증처리
);
http.formLogin((formLogin) ->
formLogin
.loginPage("/api/user/login-page").permitAll()
);
// 필터 관리
http.addFilterBefore(jwtAuthorizationFilter(), JwtAuthenticationFilter.class);
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
// 접근 불가 페이지
http.exceptionHandling((exceptionHandling) ->
exceptionHandling
// "접근 불가" 페이지 URL 설정
.accessDeniedPage("/forbidden.html")
);
return http.build();
}
}