Spring security + jwt 사용을 위하여 구성하여야할 부분을 크게 나누어보면,
위의 실제 사용자 구현부는 WebSecurityConfigurerAdapter 구현체에서 사용된다.
jwt
란?json 객체를 사용해서 토큰 자체에 정보들을 저장하고 있는 web token.
jwt
구성header, payload, signature로 구성되어있다.
header
: signature
를 해싱하기 위한 알고리즘 정보들이 담겨있음
payload
: 서버와 클라이언트가 주고받는, 시스템에서 실제로 사용될 정보에 대한 내용들이 담겨있음
signature
: 토큰의 유효성 검증을 위한 문자열. 이 문자열을 통해 서버에서는 이 토큰이 유효한 토큰인지를 검증할 수 있다.
jwt
장점jwt
단점WebSecurityConfig
@RequiredArgsConstructor
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtTokenProvider jwtTokenProvider;
// 암호화에 필요한 PasswordEncoder 를 Bean 등록합니다.
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
// authenticationManager를 Bean 등록합니다.
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable() // rest api 만을 고려하여 기본 설정은 해제하겠습니다.
.csrf().disable() // csrf 보안 토큰 disable처리.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 토큰 기반 인증이므로 세션 역시 사용하지 않습니다.
.and()
.authorizeRequests() // 요청에 대한 사용권한 체크
.antMatchers("/**").permitAll()
.anyRequest().permitAll() // 그외 나머지 요청은 누구나 접근 가능
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
UsernamePasswordAuthenticationFilter.class);
// JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 전에 넣는다
}
}
@EnableWebSecurity
: 기본적인 web보안을 활성화하겠다.
@Bean
: spring IoC 컨테이너에 Bean을 등록. 개발자가 직접 제어가 불가능한 외부 라이브러리 등을 Bean으로 등록하려할 때. 개발자가 작성한 메서드를 통해 반환되는 객체를 Bean으로 등록하려할 때
추가적인 설정을 위해서 WebSecurityConfigurer
를 implements
하거나 WebSecurityConfigurerAdapter
를 extends
하는 방법이 있다.
위 코드에서 WebSecurityConfigurerAdapter
를 extends
방식 사용
WebSecurityConfigurerAdapter의 configure
메소드를 오버라이드
.authorizeRequests()
: HttpServletRequest
를 사용하는 요청들에 대한 접근제한을 설정하겠다..andMatchers(path).permitAll()
: path에 대한 요청은 인증없이 접근을 허용하겠다..anyRequest().authenticated()
: 나머지 요청들은 모두 인증되어야한다.JwtTokenProvider
@RequiredArgsConstructor
@Component
public class JwtTokenProvider { // 토큰 생성, 검증
private String secretKey = "clonekurlyclonekurlyclonekurlyclonekurlyclonekurlyclonekurlyclonekurly";
private long tokenValidTime = 30*60*1000L;
private final UserDetailsService userDetailsService;
protected void init(){
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
public String createToken(String userPk){
Claims claims = Jwts.claims().setSubject(userPk);
Date now = new Date();
return Jwts.builder()
.setClaims(claims) // 정보 저장
.setIssuedAt(now) // 토큰 발행 시간 정보
.setExpiration(new Date(now.getTime() + tokenValidTime)) // set Expire Time
.signWith(SignatureAlgorithm.HS256, secretKey) // 사용할 암호화 알고리즘과
// signature 에 들어갈 secret값 세팅
.compact();
}
// JWT 토큰에서 인증 정보 조회
public Authentication getAuthentication(String token) {
UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUserPk(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
// 토큰에서 회원 정보 추출
public String getUserPk(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}
// Request의 Header에서 token 값을 가져옵니다. "X-AUTH-TOKEN" : "TOKEN값'
public String resolveToken(HttpServletRequest request) {
return request.getHeader("X-AUTH-TOKEN");
}
// 토큰의 유효성 + 만료일자 확인
public boolean validateToken(String jwtToken) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtToken);
return !claims.getBody().getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}
}
@Component
: spring IoC 컨테이너에 Bean을 등록. 개발자가 직접 작성한 class를 Bean으로 등록하려할 때
claim
: JWT의 payload에 담기는 정보의 한 조각
JwtAuthenticationFilter
GenericFilterBean
을 extends
해서 doFilter override
, 실제 필터링 로직은 doFilter
내부에 작성doFilter
: 토큰의 인증정보를 SecurityContext
에 저장하는 역할수행@RequiredArgsConstructor
public class JwtAuthenticationFilter extends GenericFilterBean { //토큰확인, 유저정보 받아서 전달
private final JwtTokenProvider jwtTokenProvider;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 헤더에서 JWT 를 받아옵니다.
String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);
// 유효한 토큰인지 확인합니다.
if (token != null && jwtTokenProvider.validateToken(token)) {
// 토큰이 유효하면 토큰으로부터 유저 정보를 받아옵니다.
Authentication authentication = jwtTokenProvider.getAuthentication(token);
// SecurityContext 에 Authentication 객체를 저장합니다.
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
https://m.blog.naver.com/mds_datasecurity/221971440548
https://imbf.github.io/spring/2020/06/29/Spring-Security-with-JWT.html