[image:B311F605-850C-4E96-8DA1-081A2BB8B670-37344-00001564992BB504/스크린샷 2022-07-14 오후 8.20.39.png]
A Token can be a plain string of format universally unique identifier (UUID) or it can be of type JSON Web Token (JWT) usually that get generated when the user authenticated for the first time during login
이러한 토큰을 공유함으로써 Credential 을 직접적으로 네트워크에서 공유하는 행위를 줄일 수 있다.
Token helps us not to share the credentials for every request which is a security risk to make credentials send over the network frequently.
Tokens can be invalidated during any suspicious activities without invalidating the user credentials.
Tokens can be created with a short life span.
Tokens can be used to store the user related information like roles/authorities etc.
Reusability - We can have many separate servers, running on multiple platforms and domains, reusing the same token for authenticating the user.
Security - Since we are not using cookies, we don’t have to protect against cross-site request forgery (CSRF) attacks.
Stateless, easier to scale. The token contains all the information to identify the user, eliminating the need for the session state. If we use a load balancer, we can pass the user to any server, instead of being bound to the same server we logged in on. 51
JWT 는 3파트로 나뉜다.
‘.’ 으로 나뉜다.
[image:5283AA6C-4236-4497-B30C-B2E20ED5DCB6-37344-00001569DCC60F82/스크린샷 2022-07-14 오후 8.36.22.png]
[image:8E8C36CE-E2CA-414F-9FA6-EBA2E29C69A3-37344-0000156A1F058C3D/스크린샷 2022-07-14 오후 8.37.08.png]
Signature 는 optional part 이다. 하지만 중요하다.
우리 서버로 들어온 JWT 토큰이 과연 우리가 만든 토큰인지 아닌지 검증하는 기능을 한다.
[image:0D3B5C75-D109-41AF-BF55-D09BFE21AA9B-37344-0000156B5A3BBC4E/스크린샷 2022-07-14 오후 8.40.48.png]
[image:C9CDD45E-756F-4614-9CBE-6066F840FE91-37344-0000156BBA7D436D/스크린샷 2022-07-14 오후 8.41.55.png]
왜 signature 부분만 암호화해서 보낼까? Header 와 payload 부분도 encoding 해서 보내면 되지 않을까?
암호화하는 알고리즘은 서버의 컴퓨팅 자원을 많이 쓰기 때문에 주의해야한다.
이 의존성을 추가한다.
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
그리고 csrf 를 더이상 사용하지 않는다.
JWT 자체가 토큰이기 때문이다.
그리고 기본적으로 JSESSION 이 발급되는것도 막아준다.
그리고 이 api 서버에 접속한 모든 프론트에 Authorization 헤더가 노출되게끔 설정을 cors 에서 해준다.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().cors().configurationSource(request -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Collections.singletonList("*"));
config.setAllowedMethods(Collections.singletonList("*"));
config.setAllowedHeaders(Collections.singletonList("*"));
config.setMaxAge(3600L);
config.setAllowCredentials(true);
config.setExposedHeaders(List.of("Authorization"));
return config;
})
.and().csrf().disable()
.authorizeRequests()
.mvcMatchers(HttpMethod.GET, "/user/check/nickname").permitAll()
.mvcMatchers(HttpMethod.POST, "/user/signup", "/user/signin").permitAll()
.and().httpBasic();
}
public class JWTTokenGeneratorFilter extends OncePerRequestFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (null != authentication) {
SecretKey key = Keys.hmacShaKeyFor(SecurityConstants.JWT_KEY.getBytes(StandardCharsets.UTF_8));
String jwt = Jwts.builder().setIssuer("homes").setSubject("JWT Token")
.claim("username", authentication.getName()) // claim 은 payload 부분에 들어갈 데이터다.
.claim("authorities", populateAuthorities(authentication.getAuthorities()))
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + 3000000)) // 30분뒤 expired
.signWith(key).compact();
response.setHeader(SecurityConstants.JWT_HEADER, jwt);
}
chain.doFilter(request, response);
}
// 로그인 api 가 아니면 동작하지 않도록 만듦.
// 회원 가입시에도 토큰이 발급되지 않음.
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return !request.getServletPath().equals("/user/signin");
}
private String populateAuthorities(Collection<? extends GrantedAuthority> collection) {
Set<String> authoritiesSet = new HashSet<>();
for (GrantedAuthority authority : collection) {
authoritiesSet.add(authority.getAuthority());
}
return String.join(",", authoritiesSet);
}
}
public class JWTTokenValidatorFilter extends OncePerRequestFilter {
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String jwt = request.getHeader(SecurityConstants.JWT_HEADER);
if (null != jwt) {
try {
SecretKey key = Keys.hmacShaKeyFor(
SecurityConstants.JWT_KEY.getBytes(StandardCharsets.UTF_8));
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(jwt)
.getBody();
String username = String.valueOf(claims.get("username"));
String authorities = (String) claims.get("authorities");
Authentication auth = new UsernamePasswordAuthenticationToken(username,null,
AuthorityUtils.commaSeparatedStringToAuthorityList(authorities));
SecurityContextHolder.getContext().setAuthentication(auth);
}catch (Exception e) {
throw new BadCredentialsException("Invalid Token received!");
}
}
chain.doFilter(request, response);
}
// 로그인, 회원가입인 경우에는 필터가 돌아가지 않도록 한다.
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return request.getServletPath().equals("/user/signin") ||
request.getServletPath().equals("/user/signup") ||
request.getServletPath().equals("/user/signup/admin");
}
}