Spring boot을 사용하여 JWT 인증 API 만들기
107943 단어 springbootjwtapijava
오늘은 Spring Boot을 사용하여 JWT 인증을 받은 간단한 RESTAPI를 만드는 방법을 소개합니다.
너는 아마 나의 이전 블로그 글을 보고 싶을 것이다
저는 위의 블로그 글에서 MySQL을 설정하는 방법, IntelliJ에서 새로운 프로젝트를 만드는 방법을 소개했기 때문에 이 블로그에서 이 부분을 건너뛰겠습니다.
0. SQL 쿼리를 사용하여 MySQL 데이터베이스 및 테이블을 만듭니다.
CREATE DATABASE restapi;
USE restapi;
CREATE TABLE blog (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(500) NOT NULL,
content VARCHAR(5000) NOT NULL
);
CREATE TABLE user_info(
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
password VARCHAR(500) NOT NULL,
fullname VARCHAR(50) NOT NULL
);
1. 본 강좌에 필요한 의존 항목:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
REST API를 만들려면 spring boot starter가 필요합니다.Mysql 연결기 자바, Mysql 데이터베이스 연결에 사용.
Spring Security 인증 설정
JWT 사용 권한을 부여하는 jsonwebtoken
2. 프로젝트 구조
우리는 응용 프로그램에서 항목의 속성을 정의할 것이다.속성
spring.datasource.url=jdbc:mysql://localhost:3306/restapi
spring.datasource.username=xxxx
spring.datasource.password=xxxx
spring.datasource.platform=mysql
jwt.secret={bcrypt}$donald
봄.데이터베이스 정보를 제공하는 데이터 원본입니다.사용자 이름과 비밀번호를 제공해야만 일을 할 수 있습니다.jwt.secret은 jwt의 키입니다.(이따가 자세히 논의할게요)
항목을 저장하기 위한 프로필입니다.
+) 컨트롤러:
인증용 컨트롤러 클래스, 블로그 컨텐츠를 정의하는 CRUD, 새 사용자 만들기
+) 예외:
검증 데이터의 기본 오류 핸들과 이상 정의
+) 모델:
Blog 엔티티, UserInfo 엔티티, JwtRequest 및 JwtResponse에 대한 모델 만들기
+) 저장소:
JPA와 MySQL 데이터베이스를 사용하여 블로그 및 UserInfo 저장소 만들기
+) 서비스:
데이터베이스에 사용자 이름이 있는지 확인하기 위해 JwtUserDetails Service 만들기
@SpringBootApplication
public class MainApplicationClass {
public static void main(String[] args) {
SpringApplication.run(MainApplicationClass.class, args);
}
}
3. 우리는 무엇을 창조할 것인가:블로그와 상호작용하는 API는 jwt 영패를 사용하여 인증을 해야 합니다.
이렇게 하려면 config 패키지의 WebSecurity 구성 클래스에서 configure 방법을 만들어야 합니다.
httpSecurity.csrf().disable()
.authorizeRequests().antMatchers("/authenticate","/user").permitAll().
anyRequest().authenticated().and().
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
4. jwt 영패 설정JwtAuthenticationEntry Point는 사용자 자격 증명이 올바르지 않을 때 인증되지 않은 메시지를 표시합니다.
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -7858869558953243875L;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
JwtRequestFilter 필터링 헤더 값:
import donald.apiwithspringboot.service.JwtUserDetailsService;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
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;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtUserDetailsService jwtUserDetailsService;
private final JwtToken jwtTokenUtil;
public JwtRequestFilter(JwtToken jwtTokenUtil) {
this.jwtTokenUtil = jwtTokenUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
logger.warn("JWT Token does not begin with Bearer String");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
jwt 영패를 생성하는 데 사용되는 JwtToken 클래스:@Component
public class JwtToken implements Serializable {
private static final long serialVersionUID = -2550185165626007488L;
public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;
@Value("${jwt.secret}")
private String secret;
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
private String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
.signWith(SignatureAlgorithm.HS512, secret).compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
WebSecurityConfig는 우리가 필요로 하는 bean을 정의하고 인증 구성 경로를 사용합니다.
import donald.apiwithspringboot.service.JwtUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
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.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private JwtUserDetailsService jwtUserDetailsService;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public JwtAuthenticationEntryPoint jwtAuthenticationEntryPointBean() throws Exception{
return new JwtAuthenticationEntryPoint();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.authorizeRequests().antMatchers("/authenticate","/user").permitAll().
anyRequest().authenticated().and().
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
5, 컨트롤러:AuthController는 사용자 자격 증명을 검증하기 위해 API를 정의하고 jwt 영패에 올바르게 응답합니다.
import donald.apiwithspringboot.model.JwtRequest;
import donald.apiwithspringboot.model.JwtResponse;
import donald.apiwithspringboot.service.JwtUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
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.web.bind.annotation.*;
import donald.apiwithspringboot.config.JwtToken;
import org.springframework.security.authentication.AuthenticationManager;
@RestController
@CrossOrigin
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtToken jwtToken;
@Autowired
private JwtUserDetailsService jwtUserDetailsService;
@RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetails userDetails = jwtUserDetailsService
.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtToken.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
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);
}
}
}
새 블로그 만들기, 블로그 내용 수정, 블로그 보기 또는 업데이트를 위한 API 작성을 위한 BlogController 클래스
import donald.apiwithspringboot.model.Blog;
import donald.apiwithspringboot.repository.BlogRepository;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
public class BlogController {
final
private BlogRepository blogRepository;
public BlogController(BlogRepository blogRepository) {
this.blogRepository = blogRepository;
}
@GetMapping("/blog")
public List<Blog> index(){
return blogRepository.findAll();
}
@GetMapping("/blog/{id}")
public Blog show(@PathVariable String id){
int blogId = Integer.parseInt(id);
return blogRepository.findById(blogId).orElse(new Blog());
}
@PostMapping("/blog/search")
public List<Blog> search(@RequestBody Map<String, String> body){
String searchTerm = body.get("text");
return blogRepository.findByTitleContainingOrContentContaining(searchTerm, searchTerm);
}
@PostMapping("/blog")
public Blog create(@RequestBody Map<String, String> body){
String title = body.get("title");
String content = body.get("content");
return blogRepository.save(new Blog(title, content));
}
@PutMapping("/blog/{id}")
public Blog update(@PathVariable String id, @RequestBody Map<String, String> body){
int blogId = Integer.parseInt(id);
// getting blog
Blog blog = blogRepository.findById(blogId).orElse(new Blog());
blog.setTitle(body.get("title"));
blog.setContent(body.get("content"));
return blogRepository.save(blog);
}
@DeleteMapping("blog/{id}")
public boolean delete(@PathVariable String id){
int blogId = Integer.parseInt(id);
blogRepository.deleteById(blogId);
return true;
}
}
UserInfoController는 새 사용자를 만들고 암호를 사용하여 데이터베이스에 삽입하고 BCryptPasswordEncoder 인코딩을 사용합니다.import donald.apiwithspringboot.exceptions.ValidationException;
import donald.apiwithspringboot.model.UserInfo;
import donald.apiwithspringboot.repository.UserInfoRepository;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
@RestController
public class UserInfoController {
final
private UserInfoRepository userInfoRepository;
// private HashData hashData = new HashData();
public UserInfoController(UserInfoRepository userInfoRepository) {
this.userInfoRepository = userInfoRepository;
}
@PostMapping("/user")
public Boolean create(@RequestBody Map<String, String> body) throws NoSuchAlgorithmException {
String username = body.get("username");
if (userInfoRepository.existsByUsername(username)){
throw new ValidationException("Username already existed");
}
String password = body.get("password");
String encodedPassword = new BCryptPasswordEncoder().encode(password);
// String hashedPassword = hashData.get_SHA_512_SecurePassword(password);
String fullname = body.get("fullname");
userInfoRepository.save(new UserInfo(username, encodedPassword, fullname));
return true;
}
}
6. 예외적인 경우:BaseErrorHandles 클래스의 handleException은 BAD\u 요청입니다.
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
@Slf4j
public class BaseErrorHandles {
@ResponseBody
@ExceptionHandler(value = ValidationException.class)
public ResponseEntity<?> handleException(ValidationException exception) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(exception.getMsg());
}
}
유효성 검사 예외
public class ValidationException extends RuntimeException {
private static final long serialVersionUID = 1L;
private String msg;
public ValidationException(String msg) {
this.msg = msg;
}
public String getMsg() {
return msg;
}
}
7, 모델:블로그 모델: 블로그 실체 정의
@Entity
public class Blog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String title;
private String content;
public Blog() { }
public Blog(String title, String content) {
this.setTitle(title);
this.setContent(content);
}
public Blog(int id, String title, String content) {
this.setId(id);
this.setTitle(title);
this.setContent(content);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Blog{" +
"id=" + id +
", title='" + title + '\'' +
", content='" + content + '\'' +
'}';
}
}
UserInfo enty를 정의하는 데 사용되는 UserInfo 클래스:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class UserInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private String fullname;
public UserInfo() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public UserInfo(String username, String password, String fullname) {
this.username = username;
this.password = password;
this.fullname = fullname;
}
public String getFullname() {
return fullname;
}
public void setFullname(String fullname) {
this.fullname = fullname;
}
}
AuthController에서 요청된 사용자 이름과 암호를 검증하는 데 사용되는 JwtRequest 모델
public class JwtRequest implements Serializable {
private static final long serialVersionUID = 5926468583005150707L;
private String username;
private String password;
public JwtRequest()
{
}
public JwtRequest(String username, String password) {
this.setUsername(username);
this.setPassword(password);
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
}
JwtResponse 영패 응답을 위한 모델 만들기
import java.io.Serializable;
public class JwtResponse implements Serializable {
private static final long serialVersionUID = -8091879091924046844L;
private final String jwttoken;
public JwtResponse(String jwttoken) {
this.jwttoken = jwttoken;
}
public String getToken() {
return this.jwttoken;
}
}
8. 저장소:BlogRepository는 JPA for blog 테이블을 통해 MySQL 데이터베이스와 협업합니다.
@Repository
public interface BlogRepository extends JpaRepository<Blog,Integer> {
// custom query to search to blog post by title or content
List<Blog> findByTitleContainingOrContentContaining(String text, String textAgain);
}
UserInfoRepository는 JPA를 통해 MySQL 데이터베이스를 user\u info 테이블로 처리합니다.
@Repository
public interface UserInfoRepository extends JpaRepository<UserInfo,Integer> {
Boolean existsByUsername(String username);
UserInfo findByUsername(String username);
}
9. 서비스:loadUserByUsername 메서드에 대한 JwtUserDetails Service 정의:
@Component
public class JwtUserDetailsService implements UserDetailsService {
@Autowired
private UserInfoRepository userInfoRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo user = userInfoRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found with username: " + username);
}
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
new ArrayList<>());
}
}
10. spring boot 응용 프로그램을 실행합니다.단순 실행: mvn spring boot:run
11. Postman을 사용하여 API와 상호 작용:
새 사용자 만들기
새 사용자를 생성했습니다.
사용자 자격 증명 확인:
사용자 자격 증명 확인 성공:
새 블로그 요청 본문:
새 블로그 제목 만들기:
새 블로그 응답 만들기:
그렇습니다.
앞으로의 강좌에서, 나는 이를 위해 단원 테스트와 계약 테스트를 작성하는 방법을 소개할 것이다.
평소와 같이github의 here 에서 원본 코드를 찾을 수 있습니다
감사합니다!
평화롭다
주의: 이 블로그가 당신에게 도움이 되고 감사의 마음을 전하고 싶다면 언제든지 방문하세요.
이것은 내가 더욱 가치 있는 내용을 공헌하는 데 도움을 줄 것이다.
Reference
이 문제에 관하여(Spring boot을 사용하여 JWT 인증 API 만들기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/cuongld2/create-apis-with-jwt-authorization-using-spring-boot-24f9텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)