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에 담기는 정보의 한 조각
JwtAuthenticationFilterGenericFilterBean을 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