Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Step0 #409

Open
wants to merge 4 commits into
base: michaelkimm
Choose a base branch
from
Open

Step0 #409

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@

=== 경로 조회

operation::path[snippets='http-request,http-response']
operation::path[snippets='http-request,request-parameters,http-response,response-fields']
25 changes: 25 additions & 0 deletions src/main/java/nextstep/auth/AuthConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package nextstep.auth;

import nextstep.auth.principal.AuthenticationPrincipalArgumentResolver;
import nextstep.auth.token.JwtTokenProvider;
import nextstep.member.domain.MemberRepository;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class AuthConfig implements WebMvcConfigurer {
private final JwtTokenProvider jwtTokenProvider;
private final MemberRepository memberRepository;

public AuthConfig(JwtTokenProvider jwtTokenProvider, MemberRepository memberRepository) {
this.jwtTokenProvider = jwtTokenProvider;
this.memberRepository = memberRepository;
}

@Override
public void addArgumentResolvers(List argumentResolvers) {
argumentResolvers.add(new AuthenticationPrincipalArgumentResolver(jwtTokenProvider, memberRepository));
}
}
28 changes: 28 additions & 0 deletions src/main/java/nextstep/auth/AuthExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package nextstep.auth;

import nextstep.common.ErrorResponse;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.util.Locale;

@ControllerAdvice
public class AuthExceptionHandler {

private final MessageSource messageSource;

public AuthExceptionHandler(MessageSource messageSource) {
this.messageSource = messageSource;
}

@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<ErrorResponse> handleAuthenticationException(Exception e) {
ErrorResponse response = new ErrorResponse(messageSource.getMessage(e.getMessage(), null, Locale.KOREA));
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body(response);
}
}
12 changes: 12 additions & 0 deletions src/main/java/nextstep/auth/AuthenticationException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package nextstep.auth;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.UNAUTHORIZED)
public class AuthenticationException extends RuntimeException {

public AuthenticationException(String message) {
super(message);
}
}
9 changes: 9 additions & 0 deletions src/main/java/nextstep/auth/ForbiddenException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package nextstep.auth;


public class ForbiddenException extends RuntimeException{

public ForbiddenException(String message) {
super(message);
}
}
11 changes: 11 additions & 0 deletions src/main/java/nextstep/auth/principal/AuthenticationPrincipal.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package nextstep.auth.principal;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthenticationPrincipal {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package nextstep.auth.principal;

import nextstep.auth.AuthenticationException;
import nextstep.auth.token.JwtTokenProvider;
import nextstep.member.domain.Member;
import nextstep.member.domain.MemberRepository;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {
private final JwtTokenProvider jwtTokenProvider;
private final MemberRepository memberRepository;

public AuthenticationPrincipalArgumentResolver(JwtTokenProvider jwtTokenProvider, MemberRepository memberRepository) {
this.jwtTokenProvider = jwtTokenProvider;
this.memberRepository = memberRepository;
}

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(AuthenticationPrincipal.class);
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String authorization = webRequest.getHeader("Authorization");
if (!"bearer".equalsIgnoreCase(authorization.split(" ")[0])) {
throw new AuthenticationException("auth.0001");
}
String token = authorization.split(" ")[1];

String username = jwtTokenProvider.getPrincipal(token);
String role = jwtTokenProvider.getRoles(token);

Member member = memberRepository.findByEmail(username)
.orElseThrow(() -> new IllegalArgumentException("member.0001"));

return member.getId();
}
}
19 changes: 19 additions & 0 deletions src/main/java/nextstep/auth/principal/UserPrincipal.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package nextstep.auth.principal;

public class UserPrincipal {
private String username;
private String role;

public UserPrincipal(String username, String role) {
this.username = username;
this.role = role;
}

public String getUsername() {
return username;
}

public String getRole() {
return role;
}
}
52 changes: 52 additions & 0 deletions src/main/java/nextstep/auth/token/JwtTokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package nextstep.auth.token;

import io.jsonwebtoken.*;
import nextstep.auth.AuthenticationException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class JwtTokenProvider {
@Value("${security.jwt.token.secret-key}")
private String secretKey;
@Value("${security.jwt.token.expire-length}")
private long validityInMilliseconds;

public String createToken(String principal, String role) {
Claims claims = Jwts.claims().setSubject(principal);
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);

return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.claim("role", role)
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}

public String getPrincipal(String token) {
if (!validateToken(token)) {
throw new AuthenticationException("auth.0001");
}
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}

public String getRoles(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().get("role", String.class);
}

public boolean validateToken(String token) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);

return !claims.getBody().getExpiration().before(new Date());
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}

30 changes: 30 additions & 0 deletions src/main/java/nextstep/auth/token/TokenController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package nextstep.auth.token;

import nextstep.auth.token.oauth2.github.GithubTokenRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TokenController {
private TokenService tokenService;

public TokenController(TokenService tokenService) {
this.tokenService = tokenService;
}

@PostMapping("/login/token")
public ResponseEntity<TokenResponse> createToken(@RequestBody TokenRequest request) {
TokenResponse response = tokenService.createToken(request.getEmail(), request.getPassword());

return ResponseEntity.ok(response);
}

@PostMapping("/login/github")
public ResponseEntity<TokenResponse> createTokenByGithub(@RequestBody GithubTokenRequest request) {
TokenResponse response = tokenService.createTokenFromGithub(request.getCode());

return ResponseEntity.ok(response);
}
}
22 changes: 22 additions & 0 deletions src/main/java/nextstep/auth/token/TokenRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package nextstep.auth.token;

public class TokenRequest {
private String email;
private String password;

public TokenRequest() {
}

public TokenRequest(String email, String password) {
this.email = email;
this.password = password;
}

public String getEmail() {
return email;
}

public String getPassword() {
return password;
}
}
16 changes: 16 additions & 0 deletions src/main/java/nextstep/auth/token/TokenResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package nextstep.auth.token;

public class TokenResponse {
private String accessToken;

public TokenResponse() {
}

public TokenResponse(String accessToken) {
this.accessToken = accessToken;
}

public String getAccessToken() {
return accessToken;
}
}
47 changes: 47 additions & 0 deletions src/main/java/nextstep/auth/token/TokenService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package nextstep.auth.token;

import nextstep.auth.AuthenticationException;
import nextstep.auth.token.oauth2.OAuth2User;
import nextstep.auth.token.oauth2.OAuth2UserService;
import nextstep.auth.token.oauth2.github.GithubClient;
import nextstep.auth.token.oauth2.github.GithubProfileResponse;
import nextstep.auth.userdetails.UserDetails;
import nextstep.auth.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;

@Service
public class TokenService {
private final UserDetailsService userDetailsService;
private final OAuth2UserService oAuth2UserService;
private final JwtTokenProvider jwtTokenProvider;
private final GithubClient githubClient;

public TokenService(UserDetailsService userDetailsService, OAuth2UserService oAuth2UserService, JwtTokenProvider jwtTokenProvider, GithubClient githubClient) {
this.userDetailsService = userDetailsService;
this.oAuth2UserService = oAuth2UserService;
this.jwtTokenProvider = jwtTokenProvider;
this.githubClient = githubClient;
}

public TokenResponse createToken(String email, String password) {
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
if (!userDetails.getPassword().equals(password)) {
throw new AuthenticationException("auth.0001");
}

String token = jwtTokenProvider.createToken(userDetails.getUsername(), userDetails.getRole());

return new TokenResponse(token);
}

public TokenResponse createTokenFromGithub(String code) {
String accessTokenFromGithub = githubClient.getAccessTokenFromGithub(code);
GithubProfileResponse githubProfile = githubClient.getGithubProfileFromGithub(accessTokenFromGithub);

OAuth2User oAuth2User = oAuth2UserService.loadUser(githubProfile);

String token = jwtTokenProvider.createToken(oAuth2User.getUsername(), oAuth2User.getRole());

return new TokenResponse(token);
}
}
7 changes: 7 additions & 0 deletions src/main/java/nextstep/auth/token/oauth2/OAuth2User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package nextstep.auth.token.oauth2;

public interface OAuth2User {
String getUsername();

String getRole();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package nextstep.auth.token.oauth2;

public interface OAuth2UserRequest {
String getUsername();

Integer getAge();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package nextstep.auth.token.oauth2;

public interface OAuth2UserService {
OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package nextstep.auth.token.oauth2.github;

import nextstep.auth.AuthenticationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.DefaultResponseErrorHandler;

import java.io.IOException;

public class GetAccessTokenErrorHandler extends DefaultResponseErrorHandler {

@Override
protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
throw new AuthenticationException("auth.0001");
}
}
Loading