Skip to content

Commit

Permalink
Merge pull request #1 from HongDam-org/feat/kakao-user
Browse files Browse the repository at this point in the history
[FEAT] Kakao Login Init Setting
  • Loading branch information
ohksj77 authored Aug 9, 2023
2 parents 4726159 + 93e653b commit 9ead6cd
Show file tree
Hide file tree
Showing 29 changed files with 751 additions and 2 deletions.
2 changes: 2 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
.env
backend/src/main/resources/application-oauth.yml

### STS ###
.apt_generated
Expand Down
4 changes: 4 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
annotationProcessor 'org.projectlombok:lombok'
Expand Down
15 changes: 15 additions & 0 deletions backend/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: "3"

services:
db:
image: mariadb:10
ports:
- 3306:3306
env_file: .env
environment:
TZ: Asia/Seoul
networks:
- backend
restart: always
networks:
backend:
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

@SpringBootApplication
public class BackendApplication {

public static void main(String[] args) {

SpringApplication.run(BackendApplication.class, args);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.twtw.backend.config.security;

import com.twtw.backend.config.security.jwt.JwtAccessDeniedHandler;
import com.twtw.backend.config.security.jwt.JwtAuthenticationEntryPoint;
import com.twtw.backend.config.security.jwt.JwtFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig{
private final JwtFilter jwtFilter;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception{
return http.cors(cors -> cors.disable()).csrf(csrf -> csrf.disable())
.httpBasic(h -> h.disable())
.formLogin(f -> f.disable())
.authorizeHttpRequests(
x->x.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html","auth/**")
.permitAll()
)
.authorizeHttpRequests(
x->x.requestMatchers("/test/**")
.permitAll()
.anyRequest()
.authenticated()
)
.sessionManagement(x -> x.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling(
x -> {
x.authenticationEntryPoint(jwtAuthenticationEntryPoint);
x.accessDeniedHandler(jwtAccessDeniedHandler);
}
).build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.twtw.backend.config.security.jwt;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.twtw.backend.config.security.jwt;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.twtw.backend.config.security.jwt;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
@RequiredArgsConstructor
public class JwtFilter extends OncePerRequestFilter {

private final TokenProvider tokenProvider;
private final String AUTHORIZATION_HEADER = "Authorization";
private final String BEARER_PREFIX = "Bearer ";

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String jwt = resolveToken(request);

if(StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt))
{
Authentication authentication = tokenProvider.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request,response);
}

private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);

if(StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX))
{
return bearerToken.substring(7);
}

return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.twtw.backend.config.security.jwt;
import com.twtw.backend.domain.member.dto.response.TokenDto;
import com.twtw.backend.domain.member.entity.Member;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Component
public class TokenProvider implements InitializingBean {

private Key key;
private final String secretKey;
private static final String AUTHORITIES_KEY = "auth";
private static final Long ACCESS_TOKEN_EXPIRE_LENGTH = 60L * 60 * 24 * 1000; // 1 Day
private static final Long REFRESH_TOKEN_EXPIRE_LENGTH = 60L * 60 * 24 * 14 * 1000; // 14 Days

public TokenProvider(
@Value("${jwt.secret}") String secretKey
){
this.secretKey = secretKey;
}

@Override
public void afterPropertiesSet() throws Exception {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
this.key = Keys.hmacShaKeyFor(keyBytes);
}


public TokenDto createToken(Authentication authentication) {
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));

Long now = (new Date()).getTime();
Date validAccessDate = new Date(now + ACCESS_TOKEN_EXPIRE_LENGTH);
Date validRefreshDate = new Date(now + REFRESH_TOKEN_EXPIRE_LENGTH);

String accessToken = Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY,authorities)
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(validAccessDate)
.compact();

String refreshToken = Jwts.builder()
.setExpiration(validRefreshDate)
.signWith(key,SignatureAlgorithm.HS512)
.compact();

return new TokenDto(accessToken,refreshToken);
}

public Authentication getAuthentication(String accessToken) {
Claims claims = parseClaims(accessToken);

if(claims.get(AUTHORITIES_KEY) == null)
{
throw new RuntimeException("권한 정보가 없는 토큰입니다.");
}

Collection<GrantedAuthority> authorities = new ArrayList<>();
String role = claims.get(AUTHORITIES_KEY).toString();

if(role.equals("ROLE_ADMIN")) {
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
}

else if(role.equals("ROLE_USER")){
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
}

return new UsernamePasswordAuthenticationToken(claims.getSubject(),"",authorities);

}

public boolean validateToken(String token) {
try{
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
}catch (ExpiredJwtException | UnsupportedJwtException | IllegalStateException e) {
return false;
}
}

private Claims parseClaims(String accessToken) {
try{
Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(accessToken).getBody();
return claims;
}catch (ExpiredJwtException e)
{
return e.getClaims();
}
}

public UsernamePasswordAuthenticationToken makeCredit(Member member)
{
List<GrantedAuthority> role = new ArrayList<>();
role.add(new SimpleGrantedAuthority(member.getRole().toString()));
UsernamePasswordAuthenticationToken credit = new UsernamePasswordAuthenticationToken(member.getId().toString(),"",role);

return credit;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.twtw.backend.domain.member;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;

@Configuration
public class SwaggerConfiguration {
@Bean
public OpenAPI getOpenAPI() {
return new OpenAPI()
.info(
new Info().title("TWTW")
.description("Hong Dam Jin")
.version("v0.0.1")
)
.components(
new Components().addSecuritySchemes(
"bearerAuth", new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT")
.in(SecurityScheme.In.HEADER).name("Authorization")
)
).security(
List.of(new SecurityRequirement().addList("bearerAuth"))
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.twtw.backend.domain.member.controller;

import com.twtw.backend.domain.member.dto.request.MemberSaveRequest;
import com.twtw.backend.domain.member.dto.response.TokenDto;
import com.twtw.backend.domain.member.service.AuthService;
import com.twtw.backend.domain.member.dto.request.OAuthRequest;
import com.twtw.backend.domain.member.dto.request.TokenRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/auth")
public class AuthController {
private final AuthService authService;

public AuthController(AuthService authService) {
this.authService = authService;
}

@PostMapping("/refresh")
public ResponseEntity<TokenDto> authorize(@RequestBody TokenRequest tokenRequest) {
return ResponseEntity.ok(authService.refreshToken(tokenRequest.getAccessToken(),tokenRequest.getRefreshToken()));
}

@PostMapping("/save")
public ResponseEntity<TokenDto> saveMember(@RequestBody MemberSaveRequest memberSaveRequest) {
TokenDto tokenDto = authService.saveMember(memberSaveRequest);

return ResponseEntity.status(HttpStatus.OK).body(tokenDto);
}

@PostMapping("/login")
public ResponseEntity<TokenDto> afterSocialLogin(@RequestBody OAuthRequest request){
TokenDto tokenDto = authService.getTokenByOAuth(request);

if(tokenDto == null){
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(tokenDto);
}

else{
return ResponseEntity.status(HttpStatus.OK).body(tokenDto);
}
}
}
Loading

0 comments on commit 9ead6cd

Please sign in to comment.