Skip to content
This repository has been archived by the owner on Aug 13, 2022. It is now read-only.

#1 인증 토큰 spring security, JWT #5

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
68 changes: 68 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>com.pay</groupId>
<artifactId>billing</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>billing</name>
<description>billing project for Spring Boot</description>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<start-class>com.pay.billing.BillingApplication</start-class>
</properties>

<dependencies>
Expand All @@ -29,11 +34,74 @@
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<!-- Starter for using Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- Make method based security testing easier -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

<!-- JSON Web Token Support -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

<!-- JPA Data (Repositories, Entities, Hibernate, etc..) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- Model Mapper -->
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.5</version>
</dependency>

<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>

</dependencies>

<build>
Expand Down
38 changes: 37 additions & 1 deletion src/main/java/com/pay/billing/BillingApplication.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,49 @@
package com.pay.billing;

import com.pay.billing.domain.model.Role;
import com.pay.billing.domain.model.User;
import com.pay.billing.service.UserService;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.ArrayList;
import java.util.Arrays;

@SpringBootApplication
public class BillingApplication {
public class BillingApplication implements CommandLineRunner {

@Autowired
UserService userService;

public static void main(String[] args) {
SpringApplication.run(BillingApplication.class, args);
}

@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}

@Override
public void run(String... params) throws Exception {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거는 왜 넣어주신건가요?

User admin = new User();
admin.setUsername("admin");
admin.setPassword("admin");
admin.setEmail("admin@email.com");
admin.setRoles(new ArrayList<Role>(Arrays.asList(Role.ROLE_ADMIN)));

userService.signup(admin);

User client = new User();
client.setUsername("client");
client.setPassword("client");
client.setEmail("client@email.com");
client.setRoles(new ArrayList<Role>(Arrays.asList(Role.ROLE_CLIENT)));

userService.signup(client);
}
}
64 changes: 64 additions & 0 deletions src/main/java/com/pay/billing/common/config/SwaggerConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.pay.billing.common.config;

import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.Tag;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket swaggerApi() {
return new Docket(DocumentationType.SWAGGER_2)//
.select()//
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기 주석은 복붙해온 소스의 주석을 지우다가 남아있는걸까요?

.apis(RequestHandlerSelectors.any())//
.paths(Predicates.not(PathSelectors.regex("/error")))//
.build()//
.apiInfo(swaggerInfo())
.useDefaultResponseMessages(false)//
.securitySchemes(Collections.singletonList(apiKey()))
.securityContexts(Collections.singletonList(securityContext()))
.tags(new Tag("users", "Operations about users"))//
.genericModelSubstitutes(Optional.class);
}

private ApiInfo swaggerInfo() {
return new ApiInfoBuilder().title("Spring API Documentation")
.description("앱 개발시 사용되는 서버 API에 대한 연동 문서입니다").build();
}

private ApiKey apiKey() {
return new ApiKey("Authorization", "Authorization", "header");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런것들은 어떤 의도로 추가하신건지 주석이 필요하네요

}

private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.any())
.build();
}

private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
return Arrays.asList(new SecurityReference("Authorization", authorizationScopes));
}
}
80 changes: 80 additions & 0 deletions src/main/java/com/pay/billing/common/config/WebSecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.pay.billing.common.config;

import com.pay.billing.common.security.JwtTokenFilterConfigurer;
import com.pay.billing.common.security.JwtTokenProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private JwtTokenProvider jwtTokenProvider;

@Override
protected void configure(HttpSecurity http) throws Exception {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 메소드에 있는 전체 설정 한줄한줄에 대한 의도를 설명이 가능하실까요?


// Disable CSRF (cross site request forgery)
http.csrf().disable();

// No session will be created or used by spring security
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

// Entry points
http.authorizeRequests()//
.antMatchers("/users/signin").permitAll()//
.antMatchers("/users/signup").permitAll()//
.antMatchers("/h2-console/**/**").permitAll()
// Disallow everything else..
.anyRequest().authenticated();

// If a user try to access a resource without having enough permissions
http.exceptionHandling().accessDeniedPage("/login");

// Apply JWT
http.apply(new JwtTokenFilterConfigurer(jwtTokenProvider));

// Optional, if you want to test the API from a browser
// http.httpBasic();
}

@Override
public void configure(WebSecurity web) throws Exception {
// Allow swagger to be accessed without authentication
web.ignoring().antMatchers("/v2/api-docs")//
.antMatchers("/swagger-resources/**")//
.antMatchers("/swagger-ui.html")//
.antMatchers("/configuration/**")//
.antMatchers("/webjars/**")//
.antMatchers("/public")

// Un-secure H2 Database (for testing purposes, H2 console shouldn't be unprotected in production)
.and()
.ignoring()
.antMatchers("/h2-console/**/**");;
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}

@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기 들여쓰기가 다른 클래스들의 들여쓰기와 다른 것을 보니 이것 또한 베껴온 소스의 느낌이 나네요. 스스로 작성한 코드가 아니면 리뷰를 받아도 그 리뷰내용을 암기만 할뿐 이해가 되지 않기 때문에 리뷰의 의미가 없어집니다

return super.authenticationManagerBean();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.pay.billing.common.exception;

import org.springframework.http.HttpStatus;

public class CustomException extends RuntimeException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CustomException이라는 이름은 어떤곳에 쓰이는지 명확하게 보이지 않습니다.


private static final long serialVersionUID = 1L;

private final String message;
private final HttpStatus httpStatus;

public CustomException(String message, HttpStatus httpStatus) {
this.message = message;
this.httpStatus = httpStatus;
}

@Override
public String getMessage() {
return message;
}

public HttpStatus getHttpStatus() {
return httpStatus;
}

}
41 changes: 41 additions & 0 deletions src/main/java/com/pay/billing/common/security/JwtTokenFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.pay.billing.common.security;

import com.pay.billing.common.exception.CustomException;
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;

// We should use OncePerRequestFilter since we are doing a database call, there is no point in doing this more than once
public class JwtTokenFilter extends OncePerRequestFilter {

private JwtTokenProvider jwtTokenProvider;

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

@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String token = jwtTokenProvider.resolveToken(httpServletRequest);
try {
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (CustomException ex) {
//this is very important, since it guarantees the user is not authenticated at all
SecurityContextHolder.clearContext();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 clearContext를 해줘야하는 이유가 무엇인가요?

httpServletResponse.sendError(ex.getHttpStatus().value(), ex.getMessage());
return;
}

filterChain.doFilter(httpServletRequest, httpServletResponse);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.pay.billing.common.security;

import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

public class JwtTokenFilterConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

private JwtTokenProvider jwtTokenProvider;

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

@Override
public void configure(HttpSecurity http) throws Exception {
JwtTokenFilter customFilter = new JwtTokenFilter(jwtTokenProvider);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}

}
Loading