Skip to content

Commit

Permalink
Merge pull request #6 from CodingWasabi/feature-3/auth
Browse files Browse the repository at this point in the history
🌞[INIT] security 초기 세팅
  • Loading branch information
daehwan2yo authored Dec 22, 2021
2 parents e777610 + c934b01 commit c657b97
Show file tree
Hide file tree
Showing 18 changed files with 478 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
HELP.md
.gradle
.gradle/**
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
Expand Down
Binary file modified .gradle/7.3.2/executionHistory/executionHistory.lock
Binary file not shown.
Binary file modified .gradle/7.3.2/fileHashes/fileHashes.lock
Binary file not shown.
Binary file modified .gradle/buildOutputCleanup/buildOutputCleanup.lock
Binary file not shown.
5 changes: 4 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,17 @@ ext {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'mysql:mysql-connector-java'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'


implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt:0.9.1'
testImplementation 'org.springframework.security:spring-security-test'
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.codingwasabi.trti.config.auth.jwt;

import lombok.RequiredArgsConstructor;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
@RequiredArgsConstructor
public class JwtEntryPoint implements AuthenticationEntryPoint {
private final JwtProvider jwtProvider;

@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
String exception = jwtProvider.setInvalidJwtMessage(jwtProvider.resolve(request));
// JWT 관련 인증 예외를 처리한다. 403
response.sendError(HttpServletResponse.SC_FORBIDDEN, exception);
}
}
30 changes: 30 additions & 0 deletions src/main/java/com/codingwasabi/trti/config/auth/jwt/JwtFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.codingwasabi.trti.config.auth.jwt;

import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@RequiredArgsConstructor
public class JwtFilter extends OncePerRequestFilter {
private final JwtProvider jwtProvider;

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = jwtProvider.resolve(request);
// 토큰이 유효한 경우에는 인증정보를 추출한다.
if(jwtProvider.validate(token)) {
Authentication authentication = jwtProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
}
119 changes: 119 additions & 0 deletions src/main/java/com/codingwasabi/trti/config/auth/jwt/JwtProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.codingwasabi.trti.config.auth.jwt;

import com.codingwasabi.trti.config.auth.security.MemberAdaptor;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.util.Base64;
import java.util.Date;

@Component
public class JwtProvider {
private final UserDetailsService securityService;
private final Long validTimeMilli;
private String key;

public JwtProvider(@Value("${jwt.validTime}") Long validTime,
@Value("${jwt.secret}") String key,
UserDetailsService securityService) {
this.securityService = securityService;
this.validTimeMilli = validTime * 1000L;
this.key = key;
}

@PostConstruct
protected void init() {
key = Base64.getEncoder().encodeToString(key.getBytes());
}

/**
* JWT 생성
* @param email
* @return
*/
public String create(String email, String providerId) {
Date now = new Date();
Claims claims = Jwts.claims().setSubject(email);
claims.put("providerId", providerId);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + validTimeMilli))
.signWith(SignatureAlgorithm.HS256, key)
.compact();
}


/**
* JWT 유효성 검증 (key 검증 및 만료일자 검증)
* @param jwtToken
* @return
*/
public boolean validate(String jwtToken) {
try {
Jws<Claims> claimsJws = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jwtToken);

return claimsJws.getBody().getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}

/**
* JWT 에서 회원정보 추출
* @param jwtToken
* @return
*/
public Authentication getAuthentication(String jwtToken) {
MemberAdaptor memberAdaptor = (MemberAdaptor) securityService
.loadUserByUsername(getEmailFromToken(jwtToken));

return new UsernamePasswordAuthenticationToken(memberAdaptor, null,
memberAdaptor.getAuthorities());
}

/**
* JWT 에서 email 추출
* @param jwtToken
* @return
*/
private String getEmailFromToken(String jwtToken) {
return Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jwtToken)
.getBody()
.getSubject();
}

/**
* request 의 header 로부터 토큰 추출
* @param request
* @return
*/
public String resolve(HttpServletRequest request) {
return request.getHeader("X-AUTH-TOKEN");
}

public String setInvalidJwtMessage(String jwtToken) {
try {
Jwts.parser().setSigningKey(key).parseClaimsJws(jwtToken);
return "Server 내부에서 발생한 인증 오류입니다. Concorn 개발팀에 문의하세요.";
} catch (UnsupportedJwtException | MalformedJwtException e) {
return "지원하지 않는 구성의 JWT 입니다. 버전 혹은 암호화 방식을 확인하세요.";
} catch (ExpiredJwtException e) {
return "만료된 JWT 입니다.";
} catch (SignatureException e) {
return "서버에서 허용하지 않은 key 로 생성한 JWT 입니다. 접근 거부";
} catch (IllegalArgumentException e) {
return "JWT 가 공백 형태입니다. 헤더를 확인해주세요.";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.codingwasabi.trti.config.auth.oauth.kind;

import com.codingwasabi.trti.domain.member.model.entity.Member;
import com.codingwasabi.trti.domain.member.model.enumValue.Gender;

import java.util.Map;

import static com.codingwasabi.trti.config.auth.oauth.provider.OauthProvider.KAKAO;

public class KakaoOauthInfo implements OauthInfo{
private final Map<String, Object> attributeMap;

private KakaoOauthInfo(Map<String, Object> attributeMap) {
this.attributeMap = attributeMap;
}

public static KakaoOauthInfo from(Map<String, Object> attributeMap) {
return new KakaoOauthInfo(attributeMap);
}

@Override
public Gender getGender() {
return (Gender) attributeMap.get("gender");
}

@Override
public String getAgeRange() {
return (String) attributeMap.get("ageRange");
}

@Override
public String getImagePath() {
return (String) attributeMap.get("profileImage");
}

@Override
public String getProviderId() {
return "k_" + attributeMap.get("kakaoId");
}

@Override
public String getProviderKind() {
return KAKAO.toString();
}

@Override
public String getEmail() {
return (String) attributeMap.get("email");
}

@Override
public String getNickname() {
return (String) attributeMap.get("nickname");
}

@Override
public Member getEntity() {
return Member.builder()
.oauthId(getProviderId())
.email(getEmail())
.ageRange(getAgeRange())
.gender(getGender())
.imagePath(getImagePath())
.nickname(getNickname())
.provider(KAKAO)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.codingwasabi.trti.config.auth.oauth.kind;

import com.codingwasabi.trti.domain.member.model.entity.Member;
import com.codingwasabi.trti.domain.member.model.enumValue.Gender;

public interface OauthInfo {
String getProviderId();

String getProviderKind();

String getEmail();

String getNickname();

Gender getGender();

String getAgeRange();

String getImagePath();

Member getEntity();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.codingwasabi.trti.config.auth.oauth.provider;

public enum OauthProvider {
KAKAO;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.codingwasabi.trti.config.auth.oauth.service;

import com.codingwasabi.trti.config.auth.oauth.kind.KakaoOauthInfo;
import com.codingwasabi.trti.config.auth.oauth.kind.OauthInfo;
import com.codingwasabi.trti.config.auth.oauth.provider.OauthProvider;
import org.springframework.stereotype.Service;

import java.util.Map;

@Service
public class OauthService {
public OauthInfo filtrateOauth(String provider, Map<String, Object> requestMap) {
if(isKakao(provider)) {
return getKakao(requestMap);
}

// error code 추가
// "올바르지 못한 oauth 접근"
throw new IllegalArgumentException("Error");
}

private OauthInfo getKakao(Map<String, Object> requestMap) {
return KakaoOauthInfo.from(requestMap);
}

private boolean isKakao(String provider) {
return provider.equals(OauthProvider.KAKAO);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.codingwasabi.trti.config.auth.security;

import com.codingwasabi.trti.domain.member.model.entity.Member;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;

public class MemberAdaptor implements UserDetails {

@Getter
public final Member member;

public MemberAdaptor(Member member) {
this.member = member;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(member.getAuthority().getRole()));
return authorities;
}

@Override
public String getPassword() {
return null;
}

@Override
public String getUsername() {
return member.getEmail();
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}
}
Loading

0 comments on commit c657b97

Please sign in to comment.