개발자의 오르막
[SpringSecurity #14] 스프링 메소드 시큐리티 본문
# 메소드 시큐리티
- @EnableGlobalMethodSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true, securedEnabled = true)
- @Secured 와 @RollAllowed
메소드 호출 이전에 권한을 확인한다.
스프링 EL을 사용하지 못한다.
- @PreAuthorize 와 @PostAuthorize
메소드 호출 이전 이후에 권한을 확인할 수 있다.
스프링 EL을 사용하여 메소드 매개변수와 리턴값을 검증할 수 있다.
- 메소드를 활용하여 시큐리티 메소드 사용하기
1. 먼저 MethodSecurity 를 사용하기 위해 config 패키지에 클래스를 아래와 같이 생성하고 선언한다.
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class MethodSecurity {
}
securedEnabled = true 로 설정하면 @Secured 어노테이션 사용 가능
2. 사용하고자하는 메소드 위에 @Secured 라는 어노테이션으로 설정해준다.
@Secured("ROLE_USER")
public void dashboard() {
// SecurityContextHolder 를 통해서 로그인 한 객체를 어디서든지 사용할 수 있음.
// UsernamePasswordAuthenticationToken 타입
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// principal 은 로그인한 객체로 User 타입 ( UserDetails 타입 안의 User )
Object principal = authentication.getPrincipal();
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
System.out.println("===============================");
System.out.println(userDetails.getUsername());
// 로그인한 principal 의 권한 ( Roles )
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
// 인증을 한 후에는 가지고 있지 않음
Object credentials = authentication.getCredentials();
// 인증된 사용자인지 여부
boolean authenticated = authentication.isAuthenticated();
}
@Secured, @RolesAllowed, @PreAuthorize("hasRole('USER')") 는 메소드를 호출하기 전에 권한 검사
@PostAuthorize("hasRole('USER')") 는 메소드를 호출한 이후에 사용 가능하다.
3. 시큐리티 config 파일에 AuthenticationManager 를 빈으로 등록해준다.
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
4. @Secured 가 선언된 해당 메소드를 테스트 하는 테스트 파일을 만들어준다.
@RunWith(SpringRunner.class)
@SpringBootTest
public class SampleServiceTest {
@Autowired
SampleService sampleService;
@Autowired
AccountService accountService;
@Autowired
AuthenticationManager authenticationManager;
@Test
public void dashboard() {
// 계정을 생성한다.
Account account = new Account();
account.setRole("USER");
account.setUsername("jake");
account.setPassword("123");
accountService.createNew(account);
// 생성한 계정을 UserDetails 타입으로 불러온다.
UserDetails userDetails = accountService.loadUserByUsername("jake");
// 인증할 수 있는 UserDetails 의 토큰을 발행한다.
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails, "123");
// 발행된 토큰을 통해 인증한다.
Authentication authentication = authenticationManager.authenticate(token);
// 다른 필터에서 사용할 수 있도록 SecurityContextHolder 에 등록한다.
SecurityContextHolder.getContext().setAuthentication(authentication);
sampleService.dashboard();
}
}
5. Hierarchy 를 설정하기 위해서는 아래와 같이 상속받아 메소드를 재정의하여 설정해준다.
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class MethodSecurity extends GlobalMethodSecurityConfiguration {
@Override
protected AccessDecisionManager accessDecisionManager() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
AffirmativeBased accessDecisionManager = (AffirmativeBased) super.accessDecisionManager();
accessDecisionManager.getDecisionVoters().add(new RoleHierarchyVoter(roleHierarchy));
return accessDecisionManager;
}
}
또는 @Secured("ROLE_USER", "ROLE_ADMIN") 으로 정의해줘도 된다.
# AuthenticationPrincipal
우리는 도메인 Account 를 UserDeatails 로 변환하는데, 아래처럼 Build 를 해줬다.
User.builder()
.username(account.getUsername())
.password(account.getPassword())
.roles(account.getRole())
.build()
이를 줄이기 위해 UserAccount 라는 클래스를 생성하여 User 를 상속받아 사용한다.
public class UserAccount extends User {
private Account account;
public UserAccount(Account account) {
super(account.getUsername(), account.getPassword(), List.of(new SimpleGrantedAuthority("ROLE_" + account.getRole())));
}
public Account getAccount() {
return account;
}
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = accountRepository.findAccountByUsername(username);
if (account == null) {
throw new UsernameNotFoundException(username);
}
// User.builder()
// .username(account.getUsername())
// .password(account.getPassword())
// .roles(account.getRole())
// .build()
return new UserAccount(account);
}
그러면 Account 도메인에 User 라는 클래스를 상속받지 않아도 UserAccount 라는 클래스를 통해
Convert 가능하다.
- 기존 컨트롤러
@GetMapping("/")
public String index(Model model, Principal principal) {
if (principal == null) {
model.addAttribute("message", "Hello Spring Security");
} else {
model.addAttribute("message", "Hello" + principal.getName());
}
return "index";
}
- UserAccount 사용한 컨트롤러
@GetMapping("/")
public String index(Model model, @AuthenticationPrincipal UserAccount userAccount) {
if (userAccount == null) {
model.addAttribute("message", "Hello Spring Security");
} else {
model.addAttribute("message", "Hello" + userAccount.getUsername());
}
return "index";
}
- Account 도메인 사용한 컨트롤러
@GetMapping("/")
public String index(Model model, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account") Account account) {
if (account == null) {
model.addAttribute("message", "Hello Spring Security");
} else {
model.addAttribute("message", "Hello" + account.getUsername());
}
return "index";
}
위의 코드가 너무 길기 때문에 이를 어노테이션으로 등록하여 사용할 수 있다.
- CurrentUser 인터페이션 등록
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account")
public @interface CurrentUser {
}
- 리펙토링 한 컨트롤러
@GetMapping("/")
public String index(Model model, @CurrentUser Account account) {
if (account == null) {
model.addAttribute("message", "Hello Spring Security");
} else {
model.addAttribute("message", "Hello" + account.getUsername());
}
return "index";
}
'SpringFrameWork > SpringSecurity' 카테고리의 다른 글
[SpringSecurity] 스프링 시큐리티 개념 (0) | 2021.04.01 |
---|---|
[SpringSecurity #15] 스프링 시큐리티 데이터 연동 (0) | 2020.10.06 |
[SpringSecurity #13] 타임리프 스프링 시큐리티 (0) | 2020.10.05 |
[SpringSecurity #12] Security Custom Filter 만들기 (0) | 2020.10.04 |
[SpringSecurity #11] Security Auth 관련 Filter (0) | 2020.10.03 |