빙응의 공부 블로그
[Spring]Spring Security + JWT(2) 본문
🚩 전 포스팅가기
[Spring]Spring Security + JWT(1) (tistory.com)
[Spring]Spring Security + JWT(1)
📝JWT를 구현해보자! JWT를 간단하게 구현해보자. 나는 2가지 토큰을 이용한 방식을 사용하지 않고 1가지 토큰만 사용하는 방식을 사용하겠다! 📝1. 회원가입 구현 회원가입은 통상의 Spring Security
quddnd.tistory.com
📝5. JWT 토큰 발급 및 검증하기
🚩전 포스팅에서 로그인 로직을 만들었으니 JWT 토큰의 발급과 검증을 만들어보자.
토큰 발급 방식
우리는 JWT에 대한 로그인 로직을 생각해봐야한다.
로그인을 성공하면 토큰을 발급하고 다음에 로그인 시도 시에 토큰 유무를 검사하여 로그인을 패스해야한다.
로그인 시도 -> 인증 성공 -> 토큰 발급 -> 로그인 시도 -> 토큰 검사
이런 식으로 세션처럼 토큰을 이용해 자동 로그인을 구현해야한다.
그러려면 로그인 로직보다 토큰 검사가 먼저 와야 한다.
📝6. 토큰 발급
토큰에 대한 설명은 전 포스팅
JWT 개요
📝JWT JWT(JSON Web Token) JWT란 JSON 객체로 당사자 간에 정보를 안전하게 전송하기 위한 간결하고 독립적인 방법을 정의하는 개방형 표준이다. JWT는 비밀키 혹은 RSA 등을 사용하여 공개/개인 키 알고
quddnd.tistory.com
🚩비밀키를 만들자
대칭키 방식 HS256을 사용하기 위해서는 32자의 비밀키가 필요하다.
appcation.propertise
spring.jwt.secret=vmfhaltmskdlstkfkdgodyroqkfwkdbalroqkfwkdbalaaaaaaaaaaaaaaaabbbbb
🚩JWT 토큰 생성을 위한 JWTUtill 클래스 만들기
- 우리는 JWT 토큰을 생성하기 위해 jwts라는 JWT 생성, 검증을 위한 유틸리티 클래스를 사용할 것이다.
@Component
public class JWTUtil {
private SecretKey secretKey;
public JWTUtil(@Value("${spring.jwt.secret}")String secret) {
secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm());
}
public String getUsername(String token) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("username", String.class);
}
public String getRole(String token) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("role", String.class);
}
public Boolean isExpired(String token) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date());
}
public String createJwt(String username, String role, Long expiredMs) {
return Jwts.builder()
.claim("username", username)
.claim("role", role)
.issuedAt(new Date(System.currentTimeMillis()))
.expiration(new Date(System.currentTimeMillis() + expiredMs))
.signWith(secretKey)
.compact();
}
}
🚩jwts를 통해 JWT토큰 생성 및 필요한 정보를 가져올 수 있다.
- isExpired 메소드는 토큰의 만료 유무를 판별하는 메소드이다.
📝7. 로그인 로직에 토큰 발급 설정하기
🚩 JWTUtil을 통해 토큰 발급을 만들었기 때문에 로그인이 성공하면 토큰을 발급하게 하자
@RequiredArgsConstructor
@Slf4j
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final JWTUtil jwtUtil;
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = obtainUsername(request);
String password = obtainPassword(request);
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, null);
return authenticationManager.authenticate(authToken);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) {
CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal();
String username = customUserDetails.getUsername();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
GrantedAuthority auth = iterator.next();
String role = auth.getAuthority();
String token = jwtUtil.createJwt(username, role, 60*60*10L);
response.addHeader("Authorization", "Bearer " + token);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
response.setStatus(401);
}
}
🚩 1편에서 만들었던 LoginFilter의 successfulAuthentication에 로직을 구현하면 된다.
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
- 이 구문은 사용자의 역할을 가져오는 것으로
- Collection<? extends GrantedAuthority> 이러한 형식을 취한 이유는
- 권한 정보를 다양한 타입의 객체로 처리할 수 있고, 더 큰 유연성과 확장성을 가질 수 있기 때문이다.
- Collection<? extends GrantedAuthority> 이러한 형식을 취한 이유는
response.addHeader("Authorization", "Bearer " + token);
- response 헤더에 Authorization에 Bearer + 32개의 정보를 가진 토큰이 저장된다.
📝8. JWT 검증 필터 만들기
🚩 JWT 토큰 발급도 완료하였기 때문에 JWT 검증을 만들어보자
public class JWTFilter extends OncePerRequestFilter {
private final JWTUtil jwtUtil;
public JWTFilter(JWTUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//request에서 Authorization 헤더를 찾음
String authorization= request.getHeader("Authorization");
//Authorization 헤더 검증
if (authorization == null || !authorization.startsWith("Bearer ")) {
System.out.println("token null");
filterChain.doFilter(request, response);
//조건이 해당되면 메소드 종료 (필수)
return;
}
System.out.println("authorization now");
//Bearer 부분 제거 후 순수 토큰만 획득
String token = authorization.split(" ")[1];
//토큰 소멸 시간 검증
if (jwtUtil.isExpired(token)) {
System.out.println("token expired");
filterChain.doFilter(request, response);
//조건이 해당되면 메소드 종료 (필수)
return;
}
//토큰에서 username과 role 획득
String username = jwtUtil.getUsername(token);
String role = jwtUtil.getRole(token);
//userEntity를 생성하여 값 set
UserEntity userEntity = new UserEntity();
userEntity.setUsername(username);
userEntity.setPassword("temppassword");
userEntity.setRole(role);
//UserDetails에 회원 정보 객체 담기
CustomUserDetails customUserDetails = new CustomUserDetails(userEntity);
//스프링 시큐리티 인증 토큰 생성
Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities());
//세션에 사용자 등록
SecurityContextHolder.getContext().setAuthentication(authToken);
filterChain.doFilter(request, response);
}
}
🚩어렵지 않은 코드라서 이해하기 쉬울 것이다.
- 여기서 filterChain.doFilter는 다음 필터를 실행시키는 것이다.
📝9. Security Config에 체인 설정하기
🚩 JWT 토큰 검증을 만들었기 때문에 체인 설정이 필요하다.
- 중요한 것은 로그인 필터 앞에 설정하는 것이다.
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
//AuthenticationManager가 인자로 받을 AuthenticationConfiguraion 객체 생성자 주입
private final AuthenticationConfiguration authenticationConfiguration;
private final JWTUtil jwtUtil;
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//csrf disable
http
.csrf((auth) -> auth.disable());
//From 로그인 방식 disable
http
.formLogin((auth) -> auth.disable());
//http basic 인증 방식 disable
http
.httpBasic((auth) -> auth.disable());
//JWTFilter 등록
//로그인 필터보다 앞에 두어 자동로그인 구현
http
.addFilterBefore(new JWTFilter(jwtUtil), LoginFilter.class);
//필터 추가 LoginFilter()는 인자를 받음 (AuthenticationManager() 메소드에 authenticationConfiguration 객체를 넣어야 함) 따라서 등록 필요
http
.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration),jwtUtil), UsernamePasswordAuthenticationFilter.class);
//경로별 인가 작업
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/login", "/", "/join").permitAll()
.requestMatchers("/admin").hasRole("ADMIN")
.anyRequest().authenticated());
//세션 설정
//JWT 방식은 항상 상태없음 방식으로 동작하기에 설정해줘야 한다.
http
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}
//JWTFilter 등록
//로그인 필터보다 앞에 두어 자동로그인 구현
http
.addFilterBefore(new JWTFilter(jwtUtil), LoginFilter.class);
//필터 추가 LoginFilter()는 인자를 받음 (AuthenticationManager() 메소드에 authenticationConfiguration 객체를 넣어야 함) 따라서 등록 필요
http
.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration),jwtUtil), UsernamePasswordAuthenticationFilter.class);
🚩위처럼 addFilter 명령어를 통해 Security Config에 필터 우선순위를 정할 수 있다.
📝테스트 해보기
로그인 요청을 하면 정상적으로 Authorization이 발급된걸 알 수 있다.
헤더에 jwt 토큰을 넣고 사이트 접속 시 자동으로 인증이 되는 것을 알 수 있다!!!
📝보완해야 하는 점
저 위의 프로젝트에서 보완해야 하는 점을 알아보자
1. 클라이언트에서 토큰 설정
로그인 요청 시 헤더를 포함해서 전달을 해야한다.
하지만 그것을 설정하지 않았다.
2. 2개의 토큰
왜 다른 사람들은 2개의 토큰을 가지고 JWT를 구현할까?
🚩 2개의 토큰을 쓰면 장점
- 1. 보안 강화
- 리소스의 접근하는 토큰의 유효기간을 짧게 하고 노출이 적은 토큰을 이용해 발급하여 보안성을 높일 수 있다.
- 2. 각 토큰의 권한 분리의 효율성
- 각 토큰들을 따로 다른 권한으로 분리하여 유연성과 효율성을 챙길 수 있음
3. Redis?
- Redis는 세션 혹은 토큰을 따로 저장할 수 있는 키-값으로 이루어진 일종의 DB이다.
- Redis를 이용해 토큰의 보안을 올릴 수 있고 확장성 또한 높일 수 있다.
Documentation
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker
redis.io
'Spring > 개인공부_실습' 카테고리의 다른 글
[React + Spring]리액트와 스프링으로 API 받아서 처리하기 (0) | 2024.05.20 |
---|---|
[Spring]Spring - RestTemplate (0) | 2024.05.19 |
[React + Spring]리액트와 스프링 - 연동 (0) | 2024.05.17 |
[Spring]Spring Security + JWT(1) (1) | 2024.04.07 |
[Spring]스프링 시큐리티 - 간단한 실습 (0) | 2024.03.24 |