Spring Security Dependency를 추가하면 스프링 시큐리티를 사용할 수 있다.
추가하게 되면 어플리케이션 실행 시 콘솔 창에 초기 비밀번호가 나오고

이제 모든 페이지를 접속할 때 로그인이 필요해진다.

id : user, password: 초기 비밀번호를 입력하면 로그인이 된다.
스프링 시큐리티 6.1부터 많은 것들이 바뀌었다.
package com.cos.security1.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity //스프링 시큐리티 필터(SecurityConfig)가 스프링 필터체인에 등록됨.
public class SecurityConfig {
// 스프링 부트 2.7.0 이상부턴 WebSecurityConfigurerAdapter가 deprecated됨.
// 아예 자체적으로 SecurityFilterChain Bean을 생성하는 방식으로 바뀌었다.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
// 특정 URL에 대한 권한 설정.
.authorizeHttpRequests((authorizeRequests) -> {
authorizeRequests.requestMatchers("/user/**").authenticated(); //antMatcher -> requestMatchers로 변경
authorizeRequests.requestMatchers("/manager/**")
// ROLE_은 붙이면 안 된다. hasAnyRole()을 사용할 때 자동으로 ROLE_이 붙기 때문이다.
.hasAnyRole("ADMIN", "MANAGER");
authorizeRequests.requestMatchers("/admin/**")
// ROLE_은 붙이면 안 된다. hasRole()을 사용할 때 자동으로 ROLE_이 붙기 때문이다.
.hasRole("ADMIN");
authorizeRequests.anyRequest().permitAll();
})
.formLogin((formLogin) -> {
/* 권한이 필요한 요청은 해당 url로 리다이렉트 */
formLogin.loginPage("/loginForm");
})
.build();
}
}
우선, 기존의 HttpSecurity 의 무분별한 메서드 체이닝을 지양하도록 바뀌었다.
// 기존의 메서드 체이닝 방식
.csrf().disable()
.sessionManagement().sessionCreationPolicy(...)
.and()
.authorizeHttpRequests()
// ...
코드를 보면 알겠지만, 기존에는 전혀 관계가 없는 요소들 끼리도 서로 직렬로 연결해서 사용했다.
// 수정된 메서드 체이닝 방식
.csrf(이곳에 CSRF 설정을 위한 함수)
.sessionManagement(이곳에 세션 설정을 위한 함수)
.authorizeHttpRequests(이곳에 인가 설정을 위한 함수);
지금은 함수형으로 하나의 요소에 대한 건 하나의 메서드 안에서 처리하도록 바뀌었다.
이런 식으로 코딩하지 않으면 IDE에서 에러를 뱉어내므로 주의.
DB 구조

IndexController.java 회원가입 메소드 (평문)
@PostMapping("/join")
public @ResponseBody String join(User user) {
System.out.println(user);
user.setRole("ROLE_USER"); //역할 지정
userRepository.save(user);
}
다음과 같이 DB에 저장하여 회원가입을 할 수 있다.
허나 이는 문제가 생긴다. 스프링 시큐리티에선 암호화된 비밀번호만 인식할 수 있는데.
단순한 평문이기 때문에 DB 저장만 될뿐 로그인이 불가능하다.
이럴 때는 SecurityConfig에서 BCryptPasswordEncoder를 통해 암호화를 시켜 저장해야한다.
//해당 메소드의 리턴되는 오브젝트를 Ioc로 등록
@Bean
public BCryptPasswordEncoder encodePwd(){
return new BCryptPasswordEncoder();
}
@PostMapping("/join")
public @ResponseBody String join(User user) {
user.setRole("ROLE_USER");
String rawPassword = user.getPassword();
String encPassword = bCryptPasswordEncoder.encode(rawPassword); //bCrypt로 암호화
user.setPassword(encPassword);
userRepository.save(user);
return "redirect:/loginForm";
}

로그인을 만들기 앞서 기존에 있던 SecurityConfig.java에 새로운 구문을 추가한다.
.formLogin((formLogin) -> {
/* 권한이 필요한 요청은 해당 url로 리다이렉트 */
formLogin.loginPage("/loginForm");
//새로운 부분
formLogin.loginProcessingUrl("/login"); //login 주소가 호출되면 시큐리티가 낚아채서 로그인을 진행함
formLogin.defaultSuccessUrl("/"); //login 성공시 이동할 페이지, 만약 이전에 요청한 페이지가 있다면 해당 페이지로 감
})

인증(authenticated)이 되면 시큐리티는 SecurityContextHolder에 사용자 정보를 저장한다. SecurityContextHolder 안에 있는 SecurityContext는 Authentication 객체를 가지고 있고, 그 안에는 유저 정보를 담고 있는 UserDetails타입의 Principal객체가 있다.
package com.cos.security1.config.auth;
import com.cos.security1.model.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
public class PrincipalDetails implements UserDetails {
private User user; // Composition
public PrincipalDetails(User user) {
this.user = user;
}
// 해당 User의 권한을 return 함
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collect = new ArrayList<>();
collect.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole();
}
});
return collect;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//휴면계정 로직 처리
@Override
public boolean isEnabled() {
return true;
}
}
앞서 시큐리티 설정에서 loginProcessingUrl("/login")을 통해 /login으로 요청이 오면 자동으로 UserDetailsService 타입으로 등록되어 있는 loadUserByUsername 메소드가 실행된다.
package com.cos.security1.config.auth;
import com.cos.security1.model.User;
import com.cos.security1.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class PrincipalDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
// 리턴이 되면 Authentication 내부에 들어가고 그 후에 SecurityContext에 들어감
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User userEntity = userRepository.findByUsername(username);
if(userEntity != null){
return new PrincipalDetails(userEntity);
}
return null;
}
}
만약 해당하는 아이디가 존재해서 loadUserByUsername 함수에서 return 되는 UserDetails가 있을경우 이를 Authentication 내부에 넣고 해당 Authentication을 다시 세션에 넣음.
SecurityConfig.java에 새로운 어노테이션을 추가한다.
@EnableMethodSecurity(securedEnabled = true, prePostEnabled = true)
해당 어노테이션의 securedEnable은 시큐어 어노테이션을 활성화하는 것으로, 이렇게 설정을 하게 되면
@Secured("ROLE_ADMIN")
@GetMapping("/info")
public @ResponseBody String info(){
return "개인정보";
}
다음과 같이 Secured 어노테이션으로 접근이 가능한 권한을 설정할 수 있다.
다음으로 prePostenabled는 preAuthroize와 postAuthroize 어노테이션을 활성화한다.
이렇게 설정하게 되면
@PreAuthorize("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
@GetMapping("/data")
public @ResponseBody String data(){
return "데이터정보";
}
다음과 같이 PreAuthorize 어노테이션을 쓸 수 있다.
Secured 어노테이션은 권한을 1개만 지정 가능하고, PreAuthroize는 여러개를 지정할 수 있다.