Server/Spring
[Spring Security] JWT(JSON Web Token) 의 코드 구현
aonee
2020. 5. 17. 23:58
🐥 깃허브 : 전체 코드 Spring Boot에 Spring Security와 JWT를 사용해 로그인 구현
1. 동작과정
Generating JWT

- Client : 로그인 요청 POST (id, pw)
- Server : id, pw가 맞는지 확인 후 맞다면 JWT를 SecretKey로 생성
- Client : Server에게 받은 JWT를 로컬 or 세션에 저장
- Client : 서버에 요청할 때 항상 헤더에 Token을 포함시킴
- Server : 요청을 받을 때마다 SecretKey를 이용해 Token이 유효한지 검증
- 서버만이 SecretKey를 가지고 있기 때문에 검증 가능
- Token이 검증되면 따로 username, pw를 검사하지 않아도 사용자 인증 가능
- Server : response

2. Token 유효 검증
- 클라이언트의 요청 (Header : Token)
- Spring의 Interceptor에 의해 요청이 Intercept됨
- 클라이언트에게 제공되었던 Token과 클라이언트의 Header에 담긴 Token 일치 확인
auth0 JWT를 이용해issuer, expire검증
Validating JWT

3. Spring Boot Security + JWT 코드
3-1. dependency 추가
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
spring-boot-starter-security와jjwt추가
3-2. Secret Key 설정
- Hashing algorithm과 함께 사용할 Secret Key를 설정
- Secret Key는 Header, Payload와 결합되어 Hash 생성
application.properties
jwt.secret=aoneeJjangjwt
3. JwtUtil
- JWT를 생성하고 검증하는 역할 수행
io.jsonwebtoken.Jwts라이브러리 사용
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String SECRET_KEY;
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
createToken
- Token 생성
- claim : Token에 담을 정보
- issuer : Token 발급자
- subject : Token 제목
- issuedate : Token 발급 시간
- expiration : Token 만료 시간
- milliseconds 기준!
signWith(알고리즘, 비밀키)
4. MyUserDetailsService
- DB에서 UserDetail를 얻어와 AuthenticationManager에게 제공하는 역할
- 이번에는 DB 없이 하드코딩된 User List에서 get userDetail
@Service
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if ("user_id".equals(username)) {
return new User("aonee","$2a$10$VKu6eW.2pHLJn3yeW0eMxuEUBxXCq/b2Vo3HwSqROGI2mmYRnXqpm",new ArrayList<>());
} else {
throw new UsernameNotFoundException("User not found with username: " + username);
}
}
}
- Spring Security 5.0에서는 Password를 BryptEncoder를 통해 Brypt화한다.
- https://www.javainuse.com/onlineBcrypt 에서 user_pw를 Bcrypt화
- $2a$10$VKu6eW.2pHLJn3yeW0eMxuEUBxXCq/b2Vo3HwSqROGI2mmYRnXqpm
- https://www.javainuse.com/onlineBcrypt 에서 user_pw를 Bcrypt화
- id : user_id, pw: user_pw로 고정해 사용자 확인
- 사용자 확인 실패시 throw Exception
5. LoginController
- 사용자가 입력한 id, pw를 body에 넣어서 POST API mapping /authenticate
- 사용자의 id, pw를 검증
- jwtUtil을 호출해 Token을 생성하고 JwtResponse에 Token을 담아 return ResponseEntity
@RestController
@CrossOrigin
public class LoginController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtil jwtTokenUtil;
@Autowired
private MyUserDetailsService userDetailsService;
@GetMapping("/main")
public String main(){
return "Welcome!! This is main page";
}
@GetMapping("/hello")
public String hello(){
return "hello";
}
@PostMapping("/authenticate")
public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword())
);
} catch (BadCredentialsException e) {
throw new Exception("Incorrect username or password", e);
}
final UserDetails userDetails = userDetailsService
.loadUserByUsername(authenticationRequest.getUsername());
final String jwt = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new AuthenticationResponse(jwt));
}
}
6. AuthenticationRequest
- 사용자에게서 받은 id, pw를 저장
@Getter
@Setter
public class AuthenticationRequest {
private String username;
private String password;
public AuthenticationRequest() {
}
public AuthenticationRequest(String username, String password) {
this.username = username;
this.password = password;
}
}
7. AuthenticationResponse
- 사용자에게 반환될 JWT를 담은 Response
@Getter
public class AuthenticationResponse {
private final String jwt;
public AuthenticationResponse(String jwt) {
this.jwt = jwt;
}
}
8. JwtRequestFilter
- Client의 Request를 Intercept해서 Header의 Token가 유효한지 검증
- if 유효한 Token
- Spring Security의 Authentication을 Setting, to specify that the current user is authenticated
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private MyUserDetailsService userDetailsService;
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username= null;
String jwt = null;
if(authorizationHeader != null && authorizationHeader.startsWith("Bearer ")){
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if(jwtUtil.validateToken(jwt, userDetails)){
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request,response);
}
}
반응형