Skip to content

Commit

Permalink
Feat: Jwt token 기능
Browse files Browse the repository at this point in the history
- Jwt 토큰 생성 및 검증
- Jwt 토큰에서 사용자 정보 가져오기
- Jwt 인증 필터 생성
- Jwt 인증 필터 WebSecurityConfig에 등록
- UserDetails 관련 생성
- 테스트코드 Security 수정
  • Loading branch information
alertjjm committed Jul 17, 2021
1 parent 9a28456 commit 0b20a28
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 14 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
implementation 'com.auth0:java-jwt:3.4.1'
implementation 'org.hibernate:hibernate-validator:6.1.0.Final'
implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'
}

test {
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/com/meme/ala/core/auth/jwt/Authority.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.meme.ala.core.auth.jwt;

import lombok.NoArgsConstructor;

@NoArgsConstructor
public class Authority {
public static final String ROLE_USER="ROLE_USER";
public static final String ROLE_ADMIN="ROLE_ADMIN";
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
package com.meme.ala.core.auth.jwt;

public class JwtAuthenticationFilter {
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

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

@RequiredArgsConstructor
public class JwtAuthenticationFilter implements Filter {
private final JwtTokenProvider jwtTokenProvider;

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication authentication = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
71 changes: 71 additions & 0 deletions src/main/java/com/meme/ala/core/auth/jwt/JwtTokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.meme.ala.core.auth.jwt;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.util.Base64;
import java.util.Date;

@RequiredArgsConstructor
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String secretKey;
private long tokenValidTime = 30 * 60 * 1000L;
private final UserDetailsService userDetailsService;

@PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}

public String createToken(String email){
Date now=new Date();
Date expirationDate=new Date(now.getTime()+tokenValidTime);
Claims claims= Jwts.claims().setSubject("alajwt");
claims.put("email",email);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS256,secretKey)
.compact();
}

public Authentication getAuthentication(String token) {
UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUserEmail(token));
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}

public String getUserEmail(String token){
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody()
.get("email",String.class);
}

public String resolveToken(HttpServletRequest request){
return request.getHeader("X-AUTH_TOKEN");
}

public boolean validateToken(String jwtToken){
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtToken);
return !claims.getBody().getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.meme.ala.core.auth.jwt;

import com.meme.ala.domain.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
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;

@RequiredArgsConstructor
@Service
public class UserDetailServiceImpl implements UserDetailsService {
private final MemberRepository memberRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//TODO: 21.07.18 orElseThrow로 EntityNotFoundException::new 불가하여 null로 설정함
return new UserDetailsImpl(memberRepository.findByEmail(username).orElse(null));
}
}
56 changes: 56 additions & 0 deletions src/main/java/com/meme/ala/core/auth/jwt/UserDetailsImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.meme.ala.core.auth.jwt;

import com.meme.ala.domain.member.model.entity.Member;
import lombok.Data;
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;

@Data
public class UserDetailsImpl implements UserDetails {
private Member member;

public UserDetailsImpl(Member member){
this.member=member;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities=new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(member.getAuthority()));
return authorities;
}

@Override
public String getPassword() {
return null;
}

@Override
public String getUsername() {
return member.getMemberSetting().getNickname();
}

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

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

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

@Override
public boolean isEnabled() {
return true;
}
}
43 changes: 42 additions & 1 deletion src/main/java/com/meme/ala/core/config/WebSecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,45 @@
package com.meme.ala.core.config;

public class WebSecurityConfig {
import com.meme.ala.core.auth.jwt.JwtAuthenticationFilter;
import com.meme.ala.core.auth.jwt.JwtTokenProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@RequiredArgsConstructor
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtTokenProvider jwtTokenProvider;

@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.anyRequest().permitAll()
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
UsernamePasswordAuthenticationFilter.class);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.meme.ala.domain.member.model.entity;

import com.meme.ala.core.auth.jwt.Authority;
import com.meme.ala.domain.alacard.model.entity.AlaCard;
import com.meme.ala.domain.member.model.entity.cardSetting.AlaCardSetting;
import lombok.*;
Expand Down Expand Up @@ -28,6 +29,9 @@ public class Member{

private MemberSetting memberSetting;

@Builder.Default
private String authority=Authority.ROLE_USER;

@Builder.Default
@DBRef
Map<AlaCard, AlaCardSetting> alaCardAlaCardSettingMap=new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.meme.ala.domain.member.service;

import com.meme.ala.core.auth.jwt.JwtTokenProvider;
import com.meme.ala.core.auth.oauth.GoogleUser;
import com.meme.ala.core.auth.oauth.NaverUser;
import com.meme.ala.core.auth.oauth.OAuthProvider;
Expand All @@ -10,7 +11,6 @@
import com.meme.ala.domain.member.model.entity.MemberSetting;
import com.meme.ala.domain.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Map;
Expand All @@ -19,8 +19,8 @@
@RequiredArgsConstructor
@Service
public class MemberServiceImpl implements MemberService{
@Autowired
private final MemberRepository memberRepository;
private final JwtTokenProvider jwtTokenProvider;

@Override
public String loginOrJoin(Map<String, Object> data, String provider) {
Expand All @@ -34,15 +34,10 @@ public String loginOrJoin(Map<String, Object> data, String provider) {
}
Optional<Member> optionalMember =
memberRepository.findByEmail(authUserInfo.getEmail());
if(optionalMember.isPresent()){
// TODO: 2021.7.15. 성공 시 JWT 토큰 생성 및 반환하는 기능 추가 - jongmin
return "dummy token";
}
else {
if(!optionalMember.isPresent()){
join(authUserInfo,provider);
// TODO: 2021.7.15. 성공 시 JWT 토큰 생성 및 반환하는 기능 추가 - jongmin
return "dummy token";
}
return jwtTokenProvider.createToken(authUserInfo.getEmail());
}

@Override
Expand All @@ -51,7 +46,7 @@ public void join(OAuthUserInfo authUserInfo, String provider) {
.email(authUserInfo.getEmail())
.memberSetting(
MemberSetting.builder()
.nickname(provider.charAt(0)+"_"+authUserInfo.getEmail().split("@")[0])
.nickname("alamonster_"+memberRepository.count())
.build())
.build();
if(provider.equals(OAuthProvider.GOOGLE)){
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
spring.data.mongodb.uri=mongodb://localhost:27017
spring.data.mongodb.database=testdb
spring.data.mongodb.database=testdb
jwt.secret=alasecret
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.meme.ala.core.auth.oauth.OAuthProvider;
import com.meme.ala.core.config.WebSecurityConfig;
import com.meme.ala.domain.member.service.MemberService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.http.MediaType;
import java.util.Map;

Expand All @@ -16,7 +20,8 @@
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(MemberController.class)
@AutoConfigureMockMvc(addFilters = false)
@WebMvcTest(value = MemberController.class, excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = WebSecurityConfig.class)})
public class MemberControllerTest {
@Autowired
private MockMvc mockMvc;
Expand Down Expand Up @@ -46,6 +51,7 @@ public class MemberControllerTest {
.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.data").value("dummy token"));
}

@Test
public void 네이버_OAuth_로그인_유닛테스트() throws Exception{
String sampleRequestBody=
Expand Down

0 comments on commit 0b20a28

Please sign in to comment.