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) #415

Open
wants to merge 7 commits into
base: hoon25
Choose a base branch
from
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,http-response,request-parameters,response-body,response-fields']
22 changes: 22 additions & 0 deletions src/main/java/nextstep/auth/AuthConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package nextstep.auth;

import nextstep.auth.principal.AuthenticationPrincipalArgumentResolver;
import nextstep.auth.token.JwtTokenProvider;
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 JwtTokenProvider jwtTokenProvider;

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

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

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

@ResponseStatus(HttpStatus.UNAUTHORIZED)
public class AuthenticationException extends RuntimeException {
}
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,36 @@
package nextstep.auth.principal;

import nextstep.auth.AuthenticationException;
import nextstep.auth.token.JwtTokenProvider;
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 JwtTokenProvider jwtTokenProvider;

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

@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();
}
String token = authorization.split(" ")[1];

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

return new UserPrincipal(username, role);
}
}
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;
}
}
48 changes: 48 additions & 0 deletions src/main/java/nextstep/auth/token/JwtTokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package nextstep.auth.token;

import io.jsonwebtoken.*;
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) {
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;
}
}
52 changes: 52 additions & 0 deletions src/main/java/nextstep/auth/token/TokenService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
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 UserDetailsService userDetailsService;
private OAuth2UserService oAuth2UserService;
private JwtTokenProvider jwtTokenProvider;
private 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();
}

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,29 @@
package nextstep.auth.token.oauth2.github;

public class GithubAccessTokenRequest {

private String code;
private String client_id;
private String client_secret;

public GithubAccessTokenRequest(String code, String client_id, String client_secret) {
this.code = code;
this.client_id = client_id;
this.client_secret = client_secret;
}

public GithubAccessTokenRequest() {
}

public String getCode() {
return code;
}

public String getClient_id() {
return client_id;
}

public String getClient_secret() {
return client_secret;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package nextstep.auth.token.oauth2.github;

import com.fasterxml.jackson.annotation.JsonProperty;

public class GithubAccessTokenResponse {

@JsonProperty("access_token")
private String accessToken;
@JsonProperty("token_type")
private String tokenType;
private String scope;
private String bearer;

public GithubAccessTokenResponse() {

}

public GithubAccessTokenResponse(String accessToken,
String tokenType,
String scope,
String bearer) {
this.accessToken = accessToken;
this.tokenType = tokenType;
this.scope = scope;
this.bearer = bearer;
}

public String getAccessToken() {
return accessToken;
}

public String getTokenType() {
return tokenType;
}

public String getScope() {
return scope;
}

public String getBearer() {
return bearer;
}
}
Loading