Skip to content

Commit b23ca76

Browse files
author
kimyonghwa
committed
Merge branch 'feature/security' of https://github.com/codej99/SpringRestApi into feature/security
# Conflicts: # src/main/java/com/rest/api/config/security/CustomAccessDeniedHandler.java # src/main/java/com/rest/api/config/security/SecurityConfiguration.java
2 parents 695908e + 49a244c commit b23ca76

33 files changed

+873
-70
lines changed

‎README.md

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Spring Rest Api 만들기 프로젝트
2+
3+
### 0. 개요
4+
- SpringBoot2 framework 기반에서 RESTful api 서비스를 Step by Step으로 만들어 나가는 프로젝트
5+
- daddyprogrammer.org에서 연재 및 소스 Github 등록
6+
- https://daddyprogrammer.org/post/series/springboot2%EB%A1%9C-rest-api-%EB%A7%8C%EB%93%A4%EA%B8%B0/
7+
8+
### 1. 개발환경
9+
- Java 8~11
10+
- SpringBoot 2.x
11+
- SpringSecurity 5.x
12+
- JPA, H2
13+
- Intellij Community
14+
15+
### 2. 프로젝트 실행
16+
- H2 database 설치
17+
- https://www.h2database.com/html/download.html
18+
- intellij lombok 플러그인 설치
19+
- Preferences -> Plugins -> Browse repositories... -> search lombok -> Install "IntelliJ Lombok plugin"
20+
- Enable annotation processing
21+
- Preferences - Annotation Procesors - Enable annotation processing 체크
22+
- build.gradle에 lombok 추가(Git을 받은경우 이미 추가되어있음)
23+
- compileOnly 'org.projectlombok:lombok:1.16.16'
24+
- 실행
25+
- Run -> SpringBootApiApplication
26+
- Swagger
27+
- http://localhost:8080/swagger-ui.html
28+
29+
### 3. DDL
30+
create table user (
31+
msrl bigint not null auto_increment,
32+
name varchar(100) not null,
33+
password varchar(100),
34+
provider varchar(100),
35+
uid varchar(50) not null,
36+
primary key (msrl)
37+
) engine=InnoDB;
38+
39+
create table user_roles (
40+
user_msrl bigint not null,
41+
roles varchar(255)
42+
) engine=InnoDB;
43+
44+
45+
alter table user
46+
add constraint UK_a7hlm8sj8kmijx6ucp7wfyt31 unique (uid);
47+
48+
alter table user_roles
49+
add constraint FKel3d4qj41g0sy1mtp4sh055g7
50+
foreign key (user_msrl)
51+
references user (msrl);
52+
53+
### 4. 목차
54+
- SpringBoot2로 Rest api 만들기(1) – Intellij Community에서 프로젝트생성
55+
- Document
56+
- https://daddyprogrammer.org/post/19/spring-boot1-start-intellij/
57+
- SpringBoot2로 Rest api 만들기(2) – HelloWorld
58+
- Document
59+
- https://daddyprogrammer.org/post/41/spring-boot2-helloworld/
60+
- SpringBoot2로 Rest api 만들기(3) – H2 Database 연동
61+
- Document
62+
- https://daddyprogrammer.org/post/152/spring-boot2-h2-database-intergrate/
63+
- Git
64+
- https://github.com/codej99/SpringRestApi/tree/feature/h2
65+
- SpringBoot2로 Rest api 만들기(4) – Swagger API 문서 자동화
66+
- Document
67+
- https://daddyprogrammer.org/post/313/swagger-api-doc/
68+
- Git
69+
- https://github.com/codej99/SpringRestApi/tree/feature/swagger
70+
- SpringBoot2로 Rest api 만들기(5) – API 인터페이스 및 결과 데이터 구조 설계
71+
- Document
72+
- https://daddyprogrammer.org/post/404/spring-boot2-5-design-api-interface-and-data-structure/
73+
- Git
74+
- https://github.com/codej99/SpringRestApi/tree/feature/api-structure
75+
- SpringBoot2로 Rest api 만들기(6) – ControllerAdvice를 이용한 Exception처리
76+
- Document
77+
- https://daddyprogrammer.org/post/446/spring-boot2-5-exception-handling/
78+
- Git
79+
- https://github.com/codej99/SpringRestApi/tree/feature/controller-advice
80+
- SpringBoot2로 Rest api 만들기(7) – MessageSource를 이용한 Exception 처리
81+
- Document
82+
- https://daddyprogrammer.org/post/499/springboot2-message-exception-handling-with-controlleradvice/
83+
- Git
84+
- https://github.com/codej99/SpringRestApi/tree/feature/messagesource
85+
- SpringBoot2로 Rest api 만들기(8) – SpringSecurity를 이용한 인증 및 권한부여
86+
- Document
87+
- https://daddyprogrammer.org/post/636/springboot2-springsecurity-authentication-authorization/
88+
- Git
89+
- https://github.com/codej99/SpringRestApi/tree/feature/security
90+
- SpringBoot2로 Rest api 만들기(9) – Unit Test
91+
- Document
92+
- https://daddyprogrammer.org/post/938/springboot2-restapi-unit-test/
93+
- Git
94+
- https://github.com/codej99/SpringRestApi/tree/feature/junit-test
95+
- SpringBoot2로 Rest api 만들기(10) – Social Login kakao
96+
- Document
97+
- https://daddyprogrammer.org/post/1012/springboot2-rest-api-social-login-kakao/
98+
- Git
99+
- https://github.com/codej99/SpringRestApi/tree/feature/social-kakao

‎build.gradle

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
plugins {
22
id 'org.springframework.boot' version '2.1.4.RELEASE'
33
id 'java'
4+
id "org.sonarqube" version "2.7"
45
}
56

67
apply plugin: 'io.spring.dependency-management'
@@ -28,9 +29,11 @@ dependencies {
2829
implementation 'io.springfox:springfox-swagger2:2.6.1'
2930
implementation 'io.springfox:springfox-swagger-ui:2.6.1'
3031
implementation 'net.rakugakibox.util:yaml-resource-bundle:1.1'
32+
implementation 'com.google.code.gson:gson'
3133
compileOnly 'org.projectlombok:lombok'
3234
runtimeOnly 'com.h2database:h2'
3335
runtimeOnly 'mysql:mysql-connector-java'
3436
annotationProcessor 'org.projectlombok:lombok'
37+
testImplementation 'org.springframework.security:spring-security-test'
3538
testImplementation 'org.springframework.boot:spring-boot-starter-test'
3639
}

‎src/main/java/com/rest/api/SpringRestApiApplication.java

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.springframework.context.annotation.Bean;
66
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
77
import org.springframework.security.crypto.password.PasswordEncoder;
8+
import org.springframework.web.client.RestTemplate;
89

910
@SpringBootApplication
1011
public class SpringRestApiApplication {
@@ -16,4 +17,9 @@ public static void main(String[] args) {
1617
public PasswordEncoder passwordEncoder() {
1718
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
1819
}
20+
21+
@Bean
22+
public RestTemplate getRestTemplate() {
23+
return new RestTemplate();
24+
}
1925
}

‎src/main/java/com/rest/api/advice/ExceptionAdvice.java

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package com.rest.api.advice;
22

3-
import com.rest.api.advice.exception.CAuthenticationEntryPointException;
4-
import com.rest.api.advice.exception.CEmailSigninFailedException;
5-
import com.rest.api.advice.exception.CUserNotFoundException;
3+
import com.rest.api.advice.exception.*;
64
import com.rest.api.model.response.CommonResult;
75
import com.rest.api.service.ResponseService;
86
import lombok.RequiredArgsConstructor;
@@ -51,10 +49,22 @@ public CommonResult authenticationEntryPointException(HttpServletRequest request
5149

5250
@ExceptionHandler(AccessDeniedException.class)
5351
@ResponseStatus(HttpStatus.UNAUTHORIZED)
54-
public CommonResult AccessDeniedException(HttpServletRequest request, AccessDeniedException e) {
52+
public CommonResult accessDeniedException(HttpServletRequest request, AccessDeniedException e) {
5553
return responseService.getFailResult(Integer.valueOf(getMessage("accessDenied.code")), getMessage("accessDenied.msg"));
5654
}
5755

56+
@ExceptionHandler(CCommunicationException.class)
57+
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
58+
public CommonResult communicationException(HttpServletRequest request, CCommunicationException e) {
59+
return responseService.getFailResult(Integer.valueOf(getMessage("communicationError.code")), getMessage("communicationError.msg"));
60+
}
61+
62+
@ExceptionHandler(CUserExistException.class)
63+
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
64+
public CommonResult communicationException(HttpServletRequest request, CUserExistException e) {
65+
return responseService.getFailResult(Integer.valueOf(getMessage("existingUser.code")), getMessage("existingUser.msg"));
66+
}
67+
5868
// code정보에 해당하는 메시지를 조회합니다.
5969
private String getMessage(String code) {
6070
return getMessage(code, null);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.rest.api.advice.exception;
2+
3+
public class CCommunicationException extends RuntimeException {
4+
public CCommunicationException(String msg, Throwable t) {
5+
super(msg, t);
6+
}
7+
8+
public CCommunicationException(String msg) {
9+
super(msg);
10+
}
11+
12+
public CCommunicationException() {
13+
super();
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.rest.api.advice.exception;
2+
3+
public class CUserExistException extends RuntimeException {
4+
public CUserExistException(String msg, Throwable t) {
5+
super(msg, t);
6+
}
7+
8+
public CUserExistException(String msg) {
9+
super(msg);
10+
}
11+
12+
public CUserExistException() {
13+
super();
14+
}
15+
}

‎src/main/java/com/rest/api/config/MessageConfiguration.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.rest.api.config;
22

3-
import lombok.extern.slf4j.Slf4j;
43
import net.rakugakibox.util.YamlResourceBundle;
54
import org.springframework.beans.factory.annotation.Value;
65
import org.springframework.context.MessageSource;
@@ -14,7 +13,6 @@
1413
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
1514

1615
import java.util.Locale;
17-
import java.util.MissingResourceException;
1816
import java.util.ResourceBundle;
1917

2018
@Configuration
@@ -56,7 +54,7 @@ public MessageSource messageSource(
5654
// locale 정보에 따라 다른 yml 파일을 읽도록 처리
5755
private static class YamlMessageSource extends ResourceBundleMessageSource {
5856
@Override
59-
protected ResourceBundle doGetBundle(String basename, Locale locale) throws MissingResourceException {
57+
protected ResourceBundle doGetBundle(String basename, Locale locale) {
6058
return ResourceBundle.getBundle(basename, locale, YamlResourceBundle.Control.INSTANCE);
6159
}
6260
}
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
package com.rest.api.config.security;
22

33
import lombok.extern.slf4j.Slf4j;
4-
import org.slf4j.Logger;
5-
import org.slf4j.LoggerFactory;
64
import org.springframework.security.access.AccessDeniedException;
75
import org.springframework.security.web.access.AccessDeniedHandler;
86
import org.springframework.stereotype.Component;
97

10-
import javax.servlet.RequestDispatcher;
11-
import javax.servlet.ServletException;
128
import javax.servlet.http.HttpServletRequest;
139
import javax.servlet.http.HttpServletResponse;
1410
import java.io.IOException;
@@ -17,11 +13,8 @@
1713
@Component
1814
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
1915

20-
private static final Logger logger = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);
21-
22-
@Override
23-
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) throws IOException,
24-
ServletException {
25-
response.sendRedirect("/exception/accessdenied");
26-
}
16+
@Override
17+
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) throws IOException {
18+
response.sendRedirect("/exception/accessdenied");
19+
}
2720
}

‎src/main/java/com/rest/api/config/security/JwtTokenProvider.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
@Component
2323
public class JwtTokenProvider { // JWT 토큰을 생성 및 검증 모듈
2424

25-
@Value("spring.jwt.secret")
25+
@Value("${spring.jwt.secret}")
2626
private String secretKey;
2727

2828
private long tokenValidMilisecond = 1000L * 60 * 60; // 1시간만 토큰 유효

‎src/main/java/com/rest/api/config/security/SecurityConfiguration.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ protected void configure(HttpSecurity http) throws Exception {
3131
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // jwt token으로 인증할것이므로 세션필요없으므로 생성안함.
3232
.and()
3333
.authorizeRequests() // 다음 리퀘스트에 대한 사용권한 체크
34-
.antMatchers("/*/signin", "/*/signup").permitAll() // 가입 및 인증 주소는 누구나 접근가능
34+
.antMatchers("/*/signin", "/*/signin/**", "/*/signup", "/*/signup/**", "/social/**").permitAll() // 가입 및 인증 주소는 누구나 접근가능
3535
.antMatchers(HttpMethod.GET, "/exception/**","/helloworld/**").permitAll() // hellowworld로 시작하는 GET요청 리소스는 누구나 접근가능
3636
.antMatchers("/*/users").hasRole("ADMIN")
3737
.anyRequest().hasRole("USER") // 그외 나머지 요청은 모두 인증된 회원만 접근 가능

‎src/main/java/com/rest/api/controller/HelloController.java

+9-3
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22

33
import lombok.Getter;
44
import lombok.Setter;
5+
import lombok.extern.slf4j.Slf4j;
56
import org.springframework.stereotype.Controller;
67
import org.springframework.web.bind.annotation.GetMapping;
78
import org.springframework.web.bind.annotation.ResponseBody;
89

10+
@Slf4j
911
@Controller
1012
public class HelloController {
1113

14+
private static final String HELLO = "helloworld";
15+
1216
@Setter
1317
@Getter
1418
public static class Hello {
@@ -18,19 +22,21 @@ public static class Hello {
1822
@GetMapping(value = "/helloworld/string")
1923
@ResponseBody
2024
public String helloworldString() {
21-
return "helloworld";
25+
log.debug("Helloworld");
26+
log.info("Helloworld");
27+
return HELLO;
2228
}
2329

2430
@GetMapping(value = "/helloworld/json")
2531
@ResponseBody
2632
public Hello helloworldJson() {
2733
Hello hello = new Hello();
28-
hello.message = "helloworld";
34+
hello.message = HELLO;
2935
return hello;
3036
}
3137

3238
@GetMapping(value = "/helloworld/page")
3339
public String helloworld() {
34-
return "helloworld";
40+
return HELLO;
3541
}
3642
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.rest.api.controller.common;
2+
3+
import com.google.gson.Gson;
4+
import com.rest.api.service.social.KakaoService;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.beans.factory.annotation.Value;
7+
import org.springframework.core.env.Environment;
8+
import org.springframework.stereotype.Controller;
9+
import org.springframework.web.bind.annotation.GetMapping;
10+
import org.springframework.web.bind.annotation.RequestMapping;
11+
import org.springframework.web.bind.annotation.RequestParam;
12+
import org.springframework.web.client.RestTemplate;
13+
import org.springframework.web.servlet.ModelAndView;
14+
15+
@RequiredArgsConstructor
16+
@Controller
17+
@RequestMapping("/social/login")
18+
public class SocialController {
19+
20+
private final Environment env;
21+
private final RestTemplate restTemplate;
22+
private final Gson gson;
23+
private final KakaoService kakaoService;
24+
25+
@Value("${spring.url.base}")
26+
private String baseUrl;
27+
28+
@Value("${spring.social.kakao.client_id}")
29+
private String kakaoClientId;
30+
31+
@Value("${spring.social.kakao.redirect}")
32+
private String kakaoRedirect;
33+
34+
/**
35+
* 카카오 로그인 페이지
36+
*/
37+
@GetMapping
38+
public ModelAndView socialLogin(ModelAndView mav) {
39+
40+
StringBuilder loginUrl = new StringBuilder()
41+
.append(env.getProperty("spring.social.kakao.url.login"))
42+
.append("?client_id=").append(kakaoClientId)
43+
.append("&response_type=code")
44+
.append("&redirect_uri=").append(baseUrl).append(kakaoRedirect);
45+
46+
mav.addObject("loginUrl", loginUrl);
47+
mav.setViewName("social/login");
48+
return mav;
49+
}
50+
51+
/**
52+
* 카카오 인증 완료 후 리다이렉트 화면
53+
*/
54+
@GetMapping(value = "/kakao")
55+
public ModelAndView redirectKakao(ModelAndView mav, @RequestParam String code) {
56+
mav.addObject("authInfo", kakaoService.getKakaoTokenInfo(code));
57+
mav.setViewName("social/redirectKakao");
58+
return mav;
59+
}
60+
}

0 commit comments

Comments
 (0)