Json Web Token
2024. 1. 2. 20:19ㆍSpring Boot
우선 JWT는
토큰을 관리하는 provider class를 만들어 관리한다
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import com.example.demo.principal.PrincipalDetailsService;
import jakarta.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.Date;
import java.util.stream.Collectors;
import java.util.Map;
import java.util.HashMap;
@RequiredArgsConstructor
@Component
@PropertySource("classpath:jwt.properties")
public class jwtProvider {
@Value("${secret}")
private String key;
private static final String AUTHORITIES_KEY = "auth";
@Value("${seconds}")
private String seconds;
private Key secretKey;
// 만료시간 : 1Hour
private long exp;
@Autowired
private PrincipalDetailsService principalDetailsService;
@PostConstruct
protected void init() {
byte[] keyBytes = Decoders.BASE64.decode(key);
this.secretKey = Keys.hmacShaKeyFor(keyBytes);
this.exp = Long.parseLong(seconds);
}
// 토큰 생성
public String createToken(String name) {
Map<String, Object> payloads = new HashMap<String, Object>();
Map<String, Object> headers = new HashMap<>();
headers.put("alg", "HS256");
headers.put("typ", "JWT");
payloads.put("name", name);
// 토큰의 expire 시간을 설정
long now = (new Date()).getTime();
Date validity = new Date(now + this.exp);
return Jwts.builder()
.setHeader(headers)
.setClaims(payloads)
.signWith(secretKey, SignatureAlgorithm.HS256) // 사용할 암호화 알고리즘과 , signature 에 들어갈 secret값 세팅
.setExpiration(validity) // set Expire Time 해당 옵션 안넣으면 expire안함
.compact();
}
// 권한정보 획득
// Spring Security 인증과정에서 권한확인을 위한 기능
public Authentication getAuthentication(String token) {
System.out.printf("get authentication: %s\n",this.getAccount(token));
UserDetails userDetails = principalDetailsService.loadUserByUsername(this.getAccount(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
// 토큰에 담겨있는 유저 account 획득
public String getAccount(String token) {
// 만료된 토큰에 대해 parseClaimsJws를 수행하면 io.jsonwebtoken.ExpiredJwtException이 발생한다.
try {
System.out.println(Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token).getBody().get("name"));
} catch (ExpiredJwtException e) {
e.printStackTrace();
return e.getClaims().getSubject();
} catch (Exception e) {
e.printStackTrace();
}
return (String)Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token).getBody().get("name");
}
// 토큰 검증
public boolean validateToken(String token) {
try {
// Bearer 검증
if (!token.substring(0, "BEARER ".length()).equalsIgnoreCase("BEARER ")) {
return false;
} else {
token = token.split(" ")[1].trim();
}
Jws<Claims> claims = Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token);
// 만료되었을 시 false
return !claims.getBody().getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}
}
Authentication은
특정 주소에 올바른 data를 form으로 넘겨주면 cookie에 JWT를 넘겨주도록 짰다
@RequestMapping(path = "/login/jwt", method = RequestMethod.POST)
public String login_jwt(HttpServletRequest req, HttpServletResponse res) {
String token = jwtProvider.createToken(req.getParameter("name"));
Cookie cookie = new Cookie("jwt", token);
cookie.setPath("/");
cookie.setHttpOnly(true);
cookie.setMaxAge(60*60*24*7);
res.addCookie(cookie);
return "redirect:/";
}
Authorization은
filter를 활용했다
import java.io.IOException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import com.example.demo.jwt.jwtProvider;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
public class jwtAuthorizationFilter extends BasicAuthenticationFilter{
private jwtProvider jwtProvider;
public jwtAuthorizationFilter(AuthenticationManager authenticationManager, jwtProvider jwtProvider) {
super(authenticationManager);
this.jwtProvider = jwtProvider;
}
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
System.out.println("인증 필요 요청 됨");
req.setAttribute("name", null);
Cookie[] cookies = req.getCookies();
String token = null;
for(Cookie cookie: cookies) {
if(cookie.getName().equals("jwt"))
token = cookie.getValue();
}
System.out.printf("authorization jwt: %s\n",token);
if(token == null || jwtProvider.validateToken(token)) {
chain.doFilter(req, res);
return;
}
req.setAttribute("name", jwtProvider.getAccount(token));
chain.doFilter(req, res);
}
}
보통 로그인 된 정보를 임시로 security session에 저장하고 session을 stateless로 관리하지만
session 로그인도 동시에 구현하기 위해서
일부로 servlet request에 따로 attribute를 만들어 저장하였다
로그인 과정에서 security filter인 BasicAuthenticationFilter를 상속받아 이용했음으로 아래와 같이 설정하여 주었다
package com.example.demo.config;
import org.springframework.beans.factory.annotation.Autowired;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import com.example.demo.jwt.jwtProvider;
import com.example.demo.user.user_repository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
@Autowired
private CorsConfig corsConfig;
private final jwtProvider tokenProvider;
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.httpBasic((http_basic)-> http_basic.disable());
http.apply(new jwtDsl());
http.sessionManagement((session) ->
session
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS));
return http.build();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
public class jwtDsl extends AbstractHttpConfigurer<jwtDsl, HttpSecurity> {
@Override
public void configure(HttpSecurity http) throws Exception {
AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
http
.addFilter(new jwtAuthorizationFilter(authenticationManager, tokenProvider));
}
}
}
'Spring Boot' 카테고리의 다른 글
| Https (0) | 2024.01.12 |
|---|---|
| MySQL (0) | 2024.01.12 |
| CustomFilter (0) | 2024.01.02 |
| SecurityFilterChain (0) | 2024.01.02 |
| MongoDB (0) | 2023.12.28 |