Skip to content

Commit

Permalink
[Refactor] 스프링 시큐리티 코드수정 - #63
Browse files Browse the repository at this point in the history
  • Loading branch information
82everywin committed May 27, 2024
1 parent a7f87f0 commit 7c107dd
Show file tree
Hide file tree
Showing 26 changed files with 101 additions and 96 deletions.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ spring.datasource.username=root
spring.datasource.password=root

# DDL(create,alter,drop)
spring.jpa.hibernate.ddl-auto=create
spring.jpa.hibernate.ddl-auto=update

#sql 보여주기
spring.jpa.show-sql=true
Expand All @@ -26,4 +26,4 @@ spring.jpa.properties.hibernate.format_sql=true

# jwt
jwt.secret=VlwEyVBsYt9V7zq57TejMnVUyzblYcfPQye08f7MGVA9XkHa
jwt.token-validity-in-seconds=86400000
jwt.token-validity-in-seconds=864000
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,70 +1,38 @@
package com.todolist.todolist.config;

import com.todolist.todolist.domain.Member;
import com.todolist.todolist.security.JwtTokenProvider;
import com.todolist.todolist.service.MemberService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.List;

@Component
@RequiredArgsConstructor
public class JwtTokenFilter extends OncePerRequestFilter {

private final MemberService memberService;

public static final String AUTHORIZATION_HEADER = "Authorization";
private final JwtTokenProvider jwtTokenProvider;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);

String authorizationHeader = request.getHeader(AUTHORIZATION_HEADER);
String token = null;
// 토큰 전송 x -> 로그인 x
if(authorizationHeader == null){
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
token = authorizationHeader.substring(7);
} else {
filterChain.doFilter(request, response);
return;
}

// "Bearer"로 시작 x -> 잘못된 토큰
if(!authorizationHeader.startsWith("Bearer")){
if (jwtTokenProvider.validateToken(token)) {
Authentication authentication = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
return;
}

String token = authorizationHeader.split(" ")[1];

if(jwtTokenProvider.isExpired(token)){
filterChain.doFilter(request, response);
return;
}

String loginId = jwtTokenProvider.getLoginIdFromToken(token);

if ( loginId != null && SecurityContextHolder.getContext().getAuthentication() == null) {

if (jwtTokenProvider.validateToken(token,loginId)) {
Member loginMember = memberService.throwFindbyLoginId(loginId);

UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
loginMember.getLoginId(), null, List.of(new SimpleGrantedAuthority(loginMember.getRole().name())));

authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(request, response);
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.todolist.todolist.config;

import com.todolist.todolist.security.JwtTokenProvider;
import com.todolist.todolist.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -13,10 +13,11 @@

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

@Autowired
private JwtTokenFilter jwtTokenFilter;

private final JwtTokenProvider jwtTokenProvider;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
Expand All @@ -33,7 +34,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.requestMatchers("/","/swagger-ui/**","/v3/api-docs/**").permitAll()
.requestMatchers("/api/members","api/members/login").permitAll()
.anyRequest().authenticated())
.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
.addFilterBefore(new JwtTokenFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);

return http.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,28 @@
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI openAPI() {
SecurityScheme apiKey = new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.in(SecurityScheme.In.HEADER)
.name("Authorization")
.scheme("bearer")
.bearerFormat("JWT");

SecurityRequirement securityRequirement = new SecurityRequirement()
.addList("Bearer Token");

return new OpenAPI()
.components(new Components())
.components(new Components().addSecuritySchemes("Bearer Token", apiKey))
.addSecurityItem(securityRequirement)
.info(apiInfo());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;

Expand All @@ -25,12 +31,11 @@
public class MemberController {
private final MemberService memberService;

@Value("${jwt.secret")
private String key;

private final JwtTokenProvider jwtTokenProvider;

private CustomUserDetailService userDetailService;
private final CustomUserDetailService userDetailService;

private final AuthenticationManagerBuilder authenticationManager;

@Operation(summary = "회원가입")
@PostMapping
Expand All @@ -42,13 +47,20 @@ public ResponseEntity<MemberResponseDto> createMember(@Valid @RequestBody Member

@Operation(summary = "로그인")
@PostMapping("/login")
public ResponseEntity<String> loginMember(@RequestBody @Valid MemberRequestDto.LoginRequestDto request){
MemberLoginResponseDto responseDto = memberService.login(request);
public ResponseEntity<String> loginMember(@RequestBody @Valid MemberRequestDto.LoginRequestDto request) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(request.getLoginId(), request.getPassword());
Authentication authentication = authenticationManager.getObject().authenticate(authenticationToken);

SecurityContextHolder.getContext().setAuthentication(authentication);

String token = jwtTokenProvider.createToken(authentication);

String jwtToken = jwtTokenProvider.createToken(request.getLoginId());
// 헤더에 담아 리턴
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(JwtTokenFilter.AUTHORIZATION_HEADER, "Bearer " + token);

return ResponseEntity.status(HttpStatus.OK).body(jwtToken);
// return ResponseEntity.status(HttpStatus.CREATED).body(responseDto);
return ResponseEntity.status(HttpStatus.OK).headers(httpHeaders).body(token);
}
@Operation(summary = "회원정보 수정")
@PutMapping("/{memberId}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.todolist.todolist.repository.MemberRepository;
import com.todolist.todolist.validators.BaseException;
import com.todolist.todolist.validators.ErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
Expand All @@ -18,10 +19,11 @@
import java.util.Optional;

@Service
@RequiredArgsConstructor
public class CustomUserDetailService implements UserDetailsService {

@Autowired
private MemberRepository memberRepository;

private final MemberRepository memberRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.todolist.todolist.security;

import com.todolist.todolist.config.JwtTokenFilter;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;

import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
Expand All @@ -21,70 +22,71 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Component
public class JwtTokenProvider {

// 비밀키
@Value("${jwt.secret}")
private String secretKeyBase64;

@Value("${jwt.token-validity-in-seconds}")
private static long tokenValidityInSeconds;
public class JwtTokenProvider implements InitializingBean {

private static final String AUTHORITIES_KEY = "auth";
private final String secretKeyBase64;
private final long tokenValidityInSeconds;
private Key key;

@PostConstruct
public void init() {
public JwtTokenProvider(@Value("${jwt.secret}")String secretKeyBase64,
@Value("${jwt.token-validity-in-seconds}")long tokenValidityInSeconds) {
this.secretKeyBase64 = secretKeyBase64;
this.tokenValidityInSeconds = tokenValidityInSeconds;
}

@Override
public void afterPropertiesSet() throws Exception {
byte[] keyBytes = java.util.Base64.getDecoder().decode(secretKeyBase64);
this.key = Keys.hmacShaKeyFor(keyBytes);
}

// 유저 정보를 가지고 AccessToken, RefreshToken 을 생성하는 메서드
public String createToken(String loginId) {
// Claim = Jwt Token에 들어갈 정보
// Claim에 loginId를 넣어 줌으로써 나중에 loginId를 꺼낼 수 있음
Claims claims = Jwts.claims();
claims.put("loginId", loginId);

// token값 생성 -> 헤더에 저장하기 위함
public String createToken(Authentication authentication) {
String authorities = authentication.getAuthorities().stream()
.map(auth -> auth.getAuthority())
.collect(Collectors.joining(","));

return Jwts.builder()
.setClaims(claims)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + tokenValidityInSeconds))
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.setExpiration(new Date(System.currentTimeMillis() + this.tokenValidityInSeconds))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}

// JWT 토큰을 복호화하여 토큰에 들어있는 정보를 꺼내는 메서드
public Authentication getAuthentication(String token) {
// 토큰 복호화
Claims claims = parseClaims(token);
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();

if (claims.get("auth") == null) {
throw new RuntimeException("권한 정보가 없는 토큰입니다.");
}

// 클레임에서 권한 정보 가져오기
Collection<? extends GrantedAuthority> authorities =
List<? extends SimpleGrantedAuthority> authorities =
Arrays.stream(claims.get("auth").toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());

// UserDetails 객체를 만들어서 Authentication 리턴
UserDetails principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, "", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}

// Claims에서 memberId 추출
public String getLoginIdFromToken(String token) {
return extractClaims(token).get("loginId").toString();
}


// SecretKey를 사용해 Token Parsing
private Claims extractClaims(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
}

// 발급된 Token이 만료 시간이 지났는지 체크
Expand All @@ -94,9 +96,14 @@ public boolean isExpired(String token) {


// 토큰 정보를 검증하는 메서드
public boolean validateToken(String token, String loginId) {
String id = getLoginIdFromToken(token);
return (id.equals(loginId) && !isExpired(token));
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
}catch (io.jsonwebtoken.security.SecurityException e){
return false;
}

}

// 토큰 복호화
Expand All @@ -111,4 +118,6 @@ private Claims parseClaims(String accessToken) {
return e.getClaims();
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ spring.datasource.username=root
spring.datasource.password=root

# DDL(create,alter,drop)
spring.jpa.hibernate.ddl-auto=create
spring.jpa.hibernate.ddl-auto=update

#sql 보여주기
spring.jpa.show-sql=true
Expand All @@ -26,4 +26,4 @@ spring.jpa.properties.hibernate.format_sql=true

# jwt
jwt.secret=VlwEyVBsYt9V7zq57TejMnVUyzblYcfPQye08f7MGVA9XkHa
jwt.token-validity-in-seconds=86400000
jwt.token-validity-in-seconds=864000

0 comments on commit 7c107dd

Please sign in to comment.