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: spring security 의존성, 클래스 추가 #17

Merged
merged 1 commit into from
Aug 7, 2022
Merged
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
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.8.0'

implementation group: 'com.auth0', name: 'java-jwt', version: '3.19.2'

implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-test'

runtimeOnly 'com.h2database:h2'

compileOnly 'org.projectlombok:lombok'
Expand Down
43 changes: 43 additions & 0 deletions src/main/java/com/mergedoc/backend/config/SecurityConfigure.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.mergedoc.backend.config;

import com.mergedoc.backend.security.filter.JwtAuthenticationFilter;
import com.mergedoc.backend.security.provider.JwtProvider;
import com.mergedoc.backend.utils.cookie.CookieUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@EnableWebSecurity
public class SecurityConfigure {

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

public JwtAuthenticationFilter jwtAuthenticationFilter(JwtProvider provider, CookieUtil cookieUtil) {
return new JwtAuthenticationFilter(provider, cookieUtil);
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http,
JwtProvider jwtProvider,
CookieUtil cookieUtil) throws Exception{
return http
.httpBasic().disable()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/**").permitAll()
.and()
.addFilterBefore(jwtAuthenticationFilter(jwtProvider, cookieUtil),
UsernamePasswordAuthenticationFilter.class)
.build();
}
}
7 changes: 7 additions & 0 deletions src/main/java/com/mergedoc/backend/member/entity/Role.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.mergedoc.backend.member.entity;

import com.fasterxml.jackson.annotation.JsonCreator;

public enum Role {
ROLE_NOT_ASSIGNED, ROLE_USER, ROLE,MANAGER
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.mergedoc.backend.security.details;

import com.mergedoc.backend.member.entity.Member;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;

public class MemberDetails extends User {

public MemberDetails(Member member) {
super(member.getEmail(),
member.getPasswd(),
AuthorityUtils.createAuthorityList(member.getRole().toString()));
}

public String getEmail() {
return this.getUsername();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.mergedoc.backend.security.details;

import com.mergedoc.backend.member.entity.Member;
import com.mergedoc.backend.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
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.Service;

@Service
@RequiredArgsConstructor
public class MemberDetailsService implements UserDetailsService {

private final MemberRepository memberRepository;

@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Member member = memberRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("계정 정보가 올바르지 않습니다."));

return new MemberDetails(member);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.mergedoc.backend.security.filter;

import com.mergedoc.backend.security.provider.JwtProvider;
import com.mergedoc.backend.utils.cookie.CookieUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

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

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends GenericFilterBean {

private final JwtProvider jwtProvider;
private final CookieUtil cookieUtil;

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String token = null;
Authentication authenticate;

HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;

Cookie memberTokenCookie = cookieUtil.getCookie(req, "member_token");

if (memberTokenCookie != null) {
token = memberTokenCookie.getValue();
}

if (token != null && !jwtProvider.isTokenExpired(token)) {
try {
String emailFromToken = jwtProvider.getEmailFromToken(token);
authenticate = jwtProvider.
authenticate(new UsernamePasswordAuthenticationToken(emailFromToken, ""));
SecurityContextHolder.getContext().setAuthentication(authenticate);
} catch (Exception e) {
}
}
chain.doFilter(request, response);
}
}
100 changes: 100 additions & 0 deletions src/main/java/com/mergedoc/backend/security/provider/JwtProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package com.mergedoc.backend.security.provider;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.mergedoc.backend.security.details.MemberDetails;
import com.mergedoc.backend.security.details.MemberDetailsService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.Map;

@Component
@RequiredArgsConstructor
public class JwtProvider implements AuthenticationProvider {

private final MemberDetailsService memberDetailsService;

private static final long TOKEN_VALIDATION_SECOND = 1000L * 60 * 120;
private static final long REFRESH_TOKEN_VALIDATION_TIME = 1000L * 60 * 60 * 48;

@Value("${spring.jwt.secret}")
private String SECRET_KEY;

@Value("${group.name}")
private String ISSUER;

private Algorithm getSigningKey(String secretKey) {
return Algorithm.HMAC256(secretKey);
}

public String getEmailFromToken(String token) {
DecodedJWT verifiedToken = validateToken(token);
return verifiedToken.getClaim("email").asString();
}

private JWTVerifier getTokenValidator() {
return JWT.require(getSigningKey(SECRET_KEY))
.withIssuer(ISSUER)
.build();
}

public String generateToken(Map<String, String> payload) {
return doGenerateToken(TOKEN_VALIDATION_SECOND, payload);
}

public String generateRefreshToken(Map<String, String> payload) {
return doGenerateToken(REFRESH_TOKEN_VALIDATION_TIME, payload);
}

private String doGenerateToken(long expireTime, Map<String, String> payload) {

return JWT.create()
.withIssuedAt(new Date(System.currentTimeMillis()))
.withExpiresAt(new Date(System.currentTimeMillis() + expireTime))
.withPayload(payload)
.withIssuer(ISSUER)
.sign(getSigningKey(SECRET_KEY));
}

private DecodedJWT validateToken(String token) throws JWTVerificationException {
JWTVerifier validator = getTokenValidator();
return validator.verify(token);
}

public boolean isTokenExpired(String token) {
try {
DecodedJWT decodedJWT = validateToken(token);
return false;
} catch (JWTVerificationException e) {
return true;
}
}

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
MemberDetails userDetails = (MemberDetails) memberDetailsService
.loadUserByUsername((String) authentication.getPrincipal());

return new UsernamePasswordAuthenticationToken(
userDetails.getEmail(),
userDetails.getPassword(),
userDetails.getAuthorities()
);
}

@Override
public boolean supports(Class<?> authentication) {
return false;
}
}
39 changes: 39 additions & 0 deletions src/main/java/com/mergedoc/backend/utils/cookie/CookieUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.mergedoc.backend.utils.cookie;

import org.springframework.stereotype.Component;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

@Component
public class CookieUtil {

private final int COOKIE_VALIDATION_SECOND = 1000 * 60 * 60 * 48;

public Cookie createCookie(String name, String value, int maxAge) {
Cookie cookie = new Cookie(name, value);
cookie.setHttpOnly(true);
cookie.setPath("/");
cookie.setMaxAge(maxAge);

return cookie;
}

public Cookie createCookie(String name, String value) {
Cookie cookie = new Cookie(name, value);
cookie.setHttpOnly(true);
cookie.setPath("/");
cookie.setMaxAge(COOKIE_VALIDATION_SECOND);

return cookie;
}

public Cookie getCookie(HttpServletRequest req, String name) {
Cookie[] findCookies = req.getCookies();
if (findCookies == null) return null;
for (Cookie cookie : findCookies) {
if (cookie.getName().equals(name)) return cookie;
}
return null;
}
}