권한 기반 접근 제어의 예제
https://docs.spring.io/spring-boot/docs/3.2.5/reference/htmlsingle/?utm_source=chatgpt.com
Spring Boot Reference Documentation
This section goes into more detail about how you should use Spring Boot. It covers topics such as build systems, auto-configuration, and how to run your applications. We also cover some Spring Boot best practices. Although there is nothing particularly spe
docs.spring.io
위 링크에서
h2ConsoleSecurityFilterChain
를 검색하면 h2 에 대한 FilterChain 관련 이슈를 해결할 수 있다
Entity
User.java
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "users")
@Getter
@Setter
public class User {
@Id
private String username; // 유저 이름
@Column(nullable = false, length = 14)
private String password; // 비밀번호
@ManyToMany(fetch = FetchType.EAGER)
private Set<Role> roles = new HashSet<>(); // 권한
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public Set<Role> getRoles() {
return roles;
}
}
Role.java
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import java.util.HashSet;
import java.util.Set;
@Entity
public class Role {
@Id
private String name; // ROLE_EDITOR, ROLE_VIEWER 등등..
@ElementCollection(fetch = FetchType.EAGER)
private Set<String> permissions = new HashSet<>();
public Role() {
}
public Role(String name) {
this.name = name;
}
public String getName() {
return name;
}
public Set<String> getPermissions() {
return permissions;
}
}
Post.java
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id; // 포스트 아이디
@Column(nullable = false)
private String owner; // 작성자
@Column(nullable = false)
private String content; // 내용
}
Permission.java
import java.util.Objects;
public class Permission {
private final ResourceType resourceType;
private final Action action;
public Permission(ResourceType resourceType, Action action) {
this.resourceType = resourceType;
this.action = action;
}
// getter
public ResourceType getResourceType() {
return resourceType;
}
public Action getAction() {
return action;
}
@Override
public int hashCode() {
return Objects.hash(resourceType, action);
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof Permission)) {
return false;
}
Permission permission = (Permission) obj;
return permission.resourceType == resourceType && permission.action == action;
}
@Override
public String toString() {
return resourceType + ":" + action;
}
}
Action.java
public enum Action {
READ, CREATE, UPDATE, DELETE
}
ResourceType.java
public enum ResourceType {
POST, FILE, ORDER
}
Service
UserService.java
import com.b1uffer.sessiontest.entity.User;
import com.b1uffer.sessiontest.repository.UserRepository;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@NoArgsConstructor
public class UserService {
private UserRepository userRepository;
public User create(String username, String password) {
if(username == null) {
throw new IllegalArgumentException("Username cannot be null");
}
if(username.length() > 12) {
throw new IllegalArgumentException("Username cannot be longer than 12 characters");
}
if(password.length() < 7 || password.length() > 15) {
throw new IllegalArgumentException("비밀번호는 8자리에서 14자리 사이입니다.");
}
User user = new User(username, password);
userRepository.save(user);
return user;
}
public boolean validate(String username, String password) {
return "user".equals(username) && "password".equals(password);
}
}
PostService.java
import com.b1uffer.sessiontest.entity.Post;
import com.b1uffer.sessiontest.repository.PostRepository;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@NoArgsConstructor
public class PostService {
private PostRepository postRepository;
public Post update(Long id, Post post) {
Post findPost = postRepository.findById(id)
.orElseThrow(() -> new RuntimeException("post not found"));
post.setContent(post.getContent());
postRepository.save(findPost);
return findPost;
}
}
Controller
PostController.java
import com.b1uffer.sessiontest.entity.Post;
import com.b1uffer.sessiontest.service.PostService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/posts")
public class PostController {
private final PostService postService;
public PostController(PostService postService) {
this.postService = postService;
}
@PreAuthorize("hasPermission(#post, 'UPDATE')") // PreAuthorize
@PutMapping("/{id}") // PutMapping
public ResponseEntity<String> updatePost(@PathVariable Long id,
@RequestBody Post post) {
postService.update(id, post);
return ResponseEntity.ok("Updated");
}
}
Security
CustomPermissionEvaluator.java
import com.b1uffer.sessiontest.entity.Post;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.io.Serializable;
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication authentication,
Object targetDomainObject,
Object permission) {
/**
* 소유자만 수정 가능하게끔 하기
*/
if(targetDomainObject instanceof Post post && "UPDATE".equals(permission)) {
String currentUser = authentication.getName(); // 인증하는 사람 이름
return post.getOwner().equals(currentUser); // currentUser와 게시글 작성자의 이름 비교
}
return false;
}
@Override
public boolean hasPermission(Authentication authentication,
Serializable targetId,
String targetType,
Object permission) {
// 커스텀 ID 기반 정책 구현하는곳
return false;
}
}
UserPrincipal.java
import com.b1uffer.sessiontest.entity.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
public class UserPrincipal implements UserDetails {
private final User user;
public UserPrincipal(User user) {
this.user = user;
}
@Override
public boolean isAccountNonExpired() {
// return UserDetails.super.isAccountNonExpired();
return true;
}
@Override
public boolean isAccountNonLocked() {
// return UserDetails.super.isAccountNonLocked();
return true;
}
@Override
public boolean isCredentialsNonExpired() {
// return UserDetails.super.isCredentialsNonExpired();
return true;
}
@Override
public boolean isEnabled() {
// return UserDetails.super.isEnabled();
return true;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getRoles().stream()
/**
* permission 필드의 타입이 Set<String>이라서 flatMap을 사용함
*/
.flatMap(role -> role.getPermissions().stream())
.map(SimpleGrantedAuthority::new)
.toList();
}
@Override
public String getPassword() {
return user.getPassword(); // User의 password를 가져옴
}
@Override
public String getUsername() {
return user.getUsername(); // User의 username을 가져옴
}
}
config
SecurityConfiguration.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
public class SecurityConfiguration {
@Value("${spring.security.oauth2.client.registration.google.client-id}")
private String clientId;
@Value("${spring.security.oauth2.client.registration.google.client-secret}")
private String clientSecret;
/**
* Security filter chain
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.headers(headers -> headers.frameOptions().disable())
.formLogin().disable()
.httpBasic().disable()
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/posts/**").authenticated()
.anyRequest().permitAll()
)
.oauth2Login(withDefaults());
return http.build();
}
/**
* JAVA Configuration을 활용해서 OAUTH2 인증을 설정하기
*/
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
var clientRegistration = clientRegistration();
return new InMemoryClientRegistrationRepository(clientRegistration);
}
private ClientRegistration clientRegistration() {
return CommonOAuth2Provider
.GOOGLE
.getBuilder("google")
.clientId(clientId)
.clientSecret(clientSecret)
.build();
}
/**
* 권한 기반 제어 Security 설정
*/
@Bean
public MethodSecurityExpressionHandler expressionHandler(PermissionEvaluator permissionEvaluator) {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(permissionEvaluator);
return expressionHandler;
}
}
'Spring Boot > 유저 관리 기능' 카테고리의 다른 글
| 인가와 권한 관리 : 세션/토큰 기반 인증에서의 인가 구현 (0) | 2026.03.22 |
|---|---|
| 인가와 권한 관리 : 실제 인가 구현 사례 (0) | 2026.03.21 |
| 인가와 권한 관리 : 권한 기반 접근 제어 (0) | 2026.03.17 |
| 인가와 권한 관리 : 인가 (0) | 2026.03.15 |
| Authorization 헤더, 토큰 기반 인증 : Refresh 토큰 (0) | 2026.03.15 |