implementation 'io.jsonwebtoken:jjwt:0.9.1'
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtTokenProvider jwtTokenProvider;
@Bean
public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().disable()
.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/member/**").hasRole("USER")
.antMatchers("/**").permitAll()
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
UsernamePasswordAuthenticationFilter.class);
}
}
@RequiredArgsConstructor
@Component
public class JwtTokenProvider {
@Value("${spring.jwt.secret}")
private String secretKey;
// 토큰 유효시간 30분
private long tokenValidTime = 30 * 60 * 1000L;
private final UserDetailsService userDetailsService;
// 객체 초기화, secretKey를 Base64로 인코딩한다.
@PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
// JWT 토큰 생성
public String createToken(String userPk, List<String> roles) {
Claims claims = Jwts.claims().setSubject(userPk); // JWT payload 에 저장되는 정보단위
claims.put("roles", roles); // 정보는 key / value 쌍으로 저장된다.
Date now = new Date();
return Jwts.builder()
.setClaims(claims) // 정보 저장
.setIssuedAt(now) // 토큰 발행 시간 정보
.setExpiration(new Date(now.getTime() + tokenValidTime)) // set Expire Time
.signWith(SignatureAlgorithm.HS256, secretKey) // 사용할 암호화 알고리즘과
// signature 에 들어갈 secret값 세팅
.compact();
}
// JWT 토큰에서 인증 정보 조회
public Authentication getAuthentication(String token) {
UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUserPk(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
// 토큰에서 회원 정보 추출
public String getUserPk(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}
// Request의 Header에서 token 값을 가져옵니다. "X-AUTH-TOKEN" : "TOKEN값'
public String resolveToken(HttpServletRequest request) {
return request.getHeader("X-AUTH-TOKEN");
}
// 토큰의 유효성 + 만료일자 확인
public boolean validateToken(String jwtToken) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtToken);
return !claims.getBody().getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}
}
secretkey는 HS256 알고리즘을 사용했습니다.
application.yml에 작성해두었습니다.
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends GenericFilterBean {
private final JwtTokenProvider jwtTokenProvider;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 헤더에서 JWT 를 받아옵니다.
String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);
// 유효한 토큰인지 확인합니다.
if (token != null && jwtTokenProvider.validateToken(token)) {
// 토큰이 유효하면 토큰으로부터 유저 정보를 받아옵니다.
Authentication authentication = jwtTokenProvider.getAuthentication(token);
// SecurityContext 에 Authentication 객체를 저장합니다.
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
@RequiredArgsConstructor
@Service
public class CustomMemberDetailService implements UserDetailsService {
private final MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return memberRepository.findByUserFormId(username)
.orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다."));
}
}
설계해놨던 유저 엔티티에 다음과 같은 코드를 추가해주셔야합니다.
@ElementCollection(fetch = FetchType.EAGER)
@Builder.Default
private List<String> roles = new ArrayList<>();
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.roles.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
@Override
public String getUsername() {
return userFormId;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
컨트롤러에서 로그인 api에
return jwtTokenProvider.createToken(member.getUsername(), member.getRoles());
을 해주면 토큰이 생성됩니다 😆