package com.codesign.base.jwt;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.codesign.base.auth.PrincipalDetails;
import com.codesign.base.model.User;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
@RequiredArgsConstructor
@Slf4j
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
log.info("========= attemptAuthentication : 로그인 시도중 =========");
try {
ObjectMapper objectMapper = new ObjectMapper();
User user = objectMapper.readValue(request.getInputStream(), User.class);
UsernamePasswordAuthenticationToken userToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
Authentication authenticate = authenticationManager.authenticate(userToken);
return authenticate;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
log.info("successfulAuthentication 인증 성공됨 ");
PrincipalDetails principal = (PrincipalDetails) authResult.getPrincipal();
// RSA방식은 아니고 Hash암호 방식
// HAMC > 시크릿값을 가지고 있어야함
String jwtToken = JWT.create()
.withSubject("cos토큰")
.withExpiresAt(new Date(System.currentTimeMillis() + (1000 * 60 * 30) ))// 만료시간 (30분) : 현재시간 + 사용시간
.withClaim("id", principal.getUser().getId())
.withClaim("username", principal.getUsername())
.sign(Algorithm.HMAC512(JwtProperties.SECRET));
response.addHeader(JwtProperties.HEADER_STRING, JwtProperties.TOKEN_PREFIX + jwtToken);
}
}
authenticationManager
는 WebSecurityConfigurerAdapter
에 정의되어 있어서 인자로 넘겨 받음attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
UsernamePasswordAuthenticationFilter
를 타게 되는데, attemptAuthentication()
는 인증시도하는 메서드다.속성 | 설명 |
---|---|
objectMapper.readValue() | 파라미터로 넘어온 username, password를 User 객체에 맵핑 |
new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()) | username, password로 security 토큰 생성 |
authenticationManager.authenticate(userToken) | userDetailsService.loadUserByUsername() 호출해서 인증정보를 받아 return |
attemptAuthentication()
에서 인증 성공시 successfulAuthentication()
를 타게 된다. 여기에서 JWT토큰을 생성해서 response Header에 Authorization=JWT토큰
을 실어보낸다.속성 | 설명 |
---|---|
create() | JWT 토큰 생성 |
withSubject("cos토큰") | subject 설정 |
withExpiresAt() | 만료시간 설정 |
withClaim() | claim 추가 |
sign() | 시크릿 값을 넣어 서명 |
JwtProperties.SECRET | 서버에서만 관리되도록해야함 |
JwtProperties.java
package com.codesign.base.jwt;
// TODO .properties 파일로 변경하기
public interface JwtProperties {
String SECRET = "codesign123";
int EXPIRATION_TIME = 1000 * 60 * 30;
String TOKEN_PREFIX = "Bearer ";
String HEADER_STRING = "Authorization";
}
SecurityConfig.java
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserService userService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); // 위변조 방지 미사용
http.cors().configurationSource(corsFilter()) // cors설정
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // session 미사용
.and().formLogin().disable() // formlogin 미사용
.httpBasic().disable() // Baerer 사용으로 Basic방식 미사용
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
// .addFilter(new JwtAuthorizationFilter(authenticationManager(), userService))
.authorizeRequests()
.antMatchers("/api/v1/user/**").permitAll()
.antMatchers("/api/v1/manager/**").access("hasRole('ROLE_MANAGER')")
.anyRequest().permitAll();
}
...
}
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
추가
authenticationManager()
는 WebSecurityConfigurerAdapter
에 정의 되어있음참고
https://www.youtube.com/watch?v=GEv_hw0VOxE&list=PL93mKxaRDidERCyMaobSLkvSPzYtIk0Ah