티스토리 뷰
jwt token에 대한 내용은 앞서 살펴본 "Spring Security로 로그인 구현하기" 포스팅과 연결됩니다.
1. jwt 의존 추가
// jwt
implementation 'io.jsonwebtoken:jjwt:0.9.1'
2. jwt token을 생성할지 등을 나타내는 클래스 구현
package com.example.securityspring.security;
import com.example.securityspring.model.Role;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@RequiredArgsConstructor
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String secretKey;
// 토큰 유효시간 30분
@Value("${jwt.access-expired}")
private long tokenValidTime;
private final UserDetailsServiceImpl userDetailsService;
// 객체 초기화, secretKey를 Base64로 인코딩
@PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
public String generateToken(UserDetailsImpl userDetails) {
Map<String, Object> claims = new HashMap<>();
val isAdmin = userDetails.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_"+ Role.ADMIN));
if (isAdmin) {
claims.put("role","admin");
} else {
claims.put("role","user");
}
val myName = userDetails.getName();
claims.put("myName", myName);
return doGenerateToken(claims, userDetails.getUsername());
}
private String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setHeaderParam("typ","JWT")
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + tokenValidTime * 1000))
.signWith(SignatureAlgorithm.HS256, secretKey)
.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 값을 가져옴.
public String resolveToken(HttpServletRequest request) {
return request.getHeader(HttpHeaders.AUTHORIZATION);
}
// 토큰의 유효성 + 만료일자 확인
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;
}
}
}
3. jwt 검증 과정을 처리할 filter 생성
package com.example.securityspring.security;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Slf4j
@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 && token.startsWith("Bearer ")) {
val jwtToken = token.substring(7);
if (jwtTokenProvider.validateToken(jwtToken)) {
// 토큰이 유효하면 토큰으로부터 유저 정보를 받아옴
Authentication authentication = jwtTokenProvider.getAuthentication(jwtToken);
// SecurityContext 에 Authentication 객체를 저장
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} else {
log.debug("JWT Token does not begin with Bearer String");
}
chain.doFilter(request, response);
}
}
4. 로그인 기능에 jwt 적용하여 응답하기
package com.example.securityspring.service;
import com.example.securityspring.domain.Member;
import com.example.securityspring.dto.JwtRequestDto;
import com.example.securityspring.dto.JwtResponseDto;
import com.example.securityspring.dto.MemberSignupRequestDto;
import com.example.securityspring.repository.MemberRepository;
import com.example.securityspring.security.JwtTokenProvider;
import com.example.securityspring.security.UserDetailsImpl;
import lombok.AllArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
@AllArgsConstructor
public class AuthService {
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
private final AuthenticationManager authenticationManager;
private final JwtTokenProvider jwtTokenProvider;
public JwtResponseDto login(JwtRequestDto request) throws Exception {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword()));
return createJwtToken(authentication);
}
private JwtResponseDto createJwtToken(Authentication authentication) {
UserDetailsImpl principal = (UserDetailsImpl) authentication.getPrincipal();
String token = jwtTokenProvider.generateToken(principal);
return new JwtResponseDto(token);
}
...
}
package com.example.securityspring.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class JwtResponseDto {
private String accessToken;
}
5. Spring Security 설정에서 Session 사용 제거 및 jwt filter 추가
package com.example.securityspring.config;
import com.example.securityspring.security.JwtAuthenticationFilter;
import com.example.securityspring.security.JwtTokenProvider;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@AllArgsConstructor
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtTokenProvider jwtTokenProvider;
@Bean
public BCryptPasswordEncoder encodePassword() { // 회원가입 시 비밀번호 암호화에 사용할 Encoder 빈 등록
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
// 정적인 파일에 대한 요청들
private static final String[] AUTH_WHITELIST = {
// -- swagger ui
"/v2/api-docs",
"/v3/api-docs/**",
"/configuration/ui",
"/swagger-resources/**",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**",
"/file/**",
"/image/**",
"/swagger/**",
"/swagger-ui/**",
// other public endpoints of your API may be appended to this array
"/h2/**"
};
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
// login 없이 접근 허용 하는 url
.antMatchers("/auth/**").permitAll()
// '/admin'의 경우 ADMIN 권한이 있는 사용자만 접근이 가능
.antMatchers("/admin").hasRole("ADMIN")
// 그 외 모든 요청은 인증과정 필요
.anyRequest().authenticated()
.and()
// 토큰 기반 인증이기 때문에 session 사용 x
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// JwtAuthenticationFilter 는 UsernamePasswordAuthenticationFilter 전에 넣음
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
}
@Override
public void configure(WebSecurity web) throws Exception {
// 정적인 파일 요청에 대해 무시
web.ignoring().antMatchers(AUTH_WHITELIST);
}
}
최종 코드는 다음과 같습니다.
https://github.com/hanbee1005/security-spring
로그인을 통해 생성된 token을 https://jwt.io/ 에 입력하면 내용을 확인할 수 있습니다.
또한 Postman 등을 통해 로그인된 사용자를 가지고 /dashboard 등의 화면에 접근하고자 할 때는 HTTP Header에 key는 'Authentication', value는 'Bearer 토큰값'으로 추가하여 요청하면 됩니다.
'Spring' 카테고리의 다른 글
[Redis] SpringBoot + Redis 연동하기 (0) | 2022.06.08 |
---|---|
[Social Login] 구글, 네이버, 카카오 로그인 구현 (0) | 2022.06.06 |
[JPA] Could not extract ResultSet 에러 (1) | 2021.12.02 |
Spring Security 로그인 구현하기 (0) | 2021.07.12 |
[토비의 스프링] 01. 오브젝트와 의존관계 (2) | 2021.04.14 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- BFS
- 소수
- 수학
- 순열
- 프로그래머스
- map
- DFS
- SWIFT
- Algorithm
- AWS
- 조합
- CodePipeline
- permutation
- java
- array
- ionic
- Baekjoon
- string
- search
- programmers
- CodeDeploy
- 에라토스테네스의 체
- EC2
- CodeCommit
- ECR
- Combination
- spring
- Dynamic Programming
- sort
- cloudfront
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
글 보관함