Skip to content

Commit 310c57b

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents 6124327 + a7070db commit 310c57b

13 files changed

+249
-10
lines changed

src/main/java/com/example/security/jwt/account/application/AccountService.java

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ public interface AccountService {
99

1010
ResponseAccount.Information registerMember(RequestAccount.RegisterMember registerMemberDto);
1111

12+
ResponseAccount.Information registerAdmin(RequestAccount.RegisterAdmin registerAdminDto);
13+
1214
ResponseAccount.Information getAccountWithAuthorities(String username);
1315

16+
ResponseAccount.Token refreshToken(String toekn);
1417
}

src/main/java/com/example/security/jwt/account/application/AccountServiceImpl.java

+54-3
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
import com.example.security.jwt.account.application.dto.RequestAccount;
44
import com.example.security.jwt.account.application.dto.ResponseAccount;
5+
import com.example.security.jwt.account.domain.AccountErrorCode;
56
import com.example.security.jwt.account.domain.AccountRepository;
67
import com.example.security.jwt.account.domain.entity.Account;
78
import com.example.security.jwt.account.domain.entity.AccountAdapter;
89
import com.example.security.jwt.account.domain.entity.Authority;
910
import com.example.security.jwt.global.exception.ApplicationException;
1011
import com.example.security.jwt.global.exception.CommonErrorCode;
12+
import com.example.security.jwt.global.security.RefreshTokenProvider;
1113
import com.example.security.jwt.global.security.TokenProvider;
1214
import lombok.RequiredArgsConstructor;
1315
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -31,7 +33,7 @@ public class AccountServiceImpl implements AccountService{
3133
private final AuthenticationManagerBuilder authenticationManagerBuilder;
3234
private final AccountRepository accountRepository;
3335
private final PasswordEncoder passwordEncoder;
34-
// private final RefreshTokenProvider refreshTokenProvider;
36+
private final RefreshTokenProvider refreshTokenProvider;
3537

3638

3739
// username과 password로 사용자를 인증하여 액세스토큰과 리프레시 토큰을 반환한다.
@@ -47,16 +49,18 @@ public ResponseAccount.Token authenticate(String username, String password) {
4749

4850
// 인증 정보를 기준으로 jwt access 토큰 생성
4951
String accessToken = tokenProvider.createToken(authentication);
50-
Date expiredTime = tokenProvider.getExpiredTime(accessToken);
52+
Date expiredTime = tokenProvider.getExpiredTime(accessToken); // 토큰 정보에서 만료된 정보를 가져옴
5153

5254
// 위에서 loadUserByUsername를 호출하였으므로 AccountAdapter가 시큐리티 컨텍스트에 저장되어 Account 엔티티 정보를 우리는 알 수 있음
5355
// 유저 정보에서 중치를 꺼내 리프레시 토큰 가중치에 할당, 나중에 액세스토큰 재발급 시도 시 유저정보 가중치 > 리프레시 토큰이라면 실패
5456
Long tokenWeight = ((AccountAdapter)authentication.getPrincipal()).getAccount().getTokenWeight();
57+
String refreshToke = refreshTokenProvider.createToken(authentication, tokenWeight);
58+
5559

5660
return ResponseAccount.Token.builder()
5761
.accessToken(accessToken)
5862
.expiredTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(expiredTime))
59-
//.refreshToke()
63+
.refreshToken(refreshToke)
6064
.build();
6165
}
6266

@@ -87,6 +91,30 @@ public ResponseAccount.Information registerMember(RequestAccount.RegisterMember
8791
return ResponseAccount.Information.of(accountRepository.save(user));
8892
}
8993

94+
@Override
95+
public ResponseAccount.Information registerAdmin(RequestAccount.RegisterAdmin registerAdminDto) {
96+
Optional<Account> accountOptional = accountRepository.findOneWithAuthoritiesByUsername(registerAdminDto.username());
97+
98+
if(accountOptional.isPresent()) {
99+
throw new ApplicationException(CommonErrorCode.CONFLICT, "이미 가입되어있는 유저");
100+
}
101+
102+
//이건 부팅 시 data.sql에서 INSERT로 디비에 반영
103+
Authority authority = Authority.builder()
104+
.authorityName("ROLE_ADMIN")
105+
.build();
106+
107+
Account user = Account.builder()
108+
.username(registerAdminDto.username())
109+
.password(passwordEncoder.encode(registerAdminDto.password()))
110+
.nickname(registerAdminDto.nickname())
111+
.authorities(Collections.singleton(authority))
112+
.activated(true)
113+
.build();
114+
115+
return ResponseAccount.Information.of(accountRepository.save(user));
116+
}
117+
90118
@Transactional(readOnly = true)
91119
@Override
92120
public ResponseAccount.Information getAccountWithAuthorities(String username) {
@@ -96,6 +124,29 @@ public ResponseAccount.Information getAccountWithAuthorities(String username) {
96124
return ResponseAccount.Information.of(account);
97125
}
98126

127+
@Override
128+
public ResponseAccount.Token refreshToken(String refreshToken) {
129+
// 먼저 리프레시 토큰을 검증한다.
130+
if(!refreshTokenProvider.validateToken(refreshToken)) throw new ApplicationException(AccountErrorCode.INVALID_REFRESH_TOKEN);
131+
132+
// 리프레시 토큰 값을 이용해 사용자를 꺼낸다.
133+
// refreshTokenProvider과 TokenProvider는 다른 서명키를 가지고 있기에 refreshTokenProvider를 써야한다.
134+
Authentication authentication = refreshTokenProvider.getAuthentication(refreshToken);
135+
Account account = accountRepository.findOneWithAuthoritiesByUsername(authentication.getName())
136+
.orElseThrow(()-> new UsernameNotFoundException(authentication.getName() + "을 찾을 수 없습니다."));
137+
138+
//사용자 디비 값에 있는 것과 가중치 비교, 디비 가중치가 더 크다면 유효하지 않음
139+
if(account.getTokenWeight() > refreshTokenProvider.getTokenWeight(refreshToken)) throw new ApplicationException(AccountErrorCode.INVALID_REFRESH_TOKEN);
140+
141+
// 리프레시 토큰에 담긴 값을 그대로 액세스 토큰 생성에 활용한다.
142+
String accessToken = tokenProvider.createToken(authentication);
143+
144+
return ResponseAccount.Token.builder()
145+
.accessToken(accessToken)
146+
.refreshToken(refreshToken)
147+
.build();
148+
}
149+
99150
// 현재 시큐리티 컨텍스트에 저장된 username에 해당하는 정보를 가져온다.
100151

101152

src/main/java/com/example/security/jwt/account/application/dto/RequestAccount.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public record Login(
2020
@Builder
2121
public record Refresh(
2222
@NotNull
23-
String toekn
23+
String token
2424
) {
2525
}
2626

src/main/java/com/example/security/jwt/account/presentation/AccountController.java

+19-4
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@
99
import org.springframework.http.HttpHeaders;
1010
import org.springframework.http.HttpStatus;
1111
import org.springframework.http.ResponseEntity;
12-
import org.springframework.web.bind.annotation.PostMapping;
13-
import org.springframework.web.bind.annotation.RequestBody;
14-
import org.springframework.web.bind.annotation.RequestMapping;
15-
import org.springframework.web.bind.annotation.RestController;
12+
import org.springframework.web.bind.annotation.*;
1613

1714
@RestController
1815
@RequestMapping("/api/v1/accounts")
@@ -41,4 +38,22 @@ public ResponseEntity<CommonResponse> authorize(@Valid @RequestBody RequestAccou
4138

4239
return new ResponseEntity<>(response, headers, HttpStatus.OK);
4340
}
41+
42+
@PutMapping("/token") // 리프레시 토큰을 활용한 액세스 토큰 갱신
43+
public ResponseEntity<CommonResponse> refreshToken(@Valid @RequestBody RequestAccount.Refresh refreshDto) {
44+
45+
ResponseAccount.Token token = accountService.refreshToken(refreshDto.token());
46+
47+
// response header 에도 넣고 응답 객체에도 넣는다.
48+
HttpHeaders httpHeaders = new HttpHeaders();
49+
httpHeaders.add(CustomJwtFilter.AUTHORIZATION_HEADER, "Bearer " + token.accessToken());
50+
51+
// 응답
52+
CommonResponse response = CommonResponse.builder()
53+
.success(true)
54+
.response(token)
55+
.build();
56+
57+
return new ResponseEntity<>(response, httpHeaders, HttpStatus.OK);
58+
}
4459
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.example.security.jwt.admin.facade;
2+
3+
import com.example.security.jwt.admin.facade.dto.RequestAdminFacade;
4+
import com.example.security.jwt.admin.facade.dto.ResponseAdminFacade;
5+
6+
public interface AdminFacade {
7+
8+
ResponseAdminFacade.Information signup(RequestAdminFacade.Register registerDto);
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.example.security.jwt.admin.facade;
2+
3+
import com.example.security.jwt.account.application.AccountService;
4+
import com.example.security.jwt.account.application.dto.RequestAccount;
5+
import com.example.security.jwt.account.application.dto.ResponseAccount;
6+
import com.example.security.jwt.admin.facade.dto.RequestAdminFacade;
7+
import com.example.security.jwt.admin.facade.dto.ResponseAdminFacade;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.stereotype.Service;
10+
import org.springframework.transaction.annotation.Transactional;
11+
12+
@Service
13+
@RequiredArgsConstructor
14+
public class AdminFacadeImpl implements AdminFacade {
15+
16+
private final AccountService accountService;
17+
18+
// admin 회원가입 메서드
19+
@Transactional
20+
@Override
21+
public ResponseAdminFacade.Information signup(RequestAdminFacade.Register registerDto) {
22+
ResponseAccount.Information response = accountService.registerAdmin(RequestAccount.RegisterAdmin.builder()
23+
.nickname(registerDto.nickname())
24+
.password(registerDto.password())
25+
.username(registerDto.username())
26+
.build());
27+
28+
return ResponseAdminFacade.Information.builder()
29+
.authoritySet(response.authoritySet())
30+
.nickname(response.nickname())
31+
.tokenWeight(response.tokenWeight())
32+
.password(response.password())
33+
.username(response.username())
34+
.build();
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.example.security.jwt.admin.facade.dto;
2+
3+
import jakarta.validation.constraints.NotNull;
4+
import jakarta.validation.constraints.Size;
5+
import lombok.Builder;
6+
7+
public record RequestAdminFacade() {
8+
@Builder
9+
public record Register(
10+
@NotNull
11+
@Size(min = 3, max = 50)
12+
String username,
13+
14+
@NotNull
15+
@Size(min = 3, max = 50)
16+
String password,
17+
18+
@NotNull
19+
@Size(min = 3, max = 50)
20+
String nickname
21+
) {
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.example.security.jwt.admin.facade.dto;
2+
3+
import lombok.Builder;
4+
5+
import java.util.Set;
6+
7+
public record ResponseAdminFacade() {
8+
@Builder
9+
public record Information(
10+
String username,
11+
String password,
12+
String nickname,
13+
Long tokenWeight,
14+
Set<String> authoritySet
15+
) {
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.example.security.jwt.admin.presentation;
2+
3+
import com.example.security.jwt.admin.facade.AdminFacade;
4+
import com.example.security.jwt.admin.facade.dto.RequestAdminFacade;
5+
import com.example.security.jwt.admin.facade.dto.ResponseAdminFacade;
6+
import com.example.security.jwt.global.dto.CommonResponse;
7+
import com.example.security.jwt.member.facacde.MemberFacade;
8+
import com.example.security.jwt.member.facacde.dto.RequestMemberFacade;
9+
import com.example.security.jwt.member.facacde.dto.ResponseMemberFacade;
10+
import jakarta.validation.Valid;
11+
import lombok.RequiredArgsConstructor;
12+
import org.springframework.http.ResponseEntity;
13+
import org.springframework.web.bind.annotation.PostMapping;
14+
import org.springframework.web.bind.annotation.RequestBody;
15+
import org.springframework.web.bind.annotation.RequestMapping;
16+
import org.springframework.web.bind.annotation.RestController;
17+
18+
@RestController
19+
@RequestMapping("/api/v1")
20+
@RequiredArgsConstructor
21+
public class AdminController {
22+
23+
private final AdminFacade adminFacade;
24+
25+
@PostMapping("/admin/members")
26+
public ResponseEntity<CommonResponse> signup(@Valid @RequestBody RequestAdminFacade.Register AdminRegisterDto) {
27+
ResponseAdminFacade.Information userInfo = adminFacade.signup(AdminRegisterDto);
28+
29+
CommonResponse response = CommonResponse.builder()
30+
.success(true)
31+
.response(userInfo)
32+
.build();
33+
34+
return ResponseEntity.ok(response);
35+
}
36+
}

src/main/java/com/example/security/jwt/global/exception/GlobalExceptionHandler.java

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import com.example.security.jwt.global.dto.CommonResponse;
44
import com.example.security.jwt.global.dto.ErrorResponse;
5-
import org.hibernate.boot.jaxb.internal.stax.XmlInfrastructureException;
65
import org.springframework.http.ResponseEntity;
76
import org.springframework.web.bind.annotation.ExceptionHandler;
87
import org.springframework.web.bind.annotation.RestControllerAdvice;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.example.security.jwt.global.security;
2+
3+
import io.jsonwebtoken.Claims;
4+
import io.jsonwebtoken.Jwts;
5+
import io.jsonwebtoken.SignatureAlgorithm;
6+
import org.springframework.security.core.Authentication;
7+
import org.springframework.security.core.GrantedAuthority;
8+
9+
import java.util.Date;
10+
import java.util.stream.Collectors;
11+
12+
public class RefreshTokenProvider extends TokenProvider {
13+
public static final String WEIGHT_KEY = "token-weight";
14+
15+
public RefreshTokenProvider(String secret, long tokenValidityInMilliseconds) {
16+
super(secret, tokenValidityInMilliseconds);
17+
}
18+
19+
//토큰 생성
20+
public String createToken(Authentication authentication, Long tokenWeight) {
21+
String authorities = authentication.getAuthorities().stream()
22+
.map(GrantedAuthority::getAuthority)
23+
.collect(Collectors.joining(","));
24+
25+
long now = new Date().getTime();
26+
Date validity = new Date(now + super.tokenValidityInMilliseconds);
27+
28+
return Jwts.builder()
29+
.setSubject(authentication.getName())
30+
.claim(AUTHORITIES_KEY, authorities)
31+
.claim(WEIGHT_KEY, tokenWeight)
32+
.signWith(key, SignatureAlgorithm.HS512)
33+
.setExpiration(validity)
34+
.compact();
35+
}
36+
37+
public long getTokenWeight(String token) {
38+
//토큰에서 가중치를 꺼내 반환한다.
39+
Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
40+
return Long.valueOf(String.valueOf(claims.get(WEIGHT_KEY)));
41+
}
42+
43+
}

src/main/java/com/example/security/jwt/global/security/TokenProvider.java

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import org.springframework.security.core.authority.SimpleGrantedAuthority;
1414
import org.springframework.security.core.userdetails.User;
1515

16-
import java.nio.channels.ScatteringByteChannel;
1716
import java.security.Key;
1817
import java.util.Arrays;
1918
import java.util.Collection;

src/main/java/com/example/security/jwt/global/security/config/JwtConfig.java

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.example.security.jwt.global.security.config;
22

3+
import com.example.security.jwt.global.security.RefreshTokenProvider;
34
import com.example.security.jwt.global.security.TokenProvider;
45
import org.springframework.boot.context.properties.EnableConfigurationProperties;
56
import org.springframework.context.annotation.Bean;
@@ -15,4 +16,11 @@ public TokenProvider tokenProvider(JwtProperties jwtProperties) {
1516
return new TokenProvider(jwtProperties.getSecret(), jwtProperties.getAccessTokenValidityInSeconds());
1617
}
1718

19+
//리프레시 토큰은 별도의 키를 가지기 때문에 리프레시 토큰으로는 API 호출불가
20+
// 액세스 토큰 재발급시 검증용
21+
@Bean(name = "refreshTokenProvider")
22+
public RefreshTokenProvider refreshTokenProvider(JwtProperties jwtProperties) {
23+
return new RefreshTokenProvider(jwtProperties.getRefreshTokenSecret(), jwtProperties.getRefreshTokenValidityInSeconds());
24+
}
25+
1826
}

0 commit comments

Comments
 (0)