Spring Boot . Spring Security 사용하기
처음 작은 회사가서 이것 저것 해보면서 Spring Security도 공부해보라는 얘기도 듣고 회사 솔수션에 적용하지 않을까 떨리는 마음으로 공부했지만 전혀 못돌려 봤다... 봐도 잘 모르겠고 찾아서 따라쳐봐도 잘 안돼서...
그래서 다시 공부해보자...
그리고 대게 JPA로 연동하여 포스팅한곳이 많아 아직 익숙치 않아서 Mybatis로 연동하였다.
Dependency 추가
implementation 'org.springframework.boot:spring-boot-starter-security'
이런 의존성 관리하는 툴은 대단하다. 한줄만 추가 하면 알아서 관리 해주니까
개발자 분들 감사합니다.
Configration 클래스 작성
이번에는 properties가 아닌 @Configration 을 이용한 설정을 해봤다. 사실 properties로 포스팅한 곳이 없고 이부분은 경험이 전혀없기때문에 구글링에서 시키는데로 할 수 밖에 없었다... 마침 다른 설정 방법도 연습할 수 있어서 좋은거라고 생각하자..
package com.game.flower_war.configration;
import com.game.flower_war.user.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import lombok.AllArgsConstructor;
@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private UserService userService;
//PasswordEncoder 빈 등록
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
//관여 안할 URL 설정
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/lib/**", "/user/**");
}
//필터링? 적용할 URL설정 및 ROLE 설정
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/main/**").hasRole("ADMIN")
.antMatchers("/user/**").permitAll()
.and()
.formLogin()
.loginPage("/sign").defaultSuccessUrl("/main").failureUrl("/sign").permitAll()
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/signout")).logoutSuccessUrl("/sign")
.invalidateHttpSession(true)
.and()
.exceptionHandling().accessDeniedPage("/denied");
}
//PasswordEncoder 설정
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
}
@Configuration으로 설정 파일이라는걸 알려주고 @EnableWebSecurity로 SpringSecurity 활성화!
WebSecurityConfigurerAdapter을 상속하여 각 configure를 Override해주어 설정한다.
(여기서 @Bean을 처음써본거 같다. 암튼 잘 되는구만 무늬만 Spring개발자.... 여튼)
두번째 필터링?하는 것을 정의하는 부분은 설정관련한게 많아 따로 다시 정리 해봐야 할 듯하다.
매소드 이름만 봐도 대충은 어느 동작을 하는 부분인지는 알것 같다.
PasswordEncoder 설정 부분은 간단했다. 그래서 패스워드의 암호화를 서버쪽에서 하기 때문에 이전에 DB에서 사용했던 Encypt는 사용하지 않았다.
UserService 클래스
package com.game.flower_war.user.service;
import java.util.List;
import com.game.flower_war.model.UserModel;
import org.springframework.security.core.userdetails.UserDetailsService;
public interface UserService extends UserDetailsService{
public List<UserModel> userList(UserModel user);
// ...
}
일단 서비스 인터페이스로 UserDetailsService를 상속해서 만든다. 그리고 구현채에서 필요한 Method를 Override하면 기존에 형태를 바꾸지 않아도 되었다. 이렇게 상속을 해야 위 설정클래스의 UserService객체를 주입할 수 있는 객채가된다.
UserSerivceImpl - UserService의 구현체 클래스
@Service(value = "UserService")
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserModel searchUser = new UserModel();
//사용자 조회
searchUser.setUserId(username);
List<UserModel> userList = userMapper.select(searchUser);
User springSecurityUser = null;
//권한 설정
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
//리스트로 리턴 받아서 아래와 같이 작성
if( userList.size() == 0) {
springSecurityUser = new User( null , null, null);
}else{
searchUser = userList.get(0);
System.out.println( searchUser.toString() );
springSecurityUser = new User(searchUser.getUserId(), searchUser.getPassword(), authorities);
}
return springSecurityUser;
}
// 이하 다른 구현 부분들
}
Method 이름처럼 사용자이름(고유한 아이디?)로 찾는 걸 구현해내면 된다. 패스워드가 맞는지는 Security가 해준다.
(위에서 구현한 configure에 UserService를 사용하는 설정 부분으로 보인다)
위에는 리스트로 UserModel 객체를 받아오는데 사실 ID는 고유한 primaryKey 값으로 단일 객체를 리턴받는 메소드로 만들어주느것이 성능이나 코드 가독성면에서도 좋겠다. ( 귀찮아서... 기존에 있던 형태를 그대로 쓰다보니 저리 되었다...)
권한 설정은 Configuration에서는 hasRole() 에서 ADMIN 으로 사용했는데 권한관 인증은 조금 다른 역활로 명칭이 비슷해서 헛갈리는데 학습이 필요하겠다.
SpringSecurity의 User클래스에 ID, password, 권한?룰?관련을 위와 같은 형태로 생성하여 리턴해주었다.
public class User implements UserDetails{
@Override
public String getUsername() {
return null;
}
@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return false;
}
}
위와 같이 UserDetails 인터페이스를 구현하는 하면 내가 만든 모델, Entity로 인증객체로 사용할 수 가 있다.
그대신 위에 인증관련 메소드를 공부해서 어디에 작용하는지 알아야 원하는 동작을 하게 만들수 있을듯하다.
CustomLoginPage html 작성
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>로그인 페이지</title>
</head>
<body>
<h1>로그인</h1>
<hr>
<form action="/sign" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<input type="text" name="username" placeholder="이메일 입력해주세요">
<input type="password" name="password" placeholder="비밀번호">
<button type="submit">로그인</button>
</form>
</body>
</html>
간단한 custom form 양식이다. form의 action은 로그인 페이지 url을 사용하면 되고 method 방식만 post로 사용하여 구분하는 것으로 보인다. 저 hidden 부분은 CSRF을 방지하는 부분이다. Web으로 계속 하려면 이런 보안 방식도 역시나 공부를 많이 해야할듯 하다.
이 페이지와 로그인 성공 및 실패시에 나오는 페이지와 Controller는 생략하겠다. ( 거의 중복 및 간단한 코드이니까! )
테스트를 돌리면 결과 스크린샷은 개인 기록이니 필요없고 지금 너무 졸립다... 잘 안되면 또다시 찾아보자 별수 없다.