🎵순서
1. gradle에 필요한 의존성 추가
2. properties파일에 시크릿 키 설정
3. 쿠키를 담아서 보낼 클래스 생성
4. 토큰 생성 및 재발급 해주는 클래스 생성
5. 토큰 유효성 검사 필터 생성
6. 시큐리티 설정
7. UserDetailsService 를 상속받은 유저 검증 서비스 클래스 생성
8. 서비스에서 로그인 메소드 생성
9. 컨트롤러에서 로그인 메소드 생성
코드
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
jwt.secret = VlwEyVBsYt9V7zq57TejMnVUyzblYcfPQye08f7MGVA9XkHa
@Builder
@Data
@AllArgsConstructor
public class TokenInfo {
private String grantType;
private String accessToken;
private String refreshToken;
}
@Slf4j
@Component
public class JwtTokenProvider {
private final Key key;
public JwtTokenProvider(@Value("${jwt.secret}") String secretKey) {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
this.key = Keys.hmacShaKeyFor(keyBytes);
}
public TokenInfo generateToken(Authentication authentication) {
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
long now = (new Date()).getTime();
Date accessTokenExpiresIn = new Date(now + 86400000);
String accessToken = Jwts.builder()
.setSubject(authentication.getName())
.claim("auth", authorities)
.setExpiration(accessTokenExpiresIn)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
String refreshToken = Jwts.builder()
.setExpiration(new Date(now + 86400000))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
return TokenInfo.builder()
.grantType("Bearer")
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
}
public Authentication getAuthentication(String accessToken) {
Claims claims = parseClaims(accessToken);
if (claims.get("auth") == null) {
throw new RuntimeException("권한 정보가 없는 토큰입니다.");
}
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get("auth").toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
UserDetails principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, "", authorities);
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
log.info("Invalid JWT Token", e);
} catch (ExpiredJwtException e) {
log.info("Expired JWT Token", e);
} catch (UnsupportedJwtException e) {
log.info("Unsupported JWT Token", e);
} catch (IllegalArgumentException e) {
log.info("JWT claims string is empty.", e);
}
return false;
}
private Claims parseClaims(String accessToken) {
try {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(accessToken).getBody();
} catch (ExpiredJwtException e) {
return e.getClaims();
}
}
}
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends GenericFilterBean {
private final JwtTokenProvider jwtTokenProvider;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String token = resolveToken((HttpServletRequest) request);
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication authentication = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer")) {
return bearerToken.substring(7);
}
return null;
}
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/**").permitAll()
.antMatchers("/members/test").hasRole("MEMBER") // 해당 메소드는 MEMBER 권한을 가진 회원만 허용
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return memberRepository.findById(username)
.map(this::createUserDetails)
.orElseThrow(() -> new CommonException(ErrorCode.ID_NOT_FOUND));
}
private UserDetails createUserDetails(Member member) {
return User.builder()
.username(member.getUsername())
.password(member.getPassword())
.roles(member.getAuth().toString())
.build();
}
}
@Service
@RequiredArgsConstructor
public class MemberServiceImpl{
...
private final AuthenticationManagerBuilder authenticationManagerBuilder;
private final JwtTokenProvider jwtTokenProvider;
...
@Override
public TokenInfo login(String memberId, String password) throws BadCredentialsException {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(memberId, password);
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
TokenInfo tokenInfo = jwtTokenProvider.generateToken(authentication);
return tokenInfo;
}
}
@RestController
@RequiredArgsConstructor
@RequestMapping("/members")
public class MemberController {
@PostMapping("/sign-in")
public TokenInfo login(@RequestBody MemberLoginRequestDto memberLoginRequestDto) {
String memberId = memberLoginRequestDto.getMemberId();
String password = memberLoginRequestDto.getPassword();
TokenInfo tokenInfo = memberService.login(memberId, password);
return tokenInfo;
}
}
@Entity
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
@Getter
@Table(name = "member_tbl")
public class Member extends CommonTime implements UserDetails {
@Id
@Column(length = 20)
private String memberId;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String name;
@Enumerated(EnumType.STRING)
private Auth auth;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> roles = new HashSet<>();
for (String role : auth.getValue().split(",")) {
roles.add(new SimpleGrantedAuthority(role));
}
return roles;
}
@Override
public String getUsername() {
return memberId;
}
@Override
public String getPassword() {
return password;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
@AllArgsConstructor
@Getter
public enum Auth {
ADMIN("ROLE_ADMIN,ROLE_MEMBER"),
MEMBER("ROLE_MEMBER");
private String value;
}
참고 게시물
https://gksdudrb922.tistory.com/217