Skip to content

Commit

Permalink
Merge pull request #83 from yuus95/spring-security
Browse files Browse the repository at this point in the history
Spring security 설정을 구현했습니다. (작업중)
  • Loading branch information
hannut91 committed Jun 13, 2023
2 parents 274cd69 + bc1cc74 commit 8d91bb9
Show file tree
Hide file tree
Showing 31 changed files with 21,664 additions and 8,352 deletions.
3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ dependencies {
// Spring Validation
implementation 'org.springframework.boot:spring-boot-starter-validation'

// Spring Security
implementation 'org.springframework.boot:spring-boot-starter-security'

// Spring Data JPA
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.codesoom.assignment.domain.User;
import com.codesoom.assignment.domain.UserRepository;
import com.codesoom.assignment.domain.UserType;
import com.codesoom.assignment.dto.ClaimData;
import com.codesoom.assignment.errors.LoginFailException;
import com.codesoom.assignment.utils.JwtUtil;
import io.jsonwebtoken.Claims;
Expand All @@ -26,11 +28,13 @@ public String login(String email, String password) {
throw new LoginFailException(email);
}

return jwtUtil.encode(1L);
return jwtUtil.encode(user.getId(), user.getUserType());
}

public Long parseToken(String accessToken) {
public ClaimData parseToken(String accessToken) {
Claims claims = jwtUtil.decode(accessToken);
return claims.get("userId", Long.class);
Long userId = claims.get("userId", Long.class);
UserType userType = UserType.valueOf(claims.get("userType", String.class));
return new ClaimData(userId, userType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import com.codesoom.assignment.errors.UserEmailDuplicationException;
import com.codesoom.assignment.errors.UserNotFoundException;
import com.github.dozermapper.core.Mapper;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
Expand All @@ -27,9 +29,10 @@ public User registerUser(UserRegistrationData registrationData) {
if (userRepository.existsByEmail(email)) {
throw new UserEmailDuplicationException(email);
}

User user = mapper.map(registrationData, User.class);
return userRepository.save(user);
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
User user = userRepository.save(mapper.map(registrationData, User.class));
user.changePassword(user.getPassword(), passwordEncoder);
return user;
}

public User updateUser(Long id, UserModificationData modificationData) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.codesoom.assignment.authetication;

import com.codesoom.assignment.domain.UserType;
import lombok.Getter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import java.util.ArrayList;
import java.util.List;

@Getter
public class UserAuthentication extends AbstractAuthenticationToken {
private final Long userId;
private final UserType userType;

public UserAuthentication(Long userId, UserType userType) {
super(authorities(userType));
this.userId = userId;
this.userType = userType;
}

@Override
public Object getCredentials() {
return null;
}

/**
* Used to indicate to AbstractSecurityInterceptor whether it should present the authentication token to the AuthenticationManager.
*
* @return
*/
@Override
public boolean isAuthenticated() {
return true;
}

/**
* userId 같이 인증할 떄 들어오는 아이디 같은걸 반환한다.
* 대부분 userDetail을 사용한다 pricipal을 위해서
*
* @return
*/
@Override
public Object getPrincipal() {
return userId;
}

@Override
public String toString() {
return "UserAuthentication{" +
"userId=" + userId +
'}';
}

private static List<? extends GrantedAuthority> authorities(UserType userType) {
List<GrantedAuthority> authorities = new ArrayList<>();
//TODO userID에 따라서 유저 권한 나눠주기

authorities.add(new SimpleGrantedAuthority(userType.toString()));
return authorities;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.codesoom.assignment.config;

import com.codesoom.assignment.application.AuthenticationService;
import com.codesoom.assignment.filter.BeforeAuthentication;
import com.codesoom.assignment.filter.JwtAuthentication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;

import javax.servlet.Filter;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationService authenticationService;

public SecurityJavaConfig() {
}

@Override
protected void configure(HttpSecurity http) throws Exception {
Filter jwtAuthentication = new JwtAuthentication(authenticationManager(), authenticationService);
Filter beforeAuthentication = new BeforeAuthentication();

http.csrf()
.disable()
.addFilterBefore(beforeAuthentication, JwtAuthentication.class)
.addFilter(jwtAuthentication)
.exceptionHandling()
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

import com.codesoom.assignment.application.AuthenticationService;
import com.codesoom.assignment.application.ProductService;
import com.codesoom.assignment.authetication.UserAuthentication;
import com.codesoom.assignment.domain.Product;
import com.codesoom.assignment.dto.ProductData;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
Expand Down Expand Up @@ -40,16 +42,18 @@ public Product detail(@PathVariable Long id) {

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
@PreAuthorize("isAuthenticated() and hasAuthority('USER')")
public Product create(
@RequestAttribute Long userId,
UserAuthentication authentication,
@RequestBody @Valid ProductData productData
) {
return productService.createProduct(productData);
}

@PatchMapping("{id}")
@PreAuthorize("isAuthenticated()")
public Product update(
@RequestAttribute Long userId,
UserAuthentication authentication,
@PathVariable Long id,
@RequestBody @Valid ProductData productData
) {
Expand Down
42 changes: 32 additions & 10 deletions app/src/main/java/com/codesoom/assignment/domain/User.java
Original file line number Diff line number Diff line change
@@ -1,43 +1,65 @@
/**
* 복호화가 불가능한 암호화 : 123 => 6
* Hash => data -> n-bit
* Hash Table (key ->(string) -> integer(32-bits)) => Hash 충돌
* 123 -> 6
*/

package com.codesoom.assignment.domain;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.*;

@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue
private Long id;

private String email;
@Builder.Default
private String email = "";

private String name;
@Builder.Default
private String name = "";

private String password;
@Builder.Default
private String password = "";

@Enumerated(value = EnumType.STRING)
private UserType userType;
@Builder.Default
private boolean deleted = false;

public User() {
this.email = "";
this.name = "";
this.password = "";
}

public void changeWith(User source) {
name = source.name;
password = source.password;
}

public void destroy() {
deleted = true;
}

public boolean authenticate(String password) {
return !deleted && password.equals(this.password);
public boolean authenticate(String password,
PasswordEncoder passwordEncoder) {
return !deleted && passwordEncoder.matches(password, this.password);
}

public void changePassword(String password,
PasswordEncoder passwordEncoder) {
this.password = passwordEncoder.encode(password);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.codesoom.assignment.domain;

public enum UserType {
ADMIN,USER
}
15 changes: 15 additions & 0 deletions app/src/main/java/com/codesoom/assignment/dto/ClaimData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.codesoom.assignment.dto;

import com.codesoom.assignment.domain.UserType;
import lombok.Getter;

@Getter
public class ClaimData {
private Long userId;
private UserType userType;

public ClaimData(Long userId, UserType userType) {
this.userId = userId;
this.userType = userType;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.codesoom.assignment.filter;


import com.codesoom.assignment.errors.InvalidTokenException;
import org.springframework.http.HttpStatus;

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

public class BeforeAuthentication extends HttpFilter {

@Override
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {

try {
chain.doFilter(request, response);
} catch (InvalidTokenException e) {
response.sendError(HttpStatus.UNAUTHORIZED.value());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.codesoom.assignment.filter;

import com.codesoom.assignment.application.AuthenticationService;
import com.codesoom.assignment.authetication.UserAuthentication;
import com.codesoom.assignment.domain.UserType;
import com.codesoom.assignment.dto.ClaimData;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

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


/**
* Filter 를 대신 BasicAuthenticationFilter 사용
* 기본적인 내용을 구성해놨음
*/
public class JwtAuthentication extends BasicAuthenticationFilter {
private final AuthenticationService authenticationService;

public JwtAuthentication(AuthenticationManager authenticationManager, AuthenticationService authenticationService) {
super(authenticationManager);
this.authenticationService = authenticationService;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String authorization = request.getHeader("Authorization");

if (authorization != null) {
String accessToken = authorization.substring("Bearer ".length());
ClaimData claimData = authenticationService.parseToken(accessToken);
Long userId = claimData.getUserId();
UserType userType = claimData.getUserType();
request.setAttribute("userId", userId);

// 인증
Authentication authentication = new UserAuthentication(userId,userType);
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(authentication);
}

// 다음 필터에게 전달한다
chain.doFilter(request, response);
}
}
Loading

0 comments on commit 8d91bb9

Please sign in to comment.