여러 자료들을 찾아봤는데 너무 어렵게 설명하기도 하고...
Security에 대해 간단하게 보자
참조 문헌은 이쪽이다!
https://dev-coco.tistory.com/174
그렇다면 우리는 간략하게 알아보자
인증(Authentication)과 인가(Authorization)
대부분의 시스템에서는 회원을 관리하고 있고, 그에 따른 인증(Authentication)과 인가(Authorization)에 대한 처리를 해야 한다.
- 인증(Authentication) : 해당 사용자가 본인이 맞는지 확인하는 과정
- 인가(Authorization) : 해당 사용자가 요청하는 자원을 실행할 수 있는 권한이 있는가를 확인하는 과정
Spring Security는 기본적으로 인증 절차를 거친 후에 인가 절차를 진행하며, 인가 과정에서 해당 리소스에 접근 권한이 있는지 확인하게 된다.
Spring Security에서는 이러한 인증과 인가를 위해 Principal을 아이디로, Credential을 비밀번호로 사용하는 Credential 기반의 인증 방식을 사용한다.
- Principal(접근 주체) : 보호받는 Resource에 접근하는 대상
- Credential(비밀번호) : Resource에 접근하는 대상의 비밀번호
뭐 어찌됬든 간단히 코드보면서 해보지 뭐 ㅋㅋㅋㅋㅋㅋ
우선 build.gradle
// Todo : json web token
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.2'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2'
// TODO : SECURITY
implementation 'org.springframework.boot:spring-boot-starter-security'
web token 도 쓸거다
application.properties
# server
server.port=8000
# TODO : JWT secret
jwt.secret=secretKeysecretKeysecretKeysecretKeysecretKeysecretKeysecretKeysecretKeysecretKeysecretKey
# TODO : JWT 하루
jwt.access-token-validity-in-seconds: 86400000
# Todo : react server
#react.server=http://localhost:3000
config 파일 2개
우선 Web 에 대한 설정 파일 MvcConfiguration
package com.example.playhostproject.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpMethod;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@Configuration
public class MvcConfiguration implements WebMvcConfigurer {
@Value("${react.server}")
private String reactServer;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
// 아래 url 허용
.allowedOrigins(reactServer)
.allowedHeaders("*")
.allowCredentials(true)
// Todo: 아래 추가해야 update, delete, insert, select 가 cors 문제가 안생김
.allowedMethods(
HttpMethod.GET.name(),
HttpMethod.POST.name(),
HttpMethod.PUT.name(),
HttpMethod.DELETE.name(),
HttpMethod.PATCH.name()
);
}
}
이번엔 security config 파일이다.
CorsConfigurationSource corsConfigurationSource 이건 무시해주세요
배포할때 뭐 안되서 저거 썼어요 ㅋㅋㅋㅋㅋ
package com.example.playhostproject.config;
import com.example.playhostproject.security.jwt.AuthEntryPointJwt;
import com.example.playhostproject.security.jwt.AuthTokenFilter;
import com.example.playhostproject.security.services.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.WebSecurityCustomizer;
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;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
// spring security 라이브러리를 설치하면
// 기본적으로 모든 url 에 대해 인증을 진행함
// 내부적으로 사용하고 있는 로그인페이지로 자동 리다이렉트함
// 기본 user id : user , pwd: 콘솔에 보임
// application.properties 파일에 user/pwd 설정 가능
// 아래 클래스에서 인증/접근권한을 설정할 수 있음
@Configuration
// securedEnabled, prePostEnabled, jsr250Enabled 3개의 옵션이 존재(활성화 @)
// 1.securedEnabled
// @Secured 애노테이션을 사용하여 인가 처리를 하고 싶을때 사용하는 옵션이다.
// 단순 권한체크, spring 에서만 가능
// 기본값은 false
// 2.prePostEnabled
// @PreAuthorize, @PostAuthorize 애노테이션을 사용하여 인가 처리를 하고 싶을때 사용하는 옵션이다.
// 다양하고 유연하게 권한체크 가능, 유연한 권한체크를 위한 el 언어 제공 : 예) 권한문자열이 140 이상일때만 통과 등
// 기본값은 false
// 3.jsr250Enabled
// @RolesAllowed 애노테이션을 사용하여 인가 처리를 하고 싶을때 사용하는 옵션이다.
// 단순 권한체크, java 사용하는 곳은 모두 가능
// 기본값은 false
@EnableGlobalMethodSecurity(
// securedEnabled = true,
// jsr250Enabled = true,
prePostEnabled = true)
public class WebSecurityConfig { // extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsServiceImpl userDetailsService; // DB 조회 함수 객체
@Autowired
private AuthEntryPointJwt unauthorizedHandler; // 비인증/권한체크 예외처리 객체
// JWT 토큰 필터 객체 생성
@Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
@Value("${react.server}")
private String reactServer;
// @Override
// public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
// authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
// }
@Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of(reactServer));
configuration.addAllowedHeader("*");
configuration.addAllowedMethod("*");
configuration.setAllowCredentials(true);
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
// DB 에서 가져온 정보와 input 된 정보를 비교하는 함수
@Bean
public DaoAuthenticationProvider authenticationProvider() {
// db에서 가져온 정보와 리액트에서 전송한 유저 정보를 비교하는 객체
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService); // DB 유저 조회 함수 객체
authProvider.setPasswordEncoder(passwordEncoder()); // 암호 적용된 페스워드
return authProvider;
}
// @Bean
// @Override
// public AuthenticationManager authenticationManagerBean() throws Exception {
// return super.authenticationManagerBean();
// }
// AuthenticationManager 를 클래스 외부에서 사용하기 위해
// 아래 함수 정의 않하면 @Autodwired 로 가져올 때 에러가 발생할 수 있음
// 다른 클래스(객체)에서 AuthenticationManager 사용하고자 할때 @Autowired 로 객체 사용가능해서 편의성이 향상됨
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
// 패스워드 암호화 함수
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/*
* 스프링 시큐리티 룰을 무시하게 하는 Url 규칙(여기 등록하면 규칙 적용하지 않음)
*/
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers("/js/**", "/images/**", "/css/**");
}
// 스프링 시큐리티 룰을 무시하게 하는 Url 규칙(여기 등록하면 규칙 적용하지 않음)
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors()
.and(). // 연결
csrf().disable() // csrf 보안 비활성화
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() // 인증 예외처리는 AuthEntryPointJwt
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// 세션안쓰고(stateful) JWT 사용 예정
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll() // 이 url 은 모든 사용자 접근 허용
.antMatchers("/api/admin/**").hasRole("ADMIN") // admin 메뉴는 ROLE_ADMIN 만 가능
.antMatchers("/api/user/**").hasRole("USER") // admin 메뉴는 ROLE_ADMIN 만 가능
.antMatchers("/api/**").permitAll() // 이 url 은 모든 사용자 접근 허용
.anyRequest().permitAll(); // 그외 url은 모든 사용자, 모든 접속에 대해서 인증이 필요없다는 걸 의미
http.authenticationProvider(authenticationProvider()); // DB와 입력값(id, pwd) 비교
// UsernamePasswordAuthenticationFilter → username, password를 쓰는 form기반 인증을 처리하는 필터.
// AuthenticationManager를 통한 인증 실행
// 성공하면, Authentication 객체를 SecurityContext에(홀더) 저장 후 AuthenticationSuccessHandler 실행
// 실패하면 AuthenticationFailureHandler 실행
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); // JWT 토큰 필터 적용
return http.build();
}
}
자 그럼 config 파일이 끝났습니다. 짝짝짝
이번엔 뭐하지
Config 파일에 있는 필터 등등을 해보죠
-- 예외 처리 , 에러 핸들링하는 클래스 --
package com.example.playhostproject.security.jwt;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
// 인증/인가가 되지않은 유저가 요청을 했을때 동작하는 클래스
// REST API 이므로 MAP 자료구조를 body에 담아 클라이언트로 전송
@Slf4j
@Component
public class AuthEntryPointJwt implements AuthenticationEntryPoint {
// private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
log.error("Unauthorized error: {}", authException.getMessage());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
final Map<String, Object> body = new HashMap<>();
body.put("status", HttpServletResponse.SC_UNAUTHORIZED);
body.put("error", "Unauthorized");
body.put("message", authException.getMessage());
body.put("path", request.getServletPath());
// 잭슨 라이브러리
// mapper.writeValue(네트웍(파일 등), 객체) : 객체를 네트웍으로 전송
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), body);
}
}
-- 토큰 필터링 --
package com.example.playhostproject.security.jwt;
import com.example.playhostproject.security.services.UserDetailsServiceImpl;
import lombok.extern.slf4j.Slf4j;
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.util.StringUtils;
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;
// JWT 토큰 인증을 위한 필터 , 스프링에 기본적으로 없으므로 만들어 주어야함 ( 기본 : 세션 필터 )
// OncePerRequestFilter : 요청 당 반드시 한번만 인증/인가 로직 실행하게 보장함
// 예) 요청에 대해 인증/권한체크 후(서브 요청 1) 특정 url로 리다이렉트할때(서브 요청 2) 보통은 인증/권한체크가 2번 일어남 이때
// 마지막은 불필요하므로 한번만 인증/권한체크가 일어나게 보장하게 만들어주는 인터페이스
@Slf4j
public class AuthTokenFilter extends OncePerRequestFilter {
@Autowired
private JwtUtils jwtUtils;
@Autowired
private UserDetailsServiceImpl userDetailsService;
// private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class);
// Json Web Token 필터 만들어 SecurityContextHolder 에 새로운 JWT 필터 저장
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
// 웹토큰 받아서 문자열로 변환
String jwt = parseJwt(request);
// 1. 웹토큰 유효성 체크해서
// 2. 유효하면 DB에서 유저 있는 지 조회
// 3. 조회된 유저를 인증된 유저로 해서 홀더에 넣음
if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
// 웹토큰에서 유저 id 꺼냄
String email = jwtUtils.getUserNameFromJwtToken(jwt);
// 유저 id로 db 조회해서 userDetails에 넣음
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
// 인증된 객체로 반환 : new UsernamePasswordAuthenticationToken() 매개변수 3개짜리 생성자 효출하면 강제 인증 성공 authenticated = true 로 설정됨
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 인증된 authentication 객체를 홀더에 넣어둠
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Cannot set user authentication: {}", e);
}
// 필터체인으로 연결하여 줍니다.
// 체인에 필터를 실행하고 체인의 가장 마지막에는 클라이언트가 요청한 최종 자원이 위치
filterChain.doFilter(request, response); // 필터 실행
}
// 네트웍으로 전송된 헤더 데이터에 "Bearer" 있고
// "Authorization" 다음 문자열이 있으면 7부터 헤더의 길이만큼 잘라서 리턴함
private String parseJwt(HttpServletRequest request) {
String headerAuth = request.getHeader("Authorization");
if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
return headerAuth.substring(7, headerAuth.length());
}
return null;
}
}
-- 토큰을 만들거나 하는 Util 파일 --
package com.example.playhostproject.security.jwt;
import com.example.playhostproject.security.dto.UserDto;
import com.example.playhostproject.security.services.UserDetailsImpl;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.util.Date;
@Slf4j
@Component
public class JwtUtils {
// private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);
@Value("${jwt.secret}")
private String jwtSecret;
@Value("${jwt.access-token-validity-in-seconds}")
private int jwtExpirationMs;
// JWT 토큰 만들기
public String generateJwtToken(Authentication authentication) {
UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();
// Json Web Token 구조 : 헤더(header).내용(payload).서명(signature)
// 헤더 : 토큰타입, 알고리즘
// 내용 : 데이터(subject(주체(이름))), 토큰발급대상(issuedAt), 만료기간(expiration), 토큰수령자
// 서명 : Jwts.builder().signWith(암호화알고리즘, 비밀키값)
// 생성 : Jwts.builder().compact()
return Jwts.builder()
.setSubject((userPrincipal.getEmail()))
.setIssuedAt(new Date())
// 만료일자 적용
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS512, jwtSecret) // 암호화 적용 서명
.compact(); // 토큰 생성
}
// JWT 토큰에서 유저명 꺼내기
public String getUserNameFromJwtToken(String token) {
// 웹토큰의 비밀키 + 토큰명을 적용해 body 안의 subject(주체(이름))에 접근해서 꺼냄
return Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
// JWT 웹토큰 유효성 체크
// 디지털 서명이 위조 또는 훼손되었는지 확인하는 함수
public boolean validateJwtToken(String authToken) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
log.error("Invalid JWT signature: {}", e.getMessage());
} catch (MalformedJwtException e) {
log.error("Invalid JWT token: {}", e.getMessage());
} catch (ExpiredJwtException e) {
log.error("JWT token is expired: {}", e.getMessage());
} catch (UnsupportedJwtException e) {
log.error("JWT token is unsupported: {}", e.getMessage());
} catch (IllegalArgumentException e) {
log.error("JWT claims string is empty: {}", e.getMessage());
}
return false;
}
}
여기까지 오셨군요!
그렇다면 설정은 끝났습니다.
다음은 User 라는 클래스를 설정해볼게요
프로젝트때 사용한거라 좀 있어요
@Entity
@Getter
@Table(name = "PROJECT_USER")
@SequenceGenerator(
name = "SQ_PROJECT_USER_GENERATOR"
, sequenceName = "SQ_PROJECT_USER"
, initialValue = 1
, allocationSize = 1
)
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Setter
@Builder
@DynamicInsert
@DynamicUpdate
// soft delete
@Where(clause = "DELETE_YN = 'N'")
@SQLDelete(sql = "UPDATE PROJECT_USER SET DELETE_YN = 'Y', DELETE_TIME=TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS') WHERE USER_ID = ?")
public class User extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "SQ_PROJECT_USER_GENERATOR")
@Column(name = "USER_ID")
private Integer userId;
@Column(name = "NAME")
private String name;
@Column(name = "EMAIL")
private String email; // Principal
@Column(name = "PASSWORD")
private String password; // Credential
private String description;
@Column(name = "ROLE")
private String role;
@Column(name = "POINT")
private Integer point;
}
권한은 Enum 형식!
어드민 , 유저 두개입니다.
@Getter
@RequiredArgsConstructor
public enum Role {
ROLE_ADMIN,
ROLE_USER
}
UserDetails
import com.example.playhostproject.model.entity.user.User;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
// 아래 @Override 이 부분이 인터페이스에서 정의한 함수로
// email 은 개발자가 추가한 정보이고,
// 나머지 속성은 Spring Security에서 제공한 속성/함수 정보임
public class UserDetailsImpl implements UserDetails {
private static final long serialVersionUID = 1L;
private String name; // Spring Security
private String email; // 개발자 추가 속성
// json 역직렬화 시 대상 속성 무시
@JsonIgnore
private String password; // Spring Security
// 계정이 갖고 있는 권한 목록을 저장하는 속성
private GrantedAuthority authority; // Spring Security
public UserDetailsImpl( String email, String password, String name,
GrantedAuthority authority) {
this.email = email;
this.password = password;
this.name = name;
this.authority = authority;
}
public static UserDetailsImpl build(User user) {
// role.getName().name() : 롤 정보 ( ROLE_USER 등 )
// 권한 생성은 : new SimpleGrantedAuthority(권한문자열) 생성자를 호출 해서 생성
GrantedAuthority authority = new SimpleGrantedAuthority(user.getRole());
return new UserDetailsImpl(
user.getEmail(),
user.getPassword(),
user.getRole(),
authority);
}
// 권한을 스프링 시큐리티의 권한 배열로 만들기
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> set = new HashSet<>();
set.add(authority);
return set;
}
// 계정이 갖고 있는 권한을 리턴하는 함수
public GrantedAuthority getAuthority() {
return authority;
}
public String getEmail() {
return email;
}
@Override
public String getPassword() {
return password;
}
// 계정의 이름를 리턴 (주로 로그인 ID가 사용됨)
@Override
public String getUsername() {
return name;
}
// 계정이 만료되지 않았는지를 리턴( true 이면 만료되지 않았음을 의미 )
// 아래는 만료 체크가 필요없어서 항상 true 를 리턴하게 되어 있음
// 만약 필요하다면 DB에서 만료여부 컬럼을 관리하고 그 정보를 쿼리해서 사용하면 됨
@Override
public boolean isAccountNonExpired() {
return true;
}
// 계정이 잠겨있지 않은지를 리턴( true 이면 잠겨있지 않음을 의미 )
@Override
public boolean isAccountNonLocked() {
return true;
}
// 계정의 패스워드(Credential) 가 만료되지 않는지를 리턴( true 이면 패스워드가 만료되지 않음을 의미 )
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 계정이 사용 가능한 계정인지를 리턴( true 이면 사용 가능한 계정을 의미 )
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
UserDetailsImpl userDto = (UserDetailsImpl) o;
return Objects.equals(email, userDto.email);
}
}
그 다음은 service 부분입니다.
import com.example.playhostproject.model.entity.user.User;
import com.example.playhostproject.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
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;
import org.springframework.transaction.annotation.Transactional;
// 유저 인증을 위한 클래스
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
UserRepository userRepository;
// 유저 인증을 위한 함수
// DB에 있는 지 확인해서 있으면 UserDetailsImpl 로 UserDto 객체 생성
@Override
@Transactional
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
// 유저 인증을 위한 함수 ( DB 확인 ) : 기본키 (이메일)
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("User Not Found with email: " + email));
// DB에 있는 지 확인해서 있으면 UserDetailsImpl 로 User 객체 생성
return UserDetailsImpl.build(user);
}
}
Repository
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
/**
* Todo : 이메일로 유저정보 찾기
* @param email
* @return
*/
Optional<User> findByEmail(String email);
/**
* Todo : 이메일로 유저 정보가 있는지 확인
* @param email
* @return
*/
boolean existsByEmail(String email);
}
마지막으로 Controller 입니다
다른 파일과 똑같이 Contoller 파일도 자기만의 파일을 만드시면 되요.
얘는 제가 좀 바꾼거예요 잘 안되서 ㅋㅋ..
@RestController
@Slf4j
@RequestMapping("/api")
public class AuthController {
@Autowired
AuthService authService;
// Todo : 인증/권한 체크 관리 객체
@Autowired
AuthenticationManager authenticationManager;
// Todo : 웹 토큰 객체
@Autowired
JwtUtils jwtUtils;
// Todo : 패스워드 암호화 객체
@Autowired
PasswordEncoder passwordEncoder;
// Todo : 일반 로그인
/**
* Todo : 유저 로그인
*
* @param user
* @return
*/
@PostMapping("/auth/signin")
public ResponseEntity<Object> login(@RequestBody User user) {
try {
// Todo : 1) 인증 시작 : id/pw 로 db에 있는지 조사
// authentication : 인증을 통과한 객체(id / pw , 유저명 , 인증여부 = true/false)
Authentication authentication = authenticationManager.authenticate(
// 아이디와 패스워드로, Security 가 알아 볼 수 있는 token 객체로 생성해서 인증처리
new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword()));
// Todo : 2) 인증된 객체들를 홀더에 저장해둠
SecurityContextHolder.getContext().setAuthentication(authentication);
// Todo : 3) JWT(웹토큰) 발행
String jwt = jwtUtils.generateJwtToken(authentication);
// Todo : 4) 인증된 객체(authentication : 유저 및 환경정보)
// => authentication.getPrincipal() : 순수한 유저 정보
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
// Todo : 5) 리액트 전송시 : 웹토큰 + id (email) + 유저명 + 권한
// 5-1) 권한을 임시 변수에 저장
String role = userDetails.getAuthority().toString();
Optional<User> user1 = authService.findByEmail(userDetails.getEmail());
// Todo : 결과 전송을 위한 DTO : UserRes 객체
UserRes userRes = new UserRes(jwt, user1.get().getUserId(), user1.get().getName(), userDetails.getEmail(), role);
return new ResponseEntity<>(userRes, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.NOT_FOUND);
}
}
/**
* Todo : 유저 회원가입 + 수정
*
* @param user
* @return
*/
@PostMapping("/auth/signup")
public ResponseEntity<Object> register(@RequestBody User user) {
try {
// Todo : 1) 요청된 유저객체 id(email) 있는지 확인
if (authService.existByEmail(user.getEmail())) {
return ResponseEntity.badRequest().body("에러 : 이메일이 이미 있습니다.");
}
// Todo : 2) 신규 유저 생성 : 권한 없이 생성
User user1 = User.builder()
.email(user.getEmail())
// 패스워드 암호화
.password(passwordEncoder.encode(user.getPassword()))
.name(user.getName())
.build();
// Todo : 3) 리엑트에서 요청한 권한이 있는지 조사
String role = user.getRole(); // 요청 권한 가져오기
if (role.equals("ROLE_ADMIN")) {
user1.setRole(Role.ROLE_ADMIN.name());
} else {
user1.setRole(Role.ROLE_USER.name());
}
// Todo : 신규 유저 저장
authService.siginup(user1);
return new ResponseEntity<>(user1, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
바꾸면서 활용하면 되지 않을까 싶은디... ㅋ...
'SpringBoot' 카테고리의 다른 글
SprinBoot - logback , log4jdbc 설정 (1) | 2023.12.17 |
---|---|
Sprinboot 3.x버젼 - QueryDSL 설정과 간단하게 사용해보자 (1) | 2023.12.17 |
Springboot - NoSQL Redis (0) | 2023.12.14 |
윈도우 카프카 - Kafka 를 Springboot 에서 간단히 써보자 (1) | 2023.12.12 |
MySQL + Springboot JPA + logback (1) | 2023.12.12 |