Spring Security, JWT 적용하기
이번엔 보안요소인 Spring Security를 사용해보도록 하겠습니다. 일단은 build.gradle
에 라이브러리를 추가해야 하지만 저는 프로젝트 생성할 때에 미리 추가를 해놓았습니다.
그리고 추가로 JWT(JSON Web Token)을 사용하여 인증할 것 이기 때문에 JWT도 추가하였습니다.
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt:0.9.1'
일단은 security를 사용하기 위해서는 몇몇의 설정이 필요한데 이를 한 곳에 모아놓기 위해서 폴더를 만들어 관리하면 사용하기 편리합니다.
저는 첫째로 SecurityConfig를 만들었습니다.
@Configuration
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtTokenProvider jwtTokenProvider;
@Override
protected void configure(HttpSecurity http) throws Exception{
http
.httpBasic().disable()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/user/login", "/user").permitAll()
.anyRequest().hasRole("USER")
.anyRequest().hasRole("ADMIN")
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
}
}
두 번째로는 JWT 토큰에 대한 생성과 사용을 설정해 주는 JwtTokenProvider를 만들었습니다.
@RequiredArgsConstructor
@Component
public class JwtTokenProvider {
@Value("${spring.jwt.secret")
private String secretKey;
private long tokenValidMilliSecond = 1000L * 60 * 120; //2시간 유효기간
private final UserDetailsService userDetailsService;
/**
* 잘 모른다! 알아보자!
* */
@PostConstruct
protected void init() { secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); }
//유저 정보와 등급을 통하여 토큰을 생성해냅니다.
public String createToken(String username, String role){
Claims claims = Jwts.claims().setSubject(username);
claims.put("role", role);
Date now = new Date();
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + tokenValidMilliSecond))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
public Authentication getAuthentication(String token){
UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUserName(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
//토큰을 이용하여 username 을 얻어낸다.
public String getUserName(String token){
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}
//Header를 통해 받아온 토큰에서 필요 없는 부분을 잘라낸다.
public String resolveToken(HttpServletRequest req){
String myToken = req.getHeader("Authorization");
if(myToken == null){
return null;
}
String realToken = myToken.substring("Bearer".length());
return realToken;
}
//토큰의 유효기간이 지났는지 아닌지 확인한다.
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;
}
}
}
마지막으로는 Security과정에서 Jwt인증 과정을 시행하게 하기 위한 JwtAuthenticationFilter 를 만들어 주었습니다.
/**
* JWT를 이용한 실질적인 인증 절차를 진행하는 곳 입니다.
* */
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends GenericFilterBean {
private final JwtTokenProvider jwtTokenProvider;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);
if (token != null && jwtTokenProvider.validateToken(token)){
Authentication auth = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(request, response);
}
}
이 세가지 파일을 이용해 JWT 토큰을 생성하고, 인증하는 방식을 사용하였습니다.
각 코드에 대한 설명은 차차 정리해서 업데이트 하도록 하겠습니다.
해당 내용의 프로젝트는 Github에서 확인할 수 있습니다.