Spring Security는 스프링 기반 애플리케이션의 보안을 담당해주는 스프링 하위 프레임워크이다.
보호되어 있는 리소스에 접근하고자 하는 대상
유저
를 확인하는 작업, 접근한 대상이 어떤 종류의 유저인지 확인하는 과정
해당 리소스에 접근한 대상이 권한이 있는지 확인한는 과정
어떤 리소스에 대한 접근 제한을 의미, 모든 리소스는 각각 권한이 걸려있으며, 인가 과정에서 최소한의 권한을 확인하는 것
Spring Security를 활용하여 WebSecurityConfig 클래서 생성
WebSecurityConfig.class
@Configuration // Bean 관리하는 어노테이션
@EnableWebSecurity // Spring Security를 활성화하는 어노테이션
// 해당 클래스가 Spring Security 설정 파일로 역할을 하기 위해 WebSecurityConfigurerAdapter 클래스 상속
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserService userService; // 유저 정보를 가져오는 클래스
@Autowired
public WebSecurityConfig(UserService userService) {
this.userService = userService;
}
/**
* 인증을 무시할 경로 설정<br>
* 인증없이 무조건 필요하거나 누구나 접근할 수 있는 경로
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/h2-console/**");
}
/**
* http 관련 인증 설정 가능
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable().headers().frameOptions().disable() // h2-console 화면 사용하기 위함
.and()
.authorizeRequests()
.antMatchers("/login", "/signup").permitAll() // 누구나 접근 가능
.antMatchers("/").hasAnyRole("USER", "ADMIN") // 여러개 권한 가능 USER, ADMIN 만 접근 가능
.antMatchers("/admin").hasRole("ADMIN") // ADMIN 만 접근 가능
.anyRequest().authenticated() // 나머지는 권한이 있기만 하면 접근 가능
.and()
.formLogin() // 로그인에 대한 설정
.loginPage("/login") //로그인 페이지 링크
.defaultSuccessUrl("/") // 로그인 성공시 연결되는 주소
.and()
.logout() // 로그아웃 관련
.logoutSuccessUrl("/login") // 로그안웃 성공시 연결되는 주소
.invalidateHttpSession(true) // 로그아웃시 지정해 둔 세션 날리기
;
}
/**
* 로그인 시 필요한 정보를 가져온다.
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService) // 유저정보는 userService에서 가져온다.
.passwordEncoder(new BCryptPasswordEncoder()); // 패스워드 인코더는 passwordEncoder(BCrypt 사용)
}
}
권한 관리 대상을 지정하는 옵션
URL 혹은 HTTP 메소드 별로 관리를 가능하게 해 줌
permitAll() or denyAll(): 누구나 접근 가능하게 함
anonymous(): 인증되지 않은 사용자가 접근할 수 있음
authenticated(): 권한이 있기만 하면 접근 가능
fullyAuthenticated(): 완전히 인증된 사용자만 접근할 수 있음
hasRole() or hasAnyRole(): 특정 권한을 가지는 사용자만 접근할 수 있음
hasAuthority() or hasAnyAuthority(): 특정 권한을 가지는 사용자만 접근할 수 있음
hasIpAddress(): 특정 아이피 주소를 가지는 사용자만 접근할 수 있음
access(): SpEL 표현식에 의한 결과에 따라 접근할 수 있음
not(): 접근 제한 기능을 해제 함
rememberMe(): 리멤버 기능을 통해 로그인한 사용자만 접근할 수 있음
anyRequest(): antMatchers()에서 설정하지 않은 나머지 주소들
UserEntity는 UserDetails를 상속받아 진행
UserDetails 란?
Spring Security에서 사용자의 정보를 담는 인터페이스이다. Spring Security에서 사용자의 정보를 불러오기 위해서 구현해야 하는 인터페이스로 기본 오버라이드 메서드들은 아래와 같다.
메소드 return 타입 설명 default getAuthorities() Collection<? extends grantedAuthority> 계정의 권한 목록 return - getPassword() String 계정의 비밀번호 return - getUsername() String 계정의 고유값 return
(unique한 값이어야함)
(보통 DB의 PK값)- isAccountNonExpired() boolean 계정의 만료 여부 return true(만료 X) isaccountNonLocked() boolean 계정의 잠김 여부 return true(잠김 X) isCredentialsNonExpired() boolean 비밀번호 만료 여부 return true(만료 X) isEnabled() boolean 계정의 활성화 여부 return true(활성화 됨)
UserEntity.class
@Entity
public class UserEntity implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // PK
@Column
private String userId; // 회원 ID
@Column
private String password; // 비밀번호
@Column
private String username; // 회원 이름
@Column
private String email; // 이메일
@Column
private String auth; // role, 형태로 저장
public UserEntity() {
}
public UserEntity(Long id, String userId, String password, String username, String email, String auth) {
this.id = id;
this.userId = userId;
this.password = password;
this.username = username;
this.email = email;
this.auth = auth;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public void setPassword(String password) {
this.password = password;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAuth() {
return auth;
}
public void setAuth(String auth) {
this.auth = auth;
}
/**
* 필수 Override 메소드 구현<br>
*
* 사용자의 권한이 ","로 구분되어 있는 auth을 활용, 콜렉션 형태로 반환시킴<br>
* 단, 자료형은 GrantedAuthority를 구현해야 함
* */
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> roles = new HashSet<>();
for (String role : auth.split(",")) {
roles.add(new SimpleGrantedAuthority(role));
}
return roles;
}
/**
* 사용자의 unique한 값 return(보통 pk or id)
*/
@Override
public String getUsername() {
return userId;
}
/**
* 사용자의 password 반환
*/
@Override
public String getPassword() {
return password;
}
/**
* 계정 만료 여부 반환
*/
@Override
public boolean isAccountNonExpired() {
return true; // 만료되지 않음(false: 만료)
}
/**
* 계정 잠김 여부 반환
*/
@Override
public boolean isAccountNonLocked() {
return true; // 계정이 잠기지 않음(false: 계정 잠김)
}
/**
* 비밀번호 만료 여부 반환
*/
@Override
public boolean isCredentialsNonExpired() {
return true; // 만료되지 않음(false: 비밀번호 만료)
}
/**
* 계정의 활성화 여부 반환
*/
@Override
public boolean isEnabled() {
return true; // 활성화 됨(false: 비 활성화)
}
}
UserService는 UserDetailsService를 상속받아 진행
Spring Security에서 유저의 정보를 가져오는 인터페이스이다. Spring Security에서 유저의 정보를 불러오기 위해서 구현해야하는 인터페이스로 기본 오버라이드 메서드는 아래와 같다.
메소드 return 타입 설명 loadUserByUsername() UserDetails 유저의 정보를 불러와서 UserDetails로 리턴
UserService.class
@Service
public class UserService implements UserDetailsService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
/**
* UserDetailsService 상속시 필수로 구현해야 하는 메소드<br>
* UserDetails가 기본 반환 타입, UserEntity가 이를 상속하고 있으므로 자동으로 다운 캐스팅 됨?
*/
@Override
public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {
return userRepository.findByUserId(userId).orElseThrow(() -> new UsernameNotFoundException(userId));
}
public Long save(UserDto userDto) {
// 비밀번호 암호화
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
userDto.setPassword(encoder.encode(userDto.getPassword()));
UserEntity userEntity = new UserEntity();
userEntity.setUserId(userDto.getUserId());
userEntity.setPassword(userDto.getPassword());
userEntity.setUsername(userDto.getUsername());
userEntity.setEmail(userDto.getEmail());
userEntity.setAuth(userDto.getAuth());
this.userRepository.save(userEntity);
return userEntity.getId();
}
}
@Controller
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/signup")
public String signup(){
return "signup";
}
@PostMapping("/signup") // signup api
public String signup(UserDto userDto) {
userService.save(userDto);
return "redirect:/login";
}
@GetMapping("/login")
public String login(){
return "login";
}
@GetMapping("/logout") // logout by GET 요청
public String logoutPage(HttpServletRequest request, HttpServletResponse response) {
new SecurityContextLogoutHandler().logout(request, response, SecurityContextHolder
.getContext().getAuthentication());
return "redirect:/login";
}
@GetMapping("/")
public String user(Model model, @AuthenticationPrincipal UserEntity userEntity) {
// @AuthenticationPrincipal: UserDetailsService에서 return한 객체를 파라미터로 직접 받아 사용할 수 있다.
model.addAttribute("user", userEntity);
return "user";
}
@GetMapping("/admin")
public String admin(Model model, @AuthenticationPrincipal UserEntity userEntity) {
// @AuthenticationPrincipal: UserDetailsService에서 return한 객체를 파라미터로 직접 받아 사용할 수 있다.
model.addAttribute("user", userEntity);
return "admin";
}
}
...
ID : <span sec:authentication="name"></span><br>
소유 권한 : <span sec:authentication="authorities"></span><br>
<br>
<h2>모델 객체와 @AuthenticationPrincipal 사용</h2>
ID : <span th:text="${user.userId}"></span><br>
이름 : <span th:text="${user.username}"></span><br>
Email : <span th:text="${user.email}"></span><br>
소유 권한 : <span th:text="${user.auth}"></span><br>
...
참조