diff --git a/cardatabase/pom.xml b/cardatabase/pom.xml index 996a8fe..01baaad 100644 --- a/cardatabase/pom.xml +++ b/cardatabase/pom.xml @@ -74,6 +74,24 @@ xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/ --> + + io.jsonwebtoken + jjwt-api + 0.11.2 + + + io.jsonwebtoken + jjwt-impl + 0.11.2 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.2 + runtime + + diff --git a/cardatabase/src/main/java/com/packt/cardatabase/AuthenticationFilter.java b/cardatabase/src/main/java/com/packt/cardatabase/AuthenticationFilter.java new file mode 100644 index 0000000..457511a --- /dev/null +++ b/cardatabase/src/main/java/com/packt/cardatabase/AuthenticationFilter.java @@ -0,0 +1,45 @@ +package com.packt.cardatabase; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import com.packt.cardatabase.service.JwtService; + + +@Component +public class AuthenticationFilter extends OncePerRequestFilter { + + @Autowired + private JwtService jwtService; + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) + throws ServletException, java.io.IOException{ + //Authorization 헤더에서 토큰을 가져옴 + String jws = request.getHeader(HttpHeaders.AUTHORIZATION); + if (jws != null) { + //토큰을 확인하고 사용자를 얻음 + String user = jwtService.getAuthUser(request); + //인증 + Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, java.util.Collections.emptyList()); + + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + filterChain.doFilter(request, response); + } +} diff --git a/cardatabase/src/main/java/com/packt/cardatabase/SecurityConfig.java b/cardatabase/src/main/java/com/packt/cardatabase/SecurityConfig.java index 77a1d1a..e209189 100644 --- a/cardatabase/src/main/java/com/packt/cardatabase/SecurityConfig.java +++ b/cardatabase/src/main/java/com/packt/cardatabase/SecurityConfig.java @@ -3,10 +3,13 @@ package com.packt.cardatabase; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.core.userdetails.User; //import org.springframework.security.core.userdetails.UserDetails; //import org.springframework.security.core.userdetails.UserDetailsService; @@ -14,7 +17,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur //import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; //deprecated import org.springframework.security.web.SecurityFilterChain; - +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import com.packt.cardatabase.service.UserDetailsServiceImpl; @@ -25,6 +28,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsServiceImpl userDetailsService; + @Autowired + private AuthenticationFilter authenticationFilter; /* @SuppressWarnings("deprecation") @Bean @@ -43,5 +48,24 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder()); } + + @Bean + public AuthenticationManager getAuthenticationManager() throws Exception { + return authenticationManager(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + .authorizeRequests() + // /login 엔드포인트에 대한 post 요청은 보호되지 않음 + .antMatchers(HttpMethod.POST, "/login").permitAll() + // 다른 모든 요청은 보호됨 + .anyRequest().authenticated().and() + .addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class); + + } } diff --git a/cardatabase/src/main/java/com/packt/cardatabase/domain/AccountCredentials.java b/cardatabase/src/main/java/com/packt/cardatabase/domain/AccountCredentials.java new file mode 100644 index 0000000..705b0f1 --- /dev/null +++ b/cardatabase/src/main/java/com/packt/cardatabase/domain/AccountCredentials.java @@ -0,0 +1,24 @@ +package com.packt.cardatabase.domain; + +public class AccountCredentials { + private String username; + private String password; + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setUsername(String username) { + this.username = username; + } + + +} diff --git a/cardatabase/src/main/java/com/packt/cardatabase/domain/UserRepository.java b/cardatabase/src/main/java/com/packt/cardatabase/domain/UserRepository.java index 69a3c85..96c6548 100644 --- a/cardatabase/src/main/java/com/packt/cardatabase/domain/UserRepository.java +++ b/cardatabase/src/main/java/com/packt/cardatabase/domain/UserRepository.java @@ -2,8 +2,9 @@ package com.packt.cardatabase.domain; import java.util.Optional; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; - +@RepositoryRestResource(exported=false) public interface UserRepository extends CrudRepository{ Optional findByUsername(String username); diff --git a/cardatabase/src/main/java/com/packt/cardatabase/service/JwtService.java b/cardatabase/src/main/java/com/packt/cardatabase/service/JwtService.java new file mode 100644 index 0000000..184cc14 --- /dev/null +++ b/cardatabase/src/main/java/com/packt/cardatabase/service/JwtService.java @@ -0,0 +1,48 @@ +package com.packt.cardatabase.service; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import java.security.Key; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Component; +import javax.servlet.http.HttpServletRequest; +import java.util.Date; + +@Component +public class JwtService { + static final long EXPIRATIONTIME = 8640000; //1일을 밀리초로 계산한값 + static final String PREFIX = "Bearer"; + //비밀키 생성 , 시연용도 + //애플리케이션 구성에서 읽을수 있음 + static final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256); + + //서명된 JWT 토큰생성 + public String getToken(String username) { + String token = Jwts.builder() + .setSubject(username) + .setExpiration(new Date(System.currentTimeMillis()+EXPIRATIONTIME)) + .signWith(key) + .compact(); + + return token; + } + + //요청권한부여 헤더에서 토큰을 가져와 + //토큰을 확인하고 사용자 이름을 얻음 + public String getAuthUser(HttpServletRequest request) { + String token = request.getHeader(HttpHeaders.AUTHORIZATION); + if (token != null) { + String user = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token.replace(PREFIX, "")) + .getBody() + .getSubject(); + if (user != null) + return user; + } + return null; + } + +} diff --git a/cardatabase/src/main/java/com/packt/cardatabase/web/LoginController.java b/cardatabase/src/main/java/com/packt/cardatabase/web/LoginController.java new file mode 100644 index 0000000..0344d4d --- /dev/null +++ b/cardatabase/src/main/java/com/packt/cardatabase/web/LoginController.java @@ -0,0 +1,47 @@ +package com.packt.cardatabase.web; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import com.packt.cardatabase.domain.AccountCredentials; +import com.packt.cardatabase.service.JwtService; + +@RestController +public class LoginController { + @Autowired + private JwtService jwtService; + + @Autowired + AuthenticationManager authenticationManager; + + @RequestMapping(value="/login", method=RequestMethod.POST) + public ResponseEntity getToken(@RequestBody AccountCredentials credentials){ + //토큰을 생성하고 응답의 Authorization 해더로 보냄 + UsernamePasswordAuthenticationToken creds = + new UsernamePasswordAuthenticationToken( + credentials.getUsername(), + credentials.getPassword()); + + Authentication auth = authenticationManager.authenticate(creds); + + //토큰생성 + String jwts = jwtService.getToken(auth.getName()); + + //생성된 토큰으로 응답을 생성 + return ResponseEntity.ok() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwts) + .header(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "Authorization") + .build(); + + } + +}