Skip to content

Commit

Permalink
Merge pull request #21 from jenny0325/main
Browse files Browse the repository at this point in the history
JWT 회원가입(#18), 로그인(#20) / 카카오톡 로그인 기능(OAuth2)(#19)
  • Loading branch information
jenny0325 authored Nov 26, 2021
2 parents b8bac86 + 98e8374 commit 0b97a66
Show file tree
Hide file tree
Showing 27 changed files with 1,440 additions and 3 deletions.
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation group: 'org.modelmapper', name: 'modelmapper', version: '2.4.2' //model mapper

implementation 'org.json:json:20210307'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt:0.9.1'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:3.0.4'


// aws
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.0.1.RELEASE'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,9 @@ public String getPagePostList(){
public String getPagePosting(){
return "articles/post_upload";
}

@GetMapping("/user/login")
public String login() {
return "login";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.example.marumaru_sparta_verspring.controller;

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

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

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {

response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.example.marumaru_sparta_verspring.controller;

import com.example.marumaru_sparta_verspring.util.JwtTokenUtil;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.SignatureException;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
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;
import java.util.Arrays;

@RequiredArgsConstructor
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final UserDetailsService userDetailsService;
private final JwtTokenUtil jwtTokenUtil;

String HEADER_STRING = "Authorization";
String TOKEN_PREFIX = "Bearer ";

@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
String header = req.getHeader(HEADER_STRING);
String username = null;
String authToken = null;
if (header != null && header.startsWith(TOKEN_PREFIX)) {
authToken = header.replace(TOKEN_PREFIX,"");
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
} catch (IllegalArgumentException e) {
logger.error("an error occured during getting username from token", e);
} catch (ExpiredJwtException e) {
logger.warn("the token is expired and not valid anymore", e);
} catch(SignatureException e){
logger.error("Authentication Failed. Username or Password not valid.");
}
} else {
logger.warn("couldn't find bearer string, will ignore the header");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

UserDetails userDetails = userDetailsService.loadUserByUsername(username);

if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));
logger.info("authenticated user " + username + ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}

chain.doFilter(req, res);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.example.marumaru_sparta_verspring.controller;

import com.example.marumaru_sparta_verspring.dto.user.JwtResponse;
import com.example.marumaru_sparta_verspring.dto.user.SignupRequestDto;
import com.example.marumaru_sparta_verspring.dto.user.SocialLoginDto;
import com.example.marumaru_sparta_verspring.dto.user.UserDto;
import com.example.marumaru_sparta_verspring.repository.UserRepository;
import com.example.marumaru_sparta_verspring.service.UserService;
import com.example.marumaru_sparta_verspring.util.JwtTokenUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.*;

@RequiredArgsConstructor
@RestController
public class UserApiController {

private final JwtTokenUtil jwtTokenUtil;
private final AuthenticationManager authenticationManager;
private final UserDetailsService userDetailsService;
private final UserService userService;
private final UserRepository userRepository;


@RequestMapping(value = "/login", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(@RequestBody UserDto userDto) throws Exception{
authenticate(userDto.getUsername(), userDto.getPassword());
final UserDetails userDetails = userDetailsService.loadUserByUsername(userDto.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token, userDetails.getUsername()));
}

@PostMapping(value = "/login/kakao")
public ResponseEntity<?> createAuthenticationTokenByKakao(@RequestBody SocialLoginDto socialLoginDto) throws Exception {
String username = userService.kakaoLogin(socialLoginDto.getToken());
final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token, userDetails.getUsername()));
}

@PostMapping(value = "/signup")
public ResponseEntity<?> createUser(@RequestBody SignupRequestDto userDto) throws Exception {
userService.registerUser(userDto);
authenticate(userDto.getUsername(), userDto.getPassword());
final UserDetails userDetails = userDetailsService.loadUserByUsername(userDto.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token, userDetails.getUsername()));
}

@PostMapping("/signup/check-dup")
public String checkUsername(@RequestBody UserDto userDto) {
String exists;
try {
userService.checkExist(userDto);
} catch (IllegalArgumentException e) {
exists = "true";
return exists;
}
exists = "FALSE";
return exists;
}

private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.example.marumaru_sparta_verspring.domain.user;

import com.example.marumaru_sparta_verspring.domain.Timestamped;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;

@Getter
@Setter
@NoArgsConstructor // 기본 생성자를 만들어줍니다.
@Entity // DB 테이블 역할을 합니다.
public class User extends Timestamped {

public User(String username, String password, UserRole role) {
this.username = username;
this.password = password;
this.role = role;
}

public User(String username, String password, Long kakaoId, UserRole role) {
this.username = username;
this.password = password;
this.role = role;
this.kakaoId = kakaoId;
}

// ID가 자동으로 생성 및 증가합니다.
@GeneratedValue(strategy = GenerationType.AUTO)
@Id
private Long id;

// 반드시 값을 가지도록 합니다.
@Column(nullable = false)
private String username;

@Column(nullable = false)
private String password;

@Column(nullable = true)
private String nickname;

@Column(nullable = true)
private Long kakaoId;

@Column(nullable = false)
@Enumerated(value = EnumType.STRING)
private UserRole role;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.example.marumaru_sparta_verspring.domain.user;

public enum UserRole {
USER
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.marumaru_sparta_verspring.dto.user;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Getter
public class JwtResponse {
private final String token;
private final String username;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.marumaru_sparta_verspring.dto.user;

import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public class SignupRequestDto {
private String username;
private String password;
private boolean admin = false;
private String adminToken = "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example.marumaru_sparta_verspring.dto.user;

import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public class SocialLoginDto {
private String token;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.marumaru_sparta_verspring.dto.user;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class UserDto {
private String username;
private String password;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.marumaru_sparta_verspring.repository;


import com.example.marumaru_sparta_verspring.domain.user.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByKakaoId(Long kakaoId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.example.marumaru_sparta_verspring.security;

import com.example.marumaru_sparta_verspring.domain.user.User;
import com.example.marumaru_sparta_verspring.domain.user.UserRole;
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 UserDetailsImpl implements UserDetails {

private final User user;
private static final String ROLE_PREFIX = "ROLE_";

public UserDetailsImpl(User user) {
this.user = user;
}

public User getUser() {
return user;
}

@Override
public String getPassword() {
return user.getPassword();
}

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

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

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

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

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

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
UserRole userRole = user.getRole();

SimpleGrantedAuthority authority = new SimpleGrantedAuthority(ROLE_PREFIX + userRole.toString());
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(authority);

return authorities;
}
}
Loading

0 comments on commit 0b97a66

Please sign in to comment.