-
Notifications
You must be signed in to change notification settings - Fork 0
#1 인증 토큰 spring security, JWT #5
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,32 +18,37 @@ | |
@Configuration | ||
@EnableWebSecurity | ||
@EnableGlobalMethodSecurity(prePostEnabled = true) | ||
/* | ||
WebSecurityConfigurerAdapter는 사용자 지정 보안 구성을 제공하도록 확장 됩니다. | ||
이 클래스에서 아래 Bean이 구성되고 인스턴스화됩니다. | ||
- JwtTokenFilter | ||
- PasswordEncoder | ||
*/ | ||
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { | ||
|
||
@Autowired | ||
private JwtTokenProvider jwtTokenProvider; | ||
|
||
@Override | ||
// 메서드 내에서 보호, 비보호 API 엔드 포인트를 정의하는 패턴을 구성합니다. | ||
// 쿠키를 사용하지 않기 때문에 CSRF 보호를 비활성화했습니다. | ||
protected void configure(HttpSecurity http) throws Exception { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 메소드에 있는 전체 설정 한줄한줄에 대한 의도를 설명이 가능하실까요? |
||
|
||
// Disable CSRF (cross site request forgery) | ||
http.csrf().disable(); | ||
|
||
// No session will be created or used by spring security | ||
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); | ||
|
||
// Entry points | ||
http.authorizeRequests()// | ||
.antMatchers("/users/signin").permitAll()// | ||
.antMatchers("/users/signup").permitAll()// | ||
.antMatchers("/h2-console/**/**").permitAll() | ||
// Disallow everything else.. | ||
// 다른것들은 모두 비활성화 | ||
.anyRequest().authenticated(); | ||
|
||
// If a user try to access a resource without having enough permissions | ||
// 요구사항을 만족하지 않는다면 예외(exceptionHandling)를 발생 시킨다. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이건 이 코드의 동작에 대해 설명하는게 아니라 단순 번역같네요. 내용도 다릅니다~ 서비스개발자는 꼼꼼함이 중요합니다. 그 꼼꼼함이 제일 치명적인 약점으로 보입니다. |
||
http.exceptionHandling().accessDeniedPage("/login"); | ||
|
||
// Apply JWT | ||
// Security에 JWT 적용 | ||
http.apply(new JwtTokenFilterConfigurer(jwtTokenProvider)); | ||
|
||
// Optional, if you want to test the API from a browser | ||
|
@@ -52,7 +57,7 @@ protected void configure(HttpSecurity http) throws Exception { | |
|
||
@Override | ||
public void configure(WebSecurity web) throws Exception { | ||
// Allow swagger to be accessed without authentication | ||
// 인증없이 swagger에는 사용하게 설정 | ||
web.ignoring().antMatchers("/v2/api-docs")// | ||
.antMatchers("/swagger-resources/**")// | ||
.antMatchers("/swagger-ui.html")// | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,31 +11,41 @@ | |
import javax.servlet.http.HttpServletResponse; | ||
import java.io.IOException; | ||
|
||
// We should use OncePerRequestFilter since we are doing a database call, there is no point in doing this more than once | ||
/* | ||
OncePerRequestFilter를 상속하여 구현한 경우 doFilter 대신 doFilterInternal 메서드를 구현 | ||
이렇게 필터를 정의하였으면 bean 선언만 하면 spring boot를 사용하는 경우 자동으로 filter가 추가되게 된다. | ||
|
||
JwtTokenFilter 역할: JwtTokenFilter필터는 각각의 API에 인가된다 ex) /users/signin ,/users/signup. | ||
1. Authorization 헤더에서 액세스 토큰을 확인한다. | ||
2. 헤더에 액세스 토큰이있는 경우 인증을 위임한다. | ||
3. JwtTokenProvider은 인증 프로세스의 결과에 따라 미충족시 인증 예외를 발생 시킵니다. | ||
*/ | ||
public class JwtTokenFilter extends OncePerRequestFilter { | ||
|
||
private JwtTokenProvider jwtTokenProvider; | ||
|
||
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) { | ||
this.jwtTokenProvider = jwtTokenProvider; | ||
} | ||
|
||
@Override | ||
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { | ||
String token = jwtTokenProvider.resolveToken(httpServletRequest); | ||
try { | ||
if (token != null && jwtTokenProvider.validateToken(token)) { | ||
Authentication auth = jwtTokenProvider.getAuthentication(token); | ||
SecurityContextHolder.getContext().setAuthentication(auth); | ||
} | ||
} catch (CustomException ex) { | ||
//this is very important, since it guarantees the user is not authenticated at all | ||
SecurityContextHolder.clearContext(); | ||
httpServletResponse.sendError(ex.getHttpStatus().value(), ex.getMessage()); | ||
return; | ||
private JwtTokenProvider jwtTokenProvider; | ||
|
||
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) { | ||
this.jwtTokenProvider = jwtTokenProvider; | ||
} | ||
|
||
filterChain.doFilter(httpServletRequest, httpServletResponse); | ||
} | ||
@Override | ||
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { | ||
// 헤더의 req.getHeader("Authorization") 값을 가져와 유효 하다면 값을 가져온다. | ||
String token = jwtTokenProvider.resolveToken(httpServletRequest); | ||
try { | ||
if (token != null && jwtTokenProvider.validateToken(token)) { | ||
Authentication auth = jwtTokenProvider.getAuthentication(token); | ||
SecurityContextHolder.getContext().setAuthentication(auth); | ||
} | ||
} catch (CustomException ex) { | ||
// 인증 실패시 호출 | ||
SecurityContextHolder.clearContext(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 실패시에 이 코드를 왜 호출해야할까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Security에서 인증이 완료되고 인증 매니저가 해당 유저의 세션 생성 후 인가를 하는과정에서 |
||
httpServletResponse.sendError(ex.getHttpStatus().value(), ex.getMessage()); | ||
return; | ||
} | ||
|
||
// 인증 성공시 호출 | ||
filterChain.doFilter(httpServletRequest, httpServletResponse); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,66 +24,66 @@ | |
import java.util.stream.Collectors; | ||
|
||
@Component | ||
/* | ||
1. 액세스 토큰 서명 확인 | ||
2. 액세스 토큰 에서 ID 및 권한 부여 클레임(사용자가 누구인지 URI, 무엇에 접근 할 수 있는지, 토큰 만료 시간 확인) 을 추출하고 이를 사용하여 UserContext를 만듭니다. | ||
3. 액세스 토큰의 형식이 잘못 되었거나 만료 되었거나 토큰이 적절한 서명 키로 서명되지 않은 경우 인증 예외가 발생시킵니다. | ||
*/ | ||
public class JwtTokenProvider { | ||
@Value("${security.jwt.token.secret-key:secret-key}") | ||
private String secretKey; | ||
|
||
/** | ||
* THIS IS NOT A SECURE PRACTICE! For simplicity, we are storing a static key here. Ideally, in a | ||
* microservices environment, this key would be kept on a config-server. | ||
*/ | ||
@Value("${security.jwt.token.secret-key:secret-key}") | ||
private String secretKey; | ||
@Value("${security.jwt.token.expire-length:3600000}") | ||
private long validityInMilliseconds = 3600000; // 1h | ||
|
||
@Value("${security.jwt.token.expire-length:3600000}") | ||
private long validityInMilliseconds = 3600000; // 1h | ||
@Autowired | ||
private MyUserDetails myUserDetails; | ||
|
||
@Autowired | ||
private MyUserDetails myUserDetails; | ||
|
||
@PostConstruct | ||
protected void init() { | ||
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); | ||
} | ||
@PostConstruct | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 생성자 주입을 사용하면 이걸 실행 안시켜줘도 됩니다. 이 코드도 인터넷 어딘가에서 본 기억이 있네요. 자신의 것으로 만드셔야합니다. 가져온 코드를 고민 없이 그대로 사용하신 것 같습니다. 여태까지 공부했던 내용들이 녹아있지가 않네요 |
||
protected void init() { | ||
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); | ||
} | ||
|
||
public String createToken(String username, List<Role> roles) { | ||
public String createToken(String username, List<Role> roles) { | ||
|
||
Claims claims = Jwts.claims().setSubject(username); | ||
claims.put("auth", roles.stream().map(s -> new SimpleGrantedAuthority(s.getAuthority())).filter(Objects::nonNull).collect(Collectors.toList())); | ||
Claims claims = Jwts.claims().setSubject(username); | ||
claims.put("auth", roles.stream().map(s -> new SimpleGrantedAuthority(s.getAuthority())).filter(Objects::nonNull).collect(Collectors.toList())); | ||
|
||
Date now = new Date(); | ||
Date validity = new Date(now.getTime() + validityInMilliseconds); | ||
Date now = new Date(); | ||
Date validity = new Date(now.getTime() + validityInMilliseconds); | ||
|
||
return Jwts.builder()// | ||
.setClaims(claims)// | ||
.setIssuedAt(now)// | ||
.setExpiration(validity)// | ||
.signWith(SignatureAlgorithm.HS256, secretKey)// | ||
.compact(); | ||
} | ||
return Jwts.builder()// | ||
.setClaims(claims)// | ||
.setIssuedAt(now)// | ||
.setExpiration(validity)// | ||
.signWith(SignatureAlgorithm.HS256, secretKey)// | ||
.compact(); | ||
} | ||
|
||
public Authentication getAuthentication(String token) { | ||
UserDetails userDetails = myUserDetails.loadUserByUsername(getUsername(token)); | ||
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); | ||
} | ||
public Authentication getAuthentication(String token) { | ||
UserDetails userDetails = myUserDetails.loadUserByUsername(getUsername(token)); | ||
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); | ||
} | ||
|
||
public String getUsername(String token) { | ||
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject(); | ||
} | ||
public String getUsername(String token) { | ||
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject(); | ||
} | ||
|
||
public String resolveToken(HttpServletRequest req) { | ||
String bearerToken = req.getHeader("Authorization"); | ||
if (bearerToken != null && bearerToken.startsWith("Bearer ")) { | ||
return bearerToken.substring(7); | ||
public String resolveToken(HttpServletRequest req) { | ||
String bearerToken = req.getHeader("Authorization"); | ||
if (bearerToken != null && bearerToken.startsWith("Bearer ")) { | ||
return bearerToken.substring(7); | ||
} | ||
return null; | ||
} | ||
return null; | ||
} | ||
|
||
public boolean validateToken(String token) { | ||
try { | ||
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); | ||
return true; | ||
} catch (JwtException | IllegalArgumentException e) { | ||
throw new CustomException("Expired or invalid JWT token", HttpStatus.INTERNAL_SERVER_ERROR); | ||
public boolean validateToken(String token) { | ||
try { | ||
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); | ||
return true; | ||
} catch (JwtException | IllegalArgumentException e) { | ||
throw new CustomException("Expired or invalid JWT token", HttpStatus.INTERNAL_SERVER_ERROR); | ||
} | ||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이것도 이 라이브러리의 용도와 다른 주석 같네요