반응형
의존성 추가
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("io.jsonwebtoken:jjwt:0.9.1")
implementation("javax.xml.bind:jaxb-api:2.3.1")
SecurityConfig
@EnableWebSecurity
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(
HttpSecurity http,
UserDetailsService userDetailsService,
JwtTokenService jwtTokenService
) throws Exception {
return http
.authorizeRequests(authorize -> {
authorize.requestMatchers("/api/v**/authentication").permitAll();
authorize.anyRequest().authenticated();
})
.sessionManagement(session -> {
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
})
.httpBasic(Customizer.withDefaults())
.csrf(csrf -> {
csrf.disable();
})
.addFilterBefore(new JwtAuthenticationFilter(userDetailsService, jwtTokenService), UsernamePasswordAuthenticationFilter.class)
.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
User.UserBuilder users = User.builder();
UserDetails user = users
.username("tyler")
.password(passwordEncoder.encode("1234"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
JwtAuthenticationFilter
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
private final JwtTokenService jwtTokenService;
private static final String HEADER_AUTHORIZATION = "Authorization";
private static final String TOKEN_PREFIX = "Bearer ";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
UserToken userToken = Optional.ofNullable(request.getHeader(HEADER_AUTHORIZATION))
.filter(it -> it.startsWith(TOKEN_PREFIX))
.map(it -> it.substring(TOKEN_PREFIX.length()))
.map(jwtTokenService::parse)
.filter(it -> !it.isExpired())
.filter(it -> it.getUsername() != null)
.orElse(null);
if (userToken != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(userToken.getUsername());
if (userDetails != null) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(request, response);
}
}
JwtService
@RequiredArgsConstructor
@Service
public class JwtTokenService {
private static final String SIGNING_KEY = "test-signing-key";
public String createToken(UserToken userToken) {
return Jwts.builder()
.setSubject("user")
.claim("username", userToken.getUsername())
.setExpiration(userToken.getExpiration())
.signWith(SignatureAlgorithm.HS512, SIGNING_KEY.getBytes())
.compact();
}
public UserToken parse(String token) {
Claims claims = Jwts.parser()
.setSigningKey(SIGNING_KEY.getBytes())
.parseClaimsJws(token)
.getBody();
return UserToken.builder()
.username(claims.get("username", String.class))
.expiration(claims.getExpiration())
.build();
}
}
UserToken
@Getter
@Builder
public class UserToken {
private final String username;
private final Date expiration;
public boolean isExpired() {
return new Date().getTime() > expiration.getTime();
}
}
AuthenticationDto
public class AuthenticationDto {
@Getter
@RequiredArgsConstructor
public static class Req {
private final String username;
private final String password;
}
@Getter
@Builder
public static class Res {
private final String token;
}
}
AuthenticationController
@RequiredArgsConstructor
@RestController
public class AuthenticationController {
private final UserDetailsService userDetailsService;
private final JwtTokenService jwtTokenService;
private final PasswordEncoder passwordEncoder;
private final long EXPIRATION_TIME = 864_000_000; // 10일 (단위: 밀리초)
@PostMapping("/api/v1/authentication")
public AuthenticationDto.Res authentication(@RequestBody AuthenticationDto.Req request) {
String token = Optional.ofNullable(userDetailsService.loadUserByUsername(request.getUsername()))
.filter(it -> passwordEncoder.matches(request.getPassword(), it.getPassword()))
.map(it -> jwtTokenService.createToken(UserToken.builder()
.username(request.getUsername())
.expiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.build()))
.orElseThrow(() -> new IllegalStateException("user not found. username: " + request.getUsername()));
return AuthenticationDto.Res.builder()
.token(token)
.build();
}
}
토큰 발급
요청
curl -d '{"username":"tyler", "password":"1234"}' -H 'Content-Type: application/json' -X POST http://localhost:8080/api/v1/authentication
응답
{"token":"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ1c2VyIiwidXNlcm5hbWUiOiJ0eWxlciIsImV4cCI6MTY5Njg2NjQ3Mn0.Bpfx9yfQvS7B_fGzv2VlfuIW4tT72n7be97olVJe_L7-WgpCAbtBi3CrNZIffboCaGR5zLr-uHDSYRmyp1A45Q"}
요청 테스트
위에서 응답으로 받은 토큰 값을 Authorization 헤더로 세팅
curl -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ1c2VyIiwidXNlcm5hbWUiOiJ0eWxlciIsImV4cCI6MTY5Njg2NjQ3Mn0.Bpfx9yfQvS7B_fGzv2VlfuIW4tT72n7be97olVJe_L7-WgpCAbtBi3CrNZIffboCaGR5zLr-uHDSYRmyp1A45Q' http://localhost:8080/api/v1/demo
반응형
'Development > Spring Security' 카테고리의 다른 글
[Spring Security] Test (0) | 2023.10.29 |
---|---|
[Spring Security] WebSocketSecurity (0) | 2023.10.29 |
[Spring Security] OAuth2 Authentication (0) | 2023.10.29 |
[Spring Security] Basic Authentication (0) | 2023.10.29 |
[Spring Security] Form Authentication (0) | 2023.10.29 |