SecurityConfig.java 파일을 생성하여 WebSecurityConfigurerAdapter를 상속받고, 아래 3개의 메소드를 Override한다.
1. configure(HttpSecurity http) 메소드에서는 Jwt 인증이 실패할 경우 처리할 EntryPoint 설정과 JWT인증에는 기본적으로 세션을 사용하지 않기 때문에 Session을 STATELESS해준다. 또한, JWT인증을 처리할 Filter를 SpringSecurity의 기본적인 필터인 UsernamePasswordAuthenticationFilter 앞에 넣어준다.
2. configure(AuthenticationManagerBuilder auth) 메소드는 인증방법을 의미하며,
Spring에서 xml로 Security 설정시 <authentication-manager> <authentication-provider>를 설정하는 것과 같다.예를 들면 DB에서 사용자 정보를 불러와 현재 입력된 정보와 비교 후 UserDetails 을 반환하여 인증하겠다 라는 의미이다. 현재는 CustomUserDetailSerivce 객체에 인증 방법이 있으며, 패스워드 인코더를 설정해 두었다.
3. PasswordEncoder는 로그인시 DB에 BCrpyt로 저장되어있는 패스워드를 로그인할 때 입력한 비밀번호와 비교하기 위해 사용되며, 이는 인증할 때 사용되는 configure(AuthenticationManagerBuilder auth) 메소드에 사용될 것이다.
4. AuthenticationManger는 이 글의 뒤에서 나중에 진짜로 인증을 하는 곳에서 사용하기 위해 Bean으로 등록한다.
요청이 들어올 때 마다 JWT를 인증할 Filter를 생성한다. 요청이 들어올 때마다 하기 위해 OncePerRequestFilter를 상속받는다. OncePerRequestFilter가 아니더라도 일반적인 GenericFilterBean을 상속받아도 무관하다. (OncePerRequestFilter가 이미 GenericFilterBean를 상속받고 있기 때문.)
Filter 작동 방식은 간단하다. Bearer로 토큰을 받으면 토큰을 추출하여 올바른 토큰인지 체크를 한다.
토큰이 올바르다면, 토큰에 있는 정보(username, role 등등) 을 가져와 인증을 시킨다.
3.1의 Filter 등록의 authenticationManager에서 기존의 인증방식이아닌 새로 생성한 Provider 인증방식을
등록하였기 때문에 2번에서 생성한 Provider를 수정해주어야 한다.
기존의 Form Login 기능에 사용하던 userDetailsService와 , PasswordEncoder를 가져온다.
authenticate 함수에서 Get 파라미터로 가져온 name과 password를 입력한다.
다음, 기존에 사용하던 loadUserByUsername을 통해 DB에 저장되어있는 암호화된 password와 권한들을 가져온다.
그리고 passwordEncoder의 matchs 기능으로 Get파라미터로 가져온 평문의 password와 암호화된 password를
비교해 Passoword가 맞다면 인증을 성공시키고, 다르다면 Exception을 던진다.
public class GetLoginProvider implements AuthenticationProvider {
@Autowired
UserDetailsService userDetailsService;
@Autowired
PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// TODO Auto-generated method stub
String name = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails userDetails = userDetailsService.loadUserByUsername(name);
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
if(passwordEncoder.matches(password, userDetails.getPassword()))
return new UsernamePasswordAuthenticationToken(userDetails, authentication.getCredentials().toString(),
authorities);
else
throw new BadCredentialsException("authentication failed");
}
@Override
public boolean supports(Class<?> authentication) {
// TODO Auto-generated method stub
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
만약, 패스워드입력필요없이 아이디만으로 로그인하고 싶다면 아래와 같이 패스워드 비교 코드를 삭제하고 작성하면 된다.
public class GetLoginProvider implements AuthenticationProvider {
@Autowired
userDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// TODO Auto-generated method stub
String name = authentication.getName();
UserDetails userDetails = userDetailsService.loadUserByUsername(name);
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
// use the credentials
// and authenticate against the third-party system
return new UsernamePasswordAuthenticationToken(userDetails, authentication.getCredentials().toString(),
authorities);
}
@Override
public boolean supports(Class<?> authentication) {
// TODO Auto-generated method stub
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
이러한 방식으로 기존의 POST로 진행하던 Form Login에서 Get Login 방식을 추가할 수 있다.
public interface UserRepository extends JpaRepository<UserVO, String> {
}
3. Security 적용
3.1 WebSecurityConfigurerAdapter 적용
먼저 WebSecurityConfigurerAdapter를 상속받고, 아래 2개의 메소드를 Override하고 Bean을 등록한다.
1. configure(HttpSecurity http) 메소드는 기본적인 security 기본 설정을 하는 것이다. 현재는 csrf를 비활성화 시키고,
authorizeRequests()를 이용하여 요청에대한 권한을 처리한다. /login 경로는 권한이 없어도 접속을 허용하고, anyRequest().authenticated()를 통해 이 외의 경로는 권한이 있어야 접속을 허용한다.
formLogin() 을 통해 formLogin을 사용하겠다는 것이며, formLogin 페이지를 만들지 않았다면, Security에서 제공해주는 로그인페이지를 사용한다. 만약 로그인페이지가 있다면. formLogin().loginPage("로그인페이지 경로") 라는 loginPage 옵션을 추가하여 로그인페이지를 커스텀페이지로 변경할 수 있다.
formLogin에는 로그인 성공시 작동할 기능, 실패시 작동할 기능, 로그인 인증 URL 변경 등 다양한 기능들이 있다.
2. configure(AuthenticationManagerBuilder auth) 메소드는 인증방법을 의미하며,
Spring에서 xml로 Security 설정시 <authentication-manager> <authentication-provider>를 설정하는 것과 같다. 예를 들면 DB에서 사용자 정보를 불러와 현재 입력된 정보와 비교 후 UserDetails 을 반환하여 인증하겠다 라는 의미이다. 현재는 CustomUserDetailSerivce 객체에 인증 방법이 있으며, 패스워드 인코더를 설정해 두었다.
3. PasswordEncoder는 로그인시 DB에 BCrpyt로 저장되어있는 패스워드를 로그인할 때 입력한 비밀번호와 비교하기 위해 사용되며, 이는 인증할 때 사용되는 configure(AuthenticationManagerBuilder auth) 메소드에 사용될 것이다.
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// TODO Auto-generated method stub
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
configure(AuthenticationManagerBuilder auth)에서 설정한 BCrpyt PasswordEncoder에는 macth라는 패스워드 비교하는 기능이 있는데 User 객체를 반환하면서 현재의 비밀번호와 폼에서 입력한 비밀번호가 match 기능으로 인하여비밀번호를 비교해주고 비밀번호가 올바르면 로그인 인증 성공으로 처리하고 틀리다면 실패로 처리한다.
import org.springframework.security.core.userdetails.User;
@Service
public class CustomUserDetailService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// TODO Auto-generated method stub
Collection<SimpleGrantedAuthority> roles = new ArrayList<SimpleGrantedAuthority>();
roles.add(new SimpleGrantedAuthority("ROLE_USER"));
UserVO userVO =userRepository.findById(username).orElseThrow(() -> new NoSuchElementException());
return new User(userVO.getId(), userVO.getPw(), roles);
}
}
이러한 방법을 이용하여, 생성한 DB User테이블에 임의의 ID와 BCrpyt로 암호화한 패스워드를 넣고,
서버를 실행하여 /login 경로로 접속하면, Spring Security에서 기본으로 제공하는 Login Form을 이용하여 로그인을 할 수 있다.