feat: 로그인
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
package com.audio.common;
|
package com.audio.common;
|
||||||
|
|
||||||
import com.audio.config.properties.TokenProperties;
|
import com.audio.core.auth.application.userDetail.User;
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
import io.jsonwebtoken.JwtException;
|
import io.jsonwebtoken.JwtException;
|
||||||
import io.jsonwebtoken.JwtParser;
|
import io.jsonwebtoken.JwtParser;
|
||||||
@@ -24,7 +24,7 @@ public class TokenProvider {
|
|||||||
private final Duration refreshToken;
|
private final Duration refreshToken;
|
||||||
|
|
||||||
/** Access Token 생성 */
|
/** Access Token 생성 */
|
||||||
public String createAccessToken(Long accountId, List<String> roles) {
|
public String createUserAccessToken(Long accountId) {
|
||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
Instant exp = now.plus(accessToken);
|
Instant exp = now.plus(accessToken);
|
||||||
|
|
||||||
@@ -34,13 +34,13 @@ public class TokenProvider {
|
|||||||
.setIssuedAt(Date.from(now))
|
.setIssuedAt(Date.from(now))
|
||||||
.setExpiration(Date.from(exp))
|
.setExpiration(Date.from(exp))
|
||||||
.claim("typ", "access")
|
.claim("typ", "access")
|
||||||
.claim("roles", roles)
|
.claim("roles", User.roles)
|
||||||
.signWith(key, SignatureAlgorithm.HS256)
|
.signWith(key, SignatureAlgorithm.HS256)
|
||||||
.compact();
|
.compact();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Refresh Token 생성 */
|
/** Refresh Token 생성 */
|
||||||
public String createRefreshToken(Long accountId) {
|
public String createUserRefreshToken(Long accountId) {
|
||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
Instant exp = now.plus(refreshToken);
|
Instant exp = now.plus(refreshToken);
|
||||||
|
|
||||||
@@ -67,9 +67,17 @@ public class TokenProvider {
|
|||||||
/** Access 토큰인지(typ=access)까지 확인 */
|
/** Access 토큰인지(typ=access)까지 확인 */
|
||||||
public boolean validateAccessToken(String token) {
|
public boolean validateAccessToken(String token) {
|
||||||
try {
|
try {
|
||||||
|
log.info("액세스 토큰 검증");
|
||||||
|
log.info(token);
|
||||||
|
|
||||||
Claims claims = getClaims(token);
|
Claims claims = getClaims(token);
|
||||||
|
|
||||||
|
log.info("sub: {}", claims.getSubject());
|
||||||
|
log.info("typ: {}", claims.get("typ", String.class));
|
||||||
|
|
||||||
return "access".equals(claims.get("typ", String.class));
|
return "access".equals(claims.get("typ", String.class));
|
||||||
} catch (JwtException | IllegalArgumentException e) {
|
} catch (JwtException | IllegalArgumentException e) {
|
||||||
|
log.error(e.getMessage());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,7 +110,7 @@ public class TokenProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String stripBearer(String token) {
|
private String stripBearer(String token) {
|
||||||
if (token == null) return null;
|
if (token == null || !token.startsWith("Bearer ")) return null;
|
||||||
return token.startsWith("Bearer ") ? token.substring(7) : token;
|
return token.substring(7);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.audio.config.filter;
|
package com.audio.config.security;
|
||||||
|
|
||||||
import com.audio.common.TokenProvider;
|
import com.audio.common.TokenProvider;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
@@ -8,19 +8,18 @@ import jakarta.servlet.http.HttpServletResponse;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.util.AntPathMatcher;
|
import org.springframework.util.AntPathMatcher;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||||
private final TokenProvider tokenProvider;
|
private final TokenProvider tokenProvider;
|
||||||
|
private final UserDetailsService userDetailsService;
|
||||||
private final AntPathMatcher pathMatcher = new AntPathMatcher();
|
private final AntPathMatcher pathMatcher = new AntPathMatcher();
|
||||||
private final String[] permitAllPatterns;
|
private final String[] permitAllPatterns;
|
||||||
|
|
||||||
@@ -28,22 +27,19 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
protected boolean shouldNotFilter(HttpServletRequest request) {
|
protected boolean shouldNotFilter(HttpServletRequest request) {
|
||||||
String path = request.getRequestURI();
|
String path = request.getRequestURI();
|
||||||
for (String pattern : permitAllPatterns) {
|
for (String pattern : permitAllPatterns) {
|
||||||
if (pathMatcher.match(pattern, path)) return true;
|
if (pathMatcher.match(pattern, path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
HttpServletRequest request,
|
throws ServletException, IOException {
|
||||||
HttpServletResponse response,
|
|
||||||
FilterChain filterChain
|
|
||||||
) throws ServletException, IOException {
|
|
||||||
|
|
||||||
String authHeader = request.getHeader("Authorization");
|
String authHeader = request.getHeader("Authorization");
|
||||||
String token = (authHeader != null && authHeader.startsWith("Bearer "))
|
String token = (authHeader != null && authHeader.startsWith("Bearer ")) ? authHeader.substring(7) : null;
|
||||||
? authHeader.substring(7)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (token == null || token.isBlank()) {
|
if (token == null || token.isBlank()) {
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
@@ -55,26 +51,16 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
SecurityContextHolder.clearContext();
|
SecurityContextHolder.clearContext();
|
||||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
response.getWriter().write("{\"message\":\"Invalid or expired token\"}");
|
response.getWriter().write("{\"message\":\"유효하지 않은 토큰\"}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 유효하면 인증 객체 세팅
|
// 유효하면 인증 객체 세팅
|
||||||
String subject = tokenProvider.getSubject(token); // 예: userId/loginId
|
String accountId = tokenProvider.getSubject(token);
|
||||||
List<String> roles = safeList(tokenProvider.getRoles(token));
|
UserDetails user = userDetailsService.loadUserByUsername(accountId);
|
||||||
|
SecurityContextHolder.getContext()
|
||||||
var authorities = roles.stream()
|
.setAuthentication(new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()));
|
||||||
.map(r -> r.startsWith("ROLE_") ? r : "ROLE_" + r)
|
|
||||||
.map(SimpleGrantedAuthority::new)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
var authentication = new UsernamePasswordAuthenticationToken(subject, null, authorities);
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
|
||||||
|
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> safeList(List<String> roles) {
|
|
||||||
return roles == null ? Collections.emptyList() : roles;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.audio.config.security;
|
package com.audio.config.security;
|
||||||
|
|
||||||
import com.audio.common.TokenProvider;
|
import com.audio.common.TokenProvider;
|
||||||
import com.audio.config.filter.JwtAuthenticationFilter;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@@ -10,6 +9,7 @@ import org.springframework.http.MediaType;
|
|||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
@@ -19,16 +19,21 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
@Bean
|
private static final String[] allAuthorizedUrls = {
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http, TokenProvider tokenProvider) throws Exception {
|
|
||||||
|
|
||||||
String[] allAuthorizedUrls = {
|
|
||||||
"/health-check",
|
"/health-check",
|
||||||
"/api/v1/auth/**"
|
"/api/v1/auth/**"
|
||||||
};
|
};
|
||||||
|
|
||||||
JwtAuthenticationFilter jwtFilter = new JwtAuthenticationFilter(tokenProvider, allAuthorizedUrls);
|
@Bean
|
||||||
|
public JwtAuthenticationFilter jwtAuthenticationFilter(
|
||||||
|
TokenProvider tokenProvider,
|
||||||
|
UserDetailsService userDetailsService
|
||||||
|
) {
|
||||||
|
return new JwtAuthenticationFilter(tokenProvider, userDetailsService, allAuthorizedUrls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtFilter) throws Exception {
|
||||||
http.csrf(AbstractHttpConfigurer::disable)
|
http.csrf(AbstractHttpConfigurer::disable)
|
||||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
package com.audio.core.auth.api.controller;
|
package com.audio.core.auth.api.controller;
|
||||||
|
|
||||||
|
import com.audio.core.auth.api.dto.req.LoginRequest;
|
||||||
import com.audio.core.auth.api.dto.req.SignUpRequest;
|
import com.audio.core.auth.api.dto.req.SignUpRequest;
|
||||||
|
import com.audio.core.auth.api.dto.res.LoginResponse;
|
||||||
import com.audio.core.auth.api.dto.res.SignUpResponse;
|
import com.audio.core.auth.api.dto.res.SignUpResponse;
|
||||||
|
import com.audio.core.auth.application.LoginUseCase;
|
||||||
import com.audio.core.auth.application.RegisterUseCase;
|
import com.audio.core.auth.application.RegisterUseCase;
|
||||||
import com.audio.core.auth.application.dto.command.RegisterUseCaseCommand;
|
import com.audio.core.auth.application.dto.command.RegisterUseCaseCommand;
|
||||||
|
import com.audio.core.auth.application.dto.result.LoginResult;
|
||||||
import com.audio.core.auth.application.dto.result.RegisterUseCaseResult;
|
import com.audio.core.auth.application.dto.result.RegisterUseCaseResult;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@@ -18,17 +23,14 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
public class AuthController {
|
public class AuthController {
|
||||||
|
|
||||||
private final RegisterUseCase registerUseCase;
|
private final RegisterUseCase registerUseCase;
|
||||||
|
private final LoginUseCase loginUseCase;
|
||||||
|
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
public ResponseEntity<SignUpResponse> register(
|
public ResponseEntity<SignUpResponse> register(
|
||||||
@RequestBody SignUpRequest request
|
@RequestBody SignUpRequest request
|
||||||
) {
|
) {
|
||||||
RegisterUseCaseResult result = registerUseCase.execute(
|
RegisterUseCaseResult result = registerUseCase.execute(
|
||||||
RegisterUseCaseCommand.builder()
|
RegisterUseCaseCommand.builder().loginId(request.getLoginId()).password(request.getPassword()).build());
|
||||||
.loginId(request.getLoginId())
|
|
||||||
.password(request.getPassword())
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
|
|
||||||
SignUpResponse response = SignUpResponse.builder()
|
SignUpResponse response = SignUpResponse.builder()
|
||||||
.accessToken(result.getAccessToken())
|
.accessToken(result.getAccessToken())
|
||||||
@@ -37,4 +39,17 @@ public class AuthController {
|
|||||||
|
|
||||||
return ResponseEntity.ok(response);
|
return ResponseEntity.ok(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/login")
|
||||||
|
public ResponseEntity<LoginResponse> login(
|
||||||
|
@RequestBody LoginRequest request
|
||||||
|
) {
|
||||||
|
LoginResult result = loginUseCase.execute(request.getLoginId(), request.getPassword());
|
||||||
|
LoginResponse response = LoginResponse.builder()
|
||||||
|
.accessToken(result.getAccessToken())
|
||||||
|
.refreshToken(result.getRefreshToken())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.audio.core.auth.api.controller;
|
||||||
|
|
||||||
|
import com.audio.core.auth.application.userDetail.User;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping
|
||||||
|
public class TestController {
|
||||||
|
|
||||||
|
@GetMapping("/test")
|
||||||
|
public ResponseEntity<Boolean> test(
|
||||||
|
@AuthenticationPrincipal User user
|
||||||
|
) {
|
||||||
|
System.out.println(user.getAccountId());
|
||||||
|
|
||||||
|
return ResponseEntity.ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.audio.core.auth.api.dto.req;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class LoginRequest {
|
||||||
|
|
||||||
|
private String loginId;
|
||||||
|
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.audio.core.auth.api.dto.res;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public class LoginResponse {
|
||||||
|
|
||||||
|
private String accessToken;
|
||||||
|
|
||||||
|
private String refreshToken;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.audio.core.auth.application;
|
||||||
|
|
||||||
|
import com.audio.common.TokenProvider;
|
||||||
|
import com.audio.core.auth.application.dto.result.LoginResult;
|
||||||
|
import com.audio.core.auth.domain.service.AccountService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class LoginUseCase {
|
||||||
|
|
||||||
|
private final AccountService accountService;
|
||||||
|
private final TokenProvider tokenProvider;
|
||||||
|
|
||||||
|
public LoginResult execute(String loginId, String password) {
|
||||||
|
long accountId = accountService.login(loginId, password);
|
||||||
|
|
||||||
|
String accessToken = tokenProvider.createUserAccessToken(accountId);
|
||||||
|
String refreshToken = tokenProvider.createUserRefreshToken(accountId);
|
||||||
|
|
||||||
|
return LoginResult.builder()
|
||||||
|
.accessToken(accessToken)
|
||||||
|
.refreshToken(refreshToken)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import com.audio.core.auth.application.dto.command.RegisterUseCaseCommand;
|
|||||||
import com.audio.core.auth.application.dto.result.RegisterUseCaseResult;
|
import com.audio.core.auth.application.dto.result.RegisterUseCaseResult;
|
||||||
import com.audio.core.auth.domain.service.AccountService;
|
import com.audio.core.auth.domain.service.AccountService;
|
||||||
import com.audio.core.exception.auth.ExistLoginIdException;
|
import com.audio.core.exception.auth.ExistLoginIdException;
|
||||||
import java.util.List;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -28,8 +27,8 @@ public final class RegisterUseCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long accountId = accountService.register(loginId, command.getPassword());
|
long accountId = accountService.register(loginId, command.getPassword());
|
||||||
String accessToken = tokenProvider.createAccessToken(accountId, List.of("user"));
|
String accessToken = tokenProvider.createUserAccessToken(accountId);
|
||||||
String refreshToken = tokenProvider.createRefreshToken(accountId);
|
String refreshToken = tokenProvider.createUserRefreshToken(accountId);
|
||||||
|
|
||||||
return RegisterUseCaseResult.builder()
|
return RegisterUseCaseResult.builder()
|
||||||
.accessToken(accessToken)
|
.accessToken(accessToken)
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.audio.core.auth.application.dto.result;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public class LoginResult {
|
||||||
|
|
||||||
|
private String accessToken;
|
||||||
|
private String refreshToken;
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.audio.core.auth.application.userDetail;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
public final class User implements UserDetails {
|
||||||
|
public static final List<String> roles = List.of("USER");
|
||||||
|
|
||||||
|
private static final List<SimpleGrantedAuthority> authorities = roles.stream()
|
||||||
|
.map(SimpleGrantedAuthority::new).collect(Collectors.toList());
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 0L;
|
||||||
|
private final Long accountId;
|
||||||
|
|
||||||
|
public User(Long accountId) {
|
||||||
|
this.accountId = accountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
|
return authorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPassword() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getAccountId() {
|
||||||
|
return accountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || obj.getClass() != this.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var that = (User) obj;
|
||||||
|
return Objects.equals(this.accountId, that.accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "User[" + "accountId=" + accountId + ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.audio.core.auth.application.userDetail;
|
||||||
|
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UserDetailService implements UserDetailsService {
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String accountId) throws UsernameNotFoundException {
|
||||||
|
return new User(Long.valueOf(accountId));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,9 @@ package com.audio.core.auth.domain.service;
|
|||||||
|
|
||||||
import com.audio.core.auth.domain.entity.Account;
|
import com.audio.core.auth.domain.entity.Account;
|
||||||
import com.audio.core.auth.domain.repository.AccountRepository;
|
import com.audio.core.auth.domain.repository.AccountRepository;
|
||||||
|
import com.audio.core.exception.auth.ExistLoginIdException;
|
||||||
|
import com.audio.core.exception.auth.InvalidCredentialsException;
|
||||||
|
import com.audio.core.exception.common.NotExistEntityException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -41,4 +44,18 @@ public class AccountService {
|
|||||||
|
|
||||||
return accountRepository.findByLoginId(loginId).isPresent();
|
return accountRepository.findByLoginId(loginId).isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long login(String loginId, String password) {
|
||||||
|
log.info("로그인");
|
||||||
|
|
||||||
|
Account account = accountRepository.findByLoginId(loginId)
|
||||||
|
.orElseThrow(() -> new NotExistEntityException(Account.class.getName()));
|
||||||
|
|
||||||
|
if (!passwordEncoder.matches(password, account.getPasswordHash())) {
|
||||||
|
throw new InvalidCredentialsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return account.getId();
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.audio.core.exception.auth;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ProblemDetail;
|
||||||
|
import org.springframework.web.ErrorResponseException;
|
||||||
|
|
||||||
|
public class InvalidCredentialsException extends ErrorResponseException {
|
||||||
|
|
||||||
|
public InvalidCredentialsException() {
|
||||||
|
super(
|
||||||
|
HttpStatus.UNAUTHORIZED,
|
||||||
|
ProblemDetail.forStatusAndDetail(
|
||||||
|
HttpStatus.UNAUTHORIZED,
|
||||||
|
"아이디 또는 비밀번호가 올바르지 않습니다."
|
||||||
|
),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.audio.core.exception.common;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ProblemDetail;
|
||||||
|
import org.springframework.web.ErrorResponseException;
|
||||||
|
|
||||||
|
public class NotExistEntityException extends ErrorResponseException {
|
||||||
|
|
||||||
|
public NotExistEntityException(String entityName) {
|
||||||
|
super(
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
ProblemDetail.forStatusAndDetail(
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
"존재하지 않는 엔티티: " + entityName
|
||||||
|
),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user