1๏ธโฃ JWT ๋ก ๋ฐฑ์๋ ๋ณดํธ
- ๊ธฐ์กด Basic Auth ๋ฐฉ์: username/password ํ์ธ๋ง ๊ฐ๋ฅ, ์ธ์
/ํ ํฐ ์ฒ๋ฆฌ ์์ โ React ๋ฑ ํ๋ก ํธ์์ ์ฌ์ฉ ๋ถํธ
- JWT: RESTful API์์ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ๋๋ ์ธ์ฆ/์ธ๊ฐ ๋ฐฉ์
JWT ๊ฐ๋
- Authentication (์ธ์ฆ): ๋ก๊ทธ์ธ ๊ณผ์
- Authorization (์ธ๊ฐ/๊ถํ): ์ธ์ฆ ํ ์ญํ ๊ธฐ๋ฐ ๊ถํ ๋ถ์ฌ
- JWT๋ URL, POST ํ๋ผ๋ฏธํฐ, Header ๋ฑ์ผ๋ก ์ ์ก ๊ฐ๋ฅ
- JWT ๊ตฌ์กฐ:
xxxxx.yyyyyy.zzzzz
| ๋ถ๋ถ | ๋ด์ฉ |
|---|
| Header | ํ ํฐ ์ ํ, ํด์ฑ ์๊ณ ๋ฆฌ์ฆ |
| Payload | ์ฌ์ฉ์ ์ ๋ณด (username, role ๋ฑ) |
| Signature | ํ ํฐ ๋ณ์กฐ ํ์ธ |
2๏ธโฃ JWT ์์ฑ ๋ฐ ๊ฒ์ฆ
jjwt ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์กด์ฑ
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
JwtService
@Component
public class JwtService {
static final long EXPIRATIONTIME = 86400000;
static final String PREFIX = "Bearer";
static final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
public String getToken(String username) {
return Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATIONTIME))
.signWith(key)
.compact();
}
public String getAuthUser(HttpServletRequest request) {
String token = request.getHeader(HttpHeaders.AUTHORIZATION);
if (token != null) {
String user = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token.replace(PREFIX, ""))
.getBody()
.getSubject();
if (user != null) return user;
}
return null;
}
}
3๏ธโฃ ๋ก๊ทธ์ธ ์์ฒญ ๋ฐ์ดํฐ์ฉ Record
public record AccountCredentials(String username, String password) {}
-
Record ํน์ง
- ๊ฐ๊ฒฐํจ: ํ๋๋ง ์ ์ํ๋ฉด getter/setter ์๋ ์์ฑ
- ๋ถ๋ณ์ฑ: ๋ชจ๋ ํ๋๋
private final
- ์๋ ์์ฑ:
AllArgsConstructor, toString(), equals() ๋ฑ
4๏ธโฃ Optional ์ฌ์ฉ
Optional<AppUser> user = userRepository.findByUsername(username);
user.ifPresent(u -> System.out.println(u.getUsername()));
String name = user.orElse(new AppUser("Guest","")).getUsername();
- NullPointerException ๋ฐฉ์ง, ํจ์ํ ์คํ์ผ ๊ฐ๋ฅ
5๏ธโฃ LoginController
@RestController
@RequiredArgsConstructor
public class LoginController {
private final JwtService jwtService;
private final AuthenticationManager authentication;
@PostMapping("/login")
public ResponseEntity<?> getToken(@RequestBody AccountCredentials credentials) {
UsernamePasswordAuthenticationToken creds =
new UsernamePasswordAuthenticationToken(credentials.username(), credentials.password());
Authentication auth = authentication.authenticate(creds);
String jwts = jwtService.getToken(auth.getName());
return ResponseEntity
.ok()
.header(HttpHeaders.AUTHORIZATION, "Bearer " + jwts)
.header(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "Authorization")
.build();
}
}
6๏ธโฃ SecurityConfig & AuthenticationManager
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtService jwtService;
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception{
return authConfig.getAuthenticationManager();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(new AuthenticationFilter(jwtService), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
7๏ธโฃ AuthenticationFilter
@RequiredArgsConstructor
public class AuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String jws = request.getHeader(HttpHeaders.AUTHORIZATION);
if (jws != null) {
String user = jwtService.getAuthUser(request);
Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, Collections.emptyList());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
}
8๏ธโฃ ์์ธ ์ฒ๋ฆฌ: AuthEntryPoint
public class AuthEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().println("Error : " + authException.getMessage());
}
}
- ๋ก๊ทธ์ธ ์คํจ ์ 401 ๋ฐํ, ์ปค์คํ
๋ฉ์์ง ์ถ๋ ฅ ๊ฐ๋ฅ
9๏ธโฃ ์ธ์ฆ ํ๋ฆ ๐
- ํด๋ผ์ด์ธํธ โ
POST /login โ username/password ์ ์ก
AuthenticationManager ๊ฒ์ฆ โ JWT ๋ฐ๊ธ
- ์ดํ ์์ฒญ โ
Authorization: Bearer <token> ํค๋ ํฌํจ
AuthenticationFilter ๊ฐ JWT ๊ฒ์ฆ ํ SecurityContext ์ค์
- Controller ์ ๊ทผ ๊ฐ๋ฅ
๐ ํต์ฌ ํฌ์ธํธ ๐ก
| ๊ธฐ๋ฅ | ํด๋์ค/์ธํฐํ์ด์ค | ์ค๋ช
|
|---|
| ์ธ์ฆ | UserDetailsService, AuthenticationManager | JWT ๋ฐ๊ธ ์ ๋ก๊ทธ์ธ ๊ฒ์ฆ |
| ๊ถํ | RoleVoter, @PreAuthorize | Role ๊ธฐ๋ฐ ์ ๊ทผ ์ ์ด |
| ์ธ์
| SecurityContextHolder | Stateless โ ์ธ์
์ฌ์ฉ ์ํจ |
| CSRF | csrf().disable() | REST API์์๋ ๋นํ์ฑํ |
| ๋ก๊ทธ์ธ | /login, LoginController | JWT ๋ฐ๊ธ |
| ์์ฒญ ๋ณดํธ | AuthenticationFilter | ๋ชจ๋ ์์ฒญ ์ธ์ฆ ์ฒ๋ฆฌ |