Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEAT: 9주차 JWT 적용 #21

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
60 changes: 60 additions & 0 deletions 달리_강현욱/9주차 미션/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.umcblog.Blog.config;

import com.umcblog.Blog.jwt.JwtTokenProvider;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtTokenProvider tokenProvider;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;


public SecurityConfig(JwtTokenProvider tokenProvider, JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint, JwtAccessDeniedHandler jwtAccessDeniedHandler) {
this.tokenProvider = tokenProvider;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception{
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http)throws Exception{
http
.httpBasic().disable()
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests()
.requestMatchers("/signup").permitAll()
.requestMatchers("/login").permitAll()
.requestMatchers("/post").permitAll()
.and()
.apply(new JwtSecurityConfig(tokenProvider));
return http.build();
}
}
18 changes: 18 additions & 0 deletions 달리_강현욱/9주차 미션/dto/MemberDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.umcblog.Blog.dto;

import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;

@Data
@RequiredArgsConstructor
@Getter
@Setter
public class MemberDto {
private String user_id;
private String password;



}
30 changes: 30 additions & 0 deletions 달리_강현욱/9주차 미션/entity/Member.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.umcblog.Blog.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.*;

@Setter
@Getter
@NoArgsConstructor
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private String user_id;
@JsonIgnore
private String password;
private String name;
private String phone;
@Builder
public Member(String user_id, String password, String name, String phone){
this.user_id = user_id;
this.password = password;
this.name = name;
this.phone = phone;

}
}
15 changes: 15 additions & 0 deletions 달리_강현욱/9주차 미션/jwt/JwtAccessDeniedHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.umcblog.Blog.jwt;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.web.access.AccessDeniedHandler;

import java.io.IOException;

public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN);

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.umcblog.Blog.jwt;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
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) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}

}
52 changes: 52 additions & 0 deletions 달리_강현욱/9주차 미션/jwt/JwtFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.umcblog.Blog.jwt;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
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.GenericFilterBean;
import java.io.IOException;

@Slf4j
@Component
public class JwtFilter extends GenericFilterBean {

public static final String AUTHORIZATION_HEADER = "Authorization";
private final TokenProvider tokenProvider;

public JwtFilter(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException, ServletException, IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String jwt = resolveToken(httpServletRequest);
String requestURI = httpServletRequest.getRequestURI();

if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
Authentication authentication = tokenProvider.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
log.info("Security Context에 '{}' 인증 정보 저장, uri = {}", authentication.getName(), requestURI);
} else {
log.info(jwt);
log.info("유효한 JWT 토큰 없음, uri = {}", requestURI);
// throw new TargetNotFoundException("유저가 식별되지 않았습니다.");
}
chain.doFilter(request, response);
}

private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
21 changes: 21 additions & 0 deletions 달리_강현욱/9주차 미션/jwt/JwtSecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.umcblog.Blog.jwt;

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 JwtSecurityConfigextendsSecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

privateTokenProvider tokenProvider;

public JwtSecurityConfig(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}

@Override
public void configure(HttpSecurity http) {
JwtFilter customFilter =new JwtFilter(tokenProvider);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
96 changes: 96 additions & 0 deletions 달리_강현욱/9주차 미션/jwt/JwtTokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.umcblog.Blog.jwt;


import com.umcblog.Blog.entity.Member;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
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.security.core.userdetails.User;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.stream.Collectors;

@Slf4j
@Component
@PropertySource("classpath:application.yml")
public class TokenProvider implements InitializingBean {

private static final String AUTHORITIES_KEY = "auth";
private final String secret;
private final long tokenValidityInMilliseconds;

private Key key;

public TokenProvider(
@Value("${jwt.secret}") String secret,
@Value("${jwt.token-validity-in-seconds}") long tokenValidityInMilliseconds) {
this.secret = secret;
this.tokenValidityInMilliseconds = tokenValidityInMilliseconds * 1000;
}

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

public String createToken(Authentication authentication, Member member) {
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
long now = (new Date()).getTime();
Date validity = new Date(now + this.tokenValidityInMilliseconds);

return Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.claim("user_id", member.getId())
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(validity)
.compact();
}

public Authentication getAuthentication(String token) {
Claims claims = Jwts
.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);

return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}

public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
log.info("잘못된 JWT 서명");
} catch (ExpiredJwtException e) {
log.info("만료된 JWT 서명");
} catch (UnsupportedJwtException e) {
log.info("지원되지 않는 JWT 토큰");
} catch (IllegalArgumentException e) {
log.info("JWT 토큰이 잘못됨");
}
return false;
}
}
10 changes: 10 additions & 0 deletions 달리_강현욱/9주차 미션/repository/MemberRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.umcblog.Blog.repository;

import com.umcblog.Blog.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface MemberRepository extends JpaRepository<Member,Long> {
Optional<Member> findByUser_id(String user_id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.umcblog.Blog.service;

import com.umcblog.Blog.entity.Member;
import com.umcblog.Blog.repository.MemberRepository;
import jakarta.transaction.Transactional;

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.ArrayList;

@Component("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {

private final MemberRepository memberRepository;

public CustomUserDetailsService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}

@Override
@Transactional
public UserDetails loadUserByUsername(String username)throws UsernameNotFoundException {
return memberRepository.findByUser_id(username)
.map(user -> createUser(username, user))
.orElseThrow(() ->new UsernameNotFoundException(username + " -> 데이터베이스에서 찾을 수 없습니다."));
}

private org.springframework.security.core.userdetails.User createUser(String username, Member member) {
return new org.springframework.security.core.userdetails.User(username,
member.getPassword(),
new ArrayList<>());
}
}
Loading