173만명의 커뮤니티!! 함께 토론해봐요.
미해결
나도코딩의 자바 기본편 - 풀코스 (20시간)
1. interface클래스내에 정의된 메써드는 public abstract이고, 변수는 public static final이 자동으로 앞에 정의가 된다고 하셨는데 이건 고정인건가요? 다른 형태로 봐꿀 수 있나요? 봐꿀 수 없다면 왜 그러한지가 궁금합니다. interface 클래스를 implements해주고 추상클래스를 extends 해주는 것은 둘 다 상속인건가요? 둘다 부모 클래스가 되는건가요?
최은희
2023-07-20T12:31:29.729Z
댓글 1
좋아요 1
조회수 296
미해결
스프링부트 시큐리티 & JWT 강의
package com.cos.jwt.config.jwt; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; // 시큐리티가 filter를 가지고 있는데, 그 필터중에 BasicAuthenticationFilter라는 것이 있음. // 권한이나 인증이 필요한 특정 주소를 요청했을 때 위 필터를 무조건 타게 되어있음. // 만약 권한이나 인증이 필요한 주소가 아니라면 이 필터를 안타요. public class JwtAuthorizationFilter extends BasicAuthenticationFilter { public JwtAuthorizationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } //인증이나 권한이 필요한 주소요청이 있을 때 해당 필터를 타게 됨. @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { super.doFilterInternal(request, response, chain); System.out.println("인증이나 권한이 필요한 주소 요청이 됨."); String jwtHeader = request.getHeader("Authorization"); System.out.println("jwtHeader = " + jwtHeader); } } 마지막 강의에서 해당 코드 작성하고, jwtHeader test해보려는데, 저는 강사님과 다르게 GET요청보내면 콘솔에 "필터3"만 떠요! 코드를 살펴보니, MyFilter3에 POST요청일 때만 돌아가도록 로직이 구현되었는데, 제가 수업 중 놓친 걸까요? 작성된 코드로는 불가능한 결과물인 것 같아서요 ㅠ
임주혁
2023-07-20T11:23:34.081Z
댓글 3
좋아요 0
조회수 1002
미해결
스프링부트 시큐리티 & JWT 강의
SecurityConfig package com.example.project1.config.security; import com.example.project1.config.jwt.JwtAccessDeniedHandler; import com.example.project1.config.jwt.JwtAuthenticationEntryPoint; import com.example.project1.config.jwt.JwtProvider; import com.example.project1.config.oauth2.PrincipalOauth2UserService; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.DelegatingPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import java.util.HashMap; import java.util.Map; @Configuration @RequiredArgsConstructor @EnableWebSecurity // @EnableGlobalMethodSecurity 어노테이션은 Spring Security에서 메서드 수준의 보안 설정을 활성화하는데 사용되는 어노테이션입니다. //@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) public class SecurityConfig { private final JwtProvider jwtProvider; private final PrincipalOauth2UserService principalOauth2UserService; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http // 스프링 시큐리티에서 제공하는 로그인 페이지를 안쓰기 위해 .httpBasic().disable() // JWt 방식을 제대로 쓰려고 하면 프론트엔드가 분리된 환경을 가정하고 해야합니다. .csrf().disable() .formLogin().disable() .logout().disable() // JWT 방식은 세션저장을 사용하지 않기 때문에 꺼줍니다. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http .authorizeRequests() .antMatchers("/api/v1/boards/write") .access("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')") .antMatchers("/api/v1/boards/modify") .access("hasRole('ROLE_USER')") .antMatchers("/api/v1/boards/remove") .access("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')") .antMatchers("/api/v1/admin/**") .access("hasRole('ROLE_ADMIN')") // /success-oauth 엔드포인트에 대해 인증된 사용자만 접근 가능하도록 설정 // .antMatchers("/success-oauth").authenticated() .antMatchers("/swagger-resources/**").permitAll() .antMatchers("/swagger-ui/**").permitAll() .antMatchers("/api/v1/users/**").permitAll(); http // JWT Token을 위한 Filter를 아래에서 만들어 줄건데, // 이 Filter를 어느위치에서 사용하겠다고 등록을 해주어야 Filter가 작동이 됩니다. // security 로직에 JwtFilter 등록 // .addFilterBefore(new JwtAuthenticationFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class) .apply(new JwtSecurityConfig(jwtProvider)); // 에러 방지 http .exceptionHandling() .authenticationEntryPoint(new JwtAuthenticationEntryPoint()) .accessDeniedHandler(new JwtAccessDeniedHandler()); // oauth2 http // oauth2Login() 메서드는 OAuth 2.0 프로토콜을 사용하여 소셜 로그인을 처리하는 기능을 제공합니다. .oauth2Login() // .defaultSuccessUrl("/success-oauth") // OAuth2 로그인 성공 이후 사용자 정보를 가져올 때 설정 담당 .userInfoEndpoint() // OAuth2 로그인 성공 시, 후작업을 진행할 서비스 .userService(principalOauth2UserService) .and() .defaultSuccessUrl("/success-oauth"); return http.build(); } @Bean PasswordEncoder passwordEncoder() { String idForEncode = "bcrypt"; Map<String, PasswordEncoder> encoders = new HashMap<>(); encoders.put(idForEncode, new BCryptPasswordEncoder()); return new DelegatingPasswordEncoder(idForEncode, encoders); } } JwtProvider package com.example.project1.config.jwt; import com.example.project1.domain.jwt.TokenDTO; import io.jsonwebtoken.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import io.jsonwebtoken.security.Keys; import javax.xml.bind.DatatypeConverter; import java.security.Key; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.stream.Collectors; @Slf4j @Component public class JwtProvider { private static final String AUTHORITIES_KEY = "auth"; @Value("${jwt.access.expiration}") private long accessTokenTime; @Value("${jwt.refresh.expiration}") private long refreshTokenTime; private Key key; public JwtProvider( @Value("${jwt.secret_key}") String secret_key) { byte[] secretByteKey = DatatypeConverter.parseBase64Binary(secret_key); this.key = Keys.hmacShaKeyFor(secretByteKey); } // 유저 정보를 가지고 AccessToken, RefreshToken 을 생성하는 메소드 public TokenDTO createToken(Authentication authentication) { // 권한 가져오기 // authentication 객체에서 권한 정보(GrantedAuthority)를 가져와 문자열 형태로 변환한 후, // 쉼표로 구분하여 조인한 결과를 authorities 변수에 저장합니다. 따라서 authorities는 권한 정보를 문자열 형태로 가지게 됩니다. // 권한 정보를 문자열로 변환하여 클레임에 추가하는 방식 String authorities = authentication.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(",")); long now = (new Date()).getTime(); Date now2 = new Date(); // AccessToken 생성 Date accessTokenExpire = new Date(now + this.accessTokenTime); String accessToken = Jwts.builder() // 내용 sub : 유저의 이메일 // 토큰 제목 .setSubject(authentication.getName()) .setIssuedAt(now2) // 클레임 id : 유저 ID .claim(AUTHORITIES_KEY, authorities) // 내용 exp : 토큰 만료 시간, 시간은 NumericDate 형식(예: 1480849143370)으로 하며 // 항상 현재 시간 이후로 설정합니다. .setExpiration(accessTokenExpire) // 서명 : 비밀값과 함께 해시값을 ES256 방식으로 암호화 .signWith(key, SignatureAlgorithm.HS256) .compact(); log.info("accessToken : " + accessToken); // RefreshToken 생성 Date refreshTokenExpire = new Date(now + this.refreshTokenTime); String refreshToken = Jwts.builder() .setSubject(authentication.getName()) .claim(AUTHORITIES_KEY, authorities) .setIssuedAt(now2) .setExpiration(refreshTokenExpire) .signWith(key, SignatureAlgorithm.HS256) .compact(); log.info("refreshToken : " + refreshToken); return TokenDTO.builder() .grantType("Bearer") .accessToken(accessToken) .refreshToken(refreshToken) .accessTokenTime(accessTokenExpire) .refreshTokenTime(refreshTokenExpire) // principalDeatails에서 getUserName 메소드가 반환한 것을 담아준다. // 이메일을 반환하도록 구성했으니 이메일이 반환됩니다. .userEmail(authentication.getName()) .build(); } // 소셜 로그인 성공시 JWT 발급 public TokenDTO createToken2(UserDetails userDetails) { long now = (new Date()).getTime(); Date now2 = new Date(); // userDetails.getAuthorities()는 사용자의 권한(authorities) 정보를 가져오는 메서드입니다. // claims.put("roles", userDetails.getAuthorities()) 코드는 사용자의 권한 정보를 클레임에 추가하는 것입니다. // 클레임에는 "roles"라는 키로 사용자의 권한 정보가 저장되며, 해당 권한 정보는 JWT의 페이로드 부분에 포함됩니다. Claims claims = Jwts.claims().setSubject(userDetails.getUsername()); claims.put("roles", userDetails.getAuthorities()); // access token Date accessTokenExpire = new Date(now + this.accessTokenTime); String accessToken = Jwts.builder() .setSubject(userDetails.getUsername()) .setClaims(claims) .setIssuedAt(now2) .setExpiration(accessTokenExpire) .signWith(key,SignatureAlgorithm.HS256) .compact(); // RefreshToken 생성 Date refreshTokenExpire = new Date(now + this.refreshTokenTime); String refreshToken = Jwts.builder() .setIssuedAt(now2) .setClaims(claims) .setExpiration(refreshTokenExpire) .signWith(key, SignatureAlgorithm.HS256) .compact(); return TokenDTO.builder() .grantType("Bearer") .accessToken(accessToken) .refreshToken(refreshToken) .userEmail(userDetails.getUsername()) .build(); } // accessToken 생성 // 리프레시 토큰을 사용하여 새로운 액세스 토큰을 생성하는 로직을 구현 public TokenDTO createAccessToken(String refreshToken, List<GrantedAuthority> authorities) { Long now = (new Date()).getTime(); Date now2 = new Date(); Date accessTokenExpire = new Date(now + this.accessTokenTime); String userEmail = extractUserEmailFromToken(refreshToken); String accessToken = Jwts.builder() .setIssuedAt(now2) .setSubject(userEmail) .setExpiration(accessTokenExpire) .claim(AUTHORITIES_KEY, authorities ) .signWith(key, SignatureAlgorithm.HS256) .compact(); log.info("accessToken : " + accessToken); return TokenDTO.builder() .grantType("Bearer ") .accessToken(accessToken) .userEmail(userEmail) .build(); } // 리프레시 토큰의 유효성을 검증하는 로직을 구현 // 예를 들어, 토큰 서명 검증 및 만료 시간 확인 등을 수행 public boolean validateRefreshToken(String refreshToken) { try { // 토큰의 유효성을 검증하는 로직을 구현 // 예를 들어, 토큰의 서명을 확인하고 만료 시간을 검사합니다. // 유효한 토큰인 경우 true를 반환하고, 그렇지 않은 경우 false를 반환합니다. Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(refreshToken); return true; } catch (Exception e) { return false; } } // 리프레시 토큰에서 사용자 이메일을 추출하는 로직을 구현 // 예를 들어, 토큰의 특정 클레임에서 사용자 이메일을 추출하여 반환 public String extractUserEmailFromToken(String refreshToken) { Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(refreshToken).getBody(); // 사용자 이메일을 추출하는 로직을 구현하여 결과를 반환 return claims.getSubject(); } // JWT 토큰을 복호화하여 토큰에 들어있는 정보를 꺼내는 코드 // 토큰으로 클레임을 만들고 이를 이용해 유저 객체를 만들어서 최종적으로 authentication 객체를 리턴 // 인증 정보 조회 public Authentication getAuthentication(String token) { // 토큰 복호화 메소드 Claims claims = parseClaims(token); if(claims.get("auth") == null) { throw new RuntimeException("권한 정보가 없는 토큰입니다."); } // 클레임 권한 정보 가져오기 Collection<? extends GrantedAuthority> authorities = Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(",")) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); // UserDetails 객체를 만들어서 Authentication 리턴 User principal = new User(claims.getSubject(), "", authorities); return new UsernamePasswordAuthenticationToken(principal, token, authorities); } private Claims parseClaims(String token) { try { return Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody(); } catch (ExpiredJwtException e) { log.info("ExpiredJwtException : " + e.getMessage()); log.info("ExpiredJwtException : " + e.getClaims()); return e.getClaims(); } } // 토큰의 유효성 검증을 수행 public boolean validateToken(String token) { try { Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token); return true; } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { log.info("잘못된 JWT 서명입니다."); } catch (ExpiredJwtException e) { log.info("만료된 JWT 토큰입니다."); } catch (UnsupportedJwtException e) { log.info("지원되지 않는 JWT 토큰입니다."); }catch (IllegalArgumentException e) { log.info("JWT 토큰이 잘못되었습니다."); } return false; } } JwtAuthenticationFilter package com.example.project1.config.jwt; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.StringUtils; 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.HttpServletRequest; import java.io.IOException; // 클라이언트 요청 시 JWT 인증을 하기 위해 설치하는 커스텀 필터로 // UsernamePasswordAuthenticationFiler 이전에 실행된다. // 이전에 실행된다는 뜻은 JwtAuthenticationFilter 를 통과하면 // UsernamePasswordAuthenticationFilter 이후의 필터는 통과한 것으로 본다는 뜻이다. // 쉽게 말해서, Username + Password 를 통한 인증을 Jwt 를 통해 수행한다는 것이다. // JWT 방식은 세션과 다르게 Filter 하나를 추가해야 합니다. // 이제 사용자가 로그인을 했을 때, Request 에 가지고 있는 Token 을 해석해주는 로직이 필요합니다. // 이 역할을 해주는것이 JwtAuthenticationFilter입니다. // 세부 비즈니스 로직들은 TokenProvider에 적어둡니다. 일종의 service 클래스라고 생각하면 편합니다. // 1. 사용자의 Request Header에 토큰을 가져옵니다. // 2. 해당 토큰의 유효성 검사를 실시하고 유효하면 // 3. Authentication 인증 객체를 만들고 // 4. ContextHolder에 저장해줍니다. // 5. 해당 Filter 과정이 끝나면 이제 시큐리티에 다음 Filter로 이동하게 됩니다. @RequiredArgsConstructor @Slf4j public class JwtAuthenticationFilter extends GenericFilterBean { public static final String HEADER_AUTHORIZATION = "Authorization"; private final JwtProvider jwtProvider; // doFilter는 토큰의 인증정보를 SecurityContext에 저장하는 역할 수행 @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; // Request Header에서 JWT 토큰을 추출 // 요청 헤더에서 JWT 토큰을 추출하는 역할 String jwt = resolveToken(httpServletRequest); String requestURI = httpServletRequest.getRequestURI(); if(StringUtils.hasText(jwt) && jwtProvider.validateToken(jwt)){ // 토큰이 유효할 경우 토큰에서 Authentication 객체를 가지고 와서 SecurityContext에 저장 Authentication authentication = jwtProvider.getAuthentication(jwt); SecurityContextHolder.getContext().setAuthentication(authentication); log.info("Security Context에 '{}' 인증 정보를 저장했습니다., uri : {}", authentication.getName(), requestURI); } else { log.debug("유효한 JWT 토큰이 없습니다. uri : {}", requestURI); } chain.doFilter(request, response); } // Request Header 에서 토큰 정보를 꺼내오기 위한 메소드 // HEADER_AUTHORIZATION로 정의된 헤더 이름을 사용하여 토큰을 찾고, // 토큰이 "Bearer "로 시작하는 경우에만 실제 토큰 값을 반환 private String resolveToken(HttpServletRequest httpServletRequest) { String bearerToken = httpServletRequest.getHeader(HEADER_AUTHORIZATION); if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } else { return null; } } } JwtAccessDeniedHandler, JwtAuthenticationEntryPoint 생략 PrincipalDetails package com.example.project1.config.auth; import com.example.project1.entity.member.MemberEntity; import lombok.Getter; import lombok.Setter; import lombok.ToString; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.oauth2.core.user.OAuth2User; import java.util.ArrayList; import java.util.Collection; import java.util.Map; @Setter @Getter @ToString public class PrincipalDetails implements UserDetails, OAuth2User { private MemberEntity member; private Map<String, Object> attributes; // 일반 로그인 public PrincipalDetails(MemberEntity member) { this.member = member; } // OAuth2 로그인 public PrincipalDetails(MemberEntity member, Map<String, Object> attributes) { this.member = member; this.attributes = attributes; } // 해당 유저의 권한을 리턴하는 곳 @Override public Collection<? extends GrantedAuthority> getAuthorities() { Collection<GrantedAuthority> collection = new ArrayList<>(); collection.add(new SimpleGrantedAuthority("ROLE_" + member.getUserType().toString())); return collection; } // 사용자 패스워드를 반환 @Override public String getPassword() { return member.getUserPw(); } // 사용자 이름 반환 @Override public String getUsername() { return member.getUserEmail(); } // 계정 만료 여부 반환 @Override public boolean isAccountNonExpired() { // 만료되었는지 확인하는 로직 // true = 만료되지 않음 return true; } // 계정 잠금 여부 반환 @Override public boolean isAccountNonLocked() { // true = 잠금되지 않음 return true; } // 패스워드의 만료 여부 반환 @Override public boolean isCredentialsNonExpired() { // 패스워드가 만료되었는지 확인하는 로직 // true = 만료되지 않음 return true; } // 계정 사용 가능 여부 반환 @Override public boolean isEnabled() { // 계정이 사용 가능한지 확인하는 로직 // true = 사용 가능 return true; } @Override public Map<String, Object> getAttributes() { return attributes; } @Override public String getName() { return null; } } PrincipalDetailsService package com.example.project1.config.auth; import com.example.project1.entity.member.MemberEntity; import com.example.project1.repository.member.MemberRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; 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; // http://localhost:8080/login ← 이 때 동작을 함 @Service @RequiredArgsConstructor @Slf4j public class PrincipalDetailsService implements UserDetailsService { private MemberRepository memberRepository; // 시큐리티 session = Authentication = UserDetails // 함수 종료시 @AuthenticationPrincipal 어노테이션이 만들어진다. @Override public UserDetails loadUserByUsername(String userEmail) throws UsernameNotFoundException { MemberEntity member = memberRepository.findByUserEmail(userEmail); log.info("user : " + member); return new PrincipalDetails(member); } } OAuth2UserInfo package com.example.project1.config.oauth2.provider; public interface OAuth2UserInfo { String getProviderId(); String getProvider(); String getEmail(); String getName(); } PrincipalOauth2UserService package com.example.project1.config.oauth2; import com.example.project1.config.auth.PrincipalDetails; import com.example.project1.config.oauth2.provider.GoogleUserInfo; import com.example.project1.config.oauth2.provider.NaverUserInfo; import com.example.project1.config.oauth2.provider.OAuth2UserInfo; import com.example.project1.domain.member.MemberDTO; import com.example.project1.domain.member.UserType; import com.example.project1.entity.member.MemberEntity; import com.example.project1.repository.member.MemberRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; import java.util.Map; import java.util.Objects; import java.util.Optional; @Service @Slf4j @RequiredArgsConstructor public class PrincipalOauth2UserService extends DefaultOAuth2UserService { private final BCryptPasswordEncoder bCryptPasswordEncoder; private final MemberRepository memberRepository; // 구글로부터 받은 userReuest 데이터에 대한 후처리되는 함수 @Override public PrincipalDetails loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { // registrationId로 어떤 OAuth로 로그인 했는지 확인가능 log.info("clientRegistration : " + userRequest.getClientRegistration() ); log.info("accessToken : " + userRequest.getAccessToken().getTokenValue() ); OAuth2User oAuth2User = super.loadUser(userRequest); // 구글 로그인 버튼 클릭 →구글 로그인 창 → 로그인 완료 → code 를 리턴(OAuth-Client 라이브러리) → AccessToken 요청 // userRequest 정보 → 회원 프로필 받아야함(loadUser 함수 호출) → 구글로부터 회원 프로필을 받아준다. log.info("getAttributes : " + oAuth2User.getAttributes()); // 회원가입을 강제로 진행 OAuth2UserInfo oAuth2UserInfo = null; if(userRequest.getClientRegistration().getRegistrationId().equals("google")) { log.info("구글 로그인 요청"); oAuth2UserInfo = new GoogleUserInfo(oAuth2User.getAttributes()); } else if(userRequest.getClientRegistration().getRegistrationId().equals("naver")) { log.info("네이버 로그인 요청"); // 네이버는 response를 json으로 리턴을 해주는데 아래의 코드가 받아오는 코드다. // response={id=5SN-ML41CuX_iAUFH6-KWbuei8kRV9aTHdXOOXgL2K0, email=zxzz8014@naver.com, name=전혜영} // 위의 정보를 NaverUserInfo에 넘기면 oAuth2UserInfo = new NaverUserInfo((Map)oAuth2User.getAttributes().get("response")); } else { log.info("구글과 네이버만 지원합니다."); } String provider = oAuth2UserInfo.getProvider(); String providerId = oAuth2UserInfo.getProviderId(); // 예) google_109742856182916427686 String userName = provider + "_" + providerId; String password = bCryptPasswordEncoder.encode("get"); String email = oAuth2UserInfo.getEmail(); UserType role = UserType.USER; MemberEntity member = memberRepository.findByUserEmail(email); if(member == null) { log.info("OAuth 로그인이 최초입니다."); member = MemberEntity.builder() .userName(userName) .userPw(password) .userEmail(email) .userType(role) .provider(provider) .providerId(providerId) .build(); memberRepository.save(member); } else { log.info("로그인을 이미 한적이 있습니다. 당신은 자동회원가입이 되어 있습니다."); } return new PrincipalDetails(member, oAuth2User.getAttributes()); } } GoogleUserInfo package com.example.project1.config.oauth2.provider; import java.util.Map; public class GoogleUserInfo implements OAuth2UserInfo{ // getAttributes()를 받음 private Map<String, Object> attributes; public GoogleUserInfo(Map<String, Object> attributes) { this.attributes = attributes; } @Override public String getProviderId() { return (String) attributes.get("sub"); } @Override public String getProvider() { return "google"; } @Override public String getEmail() { return (String) attributes.get("email"); } @Override public String getName() { return (String) attributes.get("name"); } } NaverUserInfo package com.example.project1.config.oauth2.provider; import java.util.Map; public class NaverUserInfo implements OAuth2UserInfo{ // oauth2User.getAttributes()를 받음 private Map<String,Object> attributes; // PrincipalOauth2UserService에서 new NaverUserInfo((Map)oAuth2User.getAttributes().get("response"))로 // Oauth2 네이버 로그인 정보를 받아온다. // → {id=5SN-ML41CuX_iAUFH6-KWbuei8kRV9aTHdXOOXgL2K0, email=zxzz8014@naver.com, name=전혜영} public NaverUserInfo(Map<String, Object> attributes) { this.attributes = attributes; } @Override public String getProviderId() { return (String)attributes.get("id"); } @Override public String getProvider() { return "naver"; } @Override public String getEmail() { return (String)attributes.get("email"); } @Override public String getName() { return (String)attributes.get("name"); } } MemberController @GetMapping("/success-oauth") public ResponseEntity<?> createTokenForGoogle(@AuthenticationPrincipal OAuth2User oAuth2User) { if(oAuth2User == null) { log.info("받아올 정보가 없습니다 ㅠㅠ"); return ResponseEntity.status(HttpStatus.NOT_FOUND).body("정보가 없어...."); } else { log.info("oauth2User 정보를 받아오자 : " + oAuth2User); // OAuth2User에서 필요한 정보를 추출하여 UserDetails 객체를 생성합니다. ResponseEntity<TokenDTO> token = memberService.createToken(oAuth2User); log.info("token : " + token); return ResponseEntity.ok().body(token); } } TokenDTO package com.example.project1.domain.jwt; import com.example.project1.entity.jwt.TokenEntity; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; import java.util.Date; @Getter @ToString @NoArgsConstructor public class TokenDTO { private Long id; // JWT 대한 인증 타입, 여기서는 Bearer를 사용하고 // 이후 HTTP 헤더에 prefix로 붙여주는 타입 private String grantType; private String accessToken; private String refreshToken; private String userEmail; private String nickName; private Long userId; private Date accessTokenTime; private Date refreshTokenTime; @Builder public TokenDTO(String grantType, String accessToken, String refreshToken, String userEmail, String nickName, Long userId, Date accessTokenTime, Date refreshTokenTime) { this.grantType = grantType; this.accessToken = accessToken; this.refreshToken = refreshToken; this.userEmail = userEmail; this.nickName = nickName; this.userId = userId; this.accessTokenTime = accessTokenTime; this.refreshTokenTime = refreshTokenTime; } public static TokenDTO toTokenDTO(TokenEntity tokenEntity) { TokenDTO tokenDTO = TokenDTO.builder() .grantType(tokenEntity.getGrantType()) .accessToken(tokenEntity.getAccessToken()) .refreshToken(tokenEntity.getRefreshToken()) .userEmail(tokenEntity.getUserEmail()) .nickName(tokenEntity.getNickName()) .userId(tokenEntity.getId()) .accessTokenTime(tokenEntity.getAccessTokenTime()) .refreshTokenTime(tokenEntity.getRefreshTokenTime()) .build(); return tokenDTO; } } TokenEntity package com.example.project1.entity.jwt; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import java.util.Date; @Entity @Getter @NoArgsConstructor @ToString public class TokenEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String grantType; private String accessToken; private String refreshToken; private String userEmail; private String nickName; private Long userId; private Date accessTokenTime; private Date refreshTokenTime; @Builder public TokenEntity(Long id, String grantType, String accessToken, String refreshToken, String userEmail, String nickName, Long userId, Date accessTokenTime, Date refreshTokenTime) { this.id = id; this.grantType = grantType; this.accessToken = accessToken; this.refreshToken = refreshToken; this.userEmail = userEmail; this.nickName = nickName; this.userId = userId; this.accessTokenTime = accessTokenTime; this.refreshTokenTime = refreshTokenTime; } } MemberService package com.example.project1.service.member; import com.example.project1.config.jwt.JwtAuthenticationFilter; import com.example.project1.config.jwt.JwtProvider; import com.example.project1.domain.jwt.TokenDTO; import com.example.project1.domain.member.MemberDTO; import com.example.project1.domain.member.UserType; import com.example.project1.entity.jwt.TokenEntity; import com.example.project1.entity.member.MemberEntity; import com.example.project1.repository.jwt.TokenRepository; import com.example.project1.repository.member.MemberRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; import java.util.*; @Service @RequiredArgsConstructor @Slf4j public class MemberService { private final MemberRepository memberRepository; private final PasswordEncoder passwordEncoder; private final AuthenticationManagerBuilder authenticationManagerBuilder; private final JwtProvider jwtProvider; private final TokenRepository tokenRepository; // 회원가입 public String signUp(MemberDTO memberDTO) throws Exception { try { MemberEntity byUserEmail = memberRepository.findByUserEmail(memberDTO.getUserEmail()); if (byUserEmail != null) { return "이미 가입된 회원입니다."; } // 아이디가 없다면 DB에 넣어서 등록 해준다. MemberEntity member = MemberEntity.builder() .userEmail(memberDTO.getUserEmail()) .userPw(passwordEncoder.encode(memberDTO.getUserPw())) .userName(memberDTO.getUserName()) .nickName(memberDTO.getNickName()) .userType(memberDTO.getUserType()) .provider(memberDTO.getProvider()) .providerId(memberDTO.getProviderId()) .build(); log.info("member : " + member); MemberEntity save = memberRepository.save(member); // MemberDTO memberDTO1 = MemberDTO.toMemberDTO(Optional.of(save)); return "회원가입에 성공했습니다."; } catch (Exception e) { log.error(e.getMessage()); throw e; // 예외를 던져서 예외 처리를 컨트롤러로 전달 } } // 아이디 조회 public MemberDTO search(Long userId) { Optional<MemberEntity> searchId = memberRepository.findById(userId); MemberDTO memberDTO = MemberDTO.toMemberDTO(searchId); return memberDTO; } // 로그인 public ResponseEntity<TokenDTO> login(String userEmail, String userPw) throws Exception { MemberEntity findUser = memberRepository.findByUserEmail(userEmail); log.info("findUser : " + findUser); if (findUser != null) { Authentication authentication = new UsernamePasswordAuthenticationToken(userEmail, userPw); TokenDTO token = jwtProvider.createToken(authentication); // // Login ID/PW를 기반으로 UsernamePasswordAuthenticationToken 생성 token = TokenDTO.builder() .grantType(token.getGrantType()) .accessToken(token.getAccessToken()) .refreshToken(token.getRefreshToken()) .userEmail(findUser.getUserEmail()) .nickName(findUser.getNickName()) .userId(findUser.getUserId()) .build(); TokenEntity tokenEntity = TokenEntity.builder() .id(token.getId()) .grantType(token.getGrantType()) .accessToken(token.getAccessToken()) .refreshToken(token.getRefreshToken()) .userEmail(token.getUserEmail()) .nickName(token.getNickName()) .userId(token.getUserId()) .build(); log.info("token : " + tokenEntity); tokenRepository.save(tokenEntity); return new ResponseEntity<>(token, HttpStatus.OK); } else { return null; } } // 회원정보 수정 public MemberDTO update(MemberDTO memberDTO) { MemberEntity member = MemberEntity.builder() .userEmail(memberDTO.getUserEmail()) .userPw(passwordEncoder.encode(memberDTO.getUserPw())) .userName(memberDTO.getUserName()) .nickName(memberDTO.getNickName()) .userType(memberDTO.getUserType()) .provider(memberDTO.getProvider()) .providerId(memberDTO.getProviderId()) .build(); memberRepository.save(member); // 제대로 DTO 값이 엔티티에 넣어졌는지 확인하기 위해서 // 엔티티에 넣어주고 다시 DTO 객체로 바꿔서 리턴을 해줬습니다. MemberDTO memberDto = MemberDTO.toMemberDTO(Optional.of(member)); log.info("memberDto : " + memberDto); return memberDto; } // 소셜 로그인 성공시 jwt 반환 // OAuth2User에서 필요한 정보를 추출하여 UserDetails 객체를 생성하는 메서드 public ResponseEntity<TokenDTO> createToken(OAuth2User oAuth2User) { String userEmail = oAuth2User.getAttribute("email"); log.info("userEmail : " + userEmail); MemberEntity findMember = memberRepository.findByUserEmail(userEmail); // 권한 정보 추출 List<GrantedAuthority> authorities = getAuthoritiesForUser(findMember); // UserDetails 객체 생성 (사용자의 아이디 정보를 활용) // 첫 번째 인자 : username 사용자 아이디 // 두 번째 인자 : 사용자의 비밀번호 // 세 번째 인자 : 사용자의 권한 정보를 담은 컬렉션 UserDetails userDetails = new User(userEmail, null, authorities); log.info("userDetails : " + userDetails); TokenDTO token = jwtProvider.createToken2(userDetails); log.info("token : " + token); return ResponseEntity.ok().body(token); } private List<GrantedAuthority> getAuthoritiesForUser(MemberEntity member) { // 예시: 데이터베이스에서 사용자의 권한 정보를 조회하는 로직을 구현 // member 객체를 이용하여 데이터베이스에서 사용자의 권한 정보를 조회하는 예시로 대체합니다. UserType role = member.getUserType(); // 사용자의 권한 정보를 가져오는 로직 (예시) List<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority(role.name())); return authorities; } } 이렇게 했는데 PrincipalOauth2UserService에는 값이 잘 받아지는데 컨트롤러에서 @AuthenticationPrincipal OAuth2User oAuth2User으로 소셜 로그인 성공하면 정보를 뽑아서 JWT를 발급해주려고 하는데 소셜 로그인을 하고 log를 찍어보면 null이 뜹니다.... 어떻게 해야할까요
유요한
2023-07-20T07:19:18.379Z
댓글 1
좋아요 0
조회수 1196
미해결
스프링 프레임워크는 내 손에 [스프2탄]
답변 클릭시 답변 내용 등록하고 답변 클릭하면 400에러가 나는데 어떻게 처리해야되나요?? 올려주신 git에 SPRINGMVC08 임포트해서 실행해봤는데도 계속해서 똑같은 에러가 발생합니다..
spring jquery mvc jpa spring-security
최현제
2023-07-19T04:13:01.915Z
댓글 1
좋아요 0
조회수 409
미해결
실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
@RunWith(SpringRunner.class) @SpringBootTest public class MemberRepositoryTest { @Autowired MemberRepository memberRepository; @Test @Transactional @Rollback(false) public void testMember() throws Exception { //given Member member = new Member(); member.setUsername("memberA"); //when Long saveId = memberRepository.save(member); Member findMember = memberRepository.find(saveId); //then assertThat(findMember.getId()).isEqualTo(member.getId()); assertThat(findMember.getUsername()).isEqualTo(member.getUsername()); assertThat(member).isEqualTo(findMember); System.out.println("findMember == member: " + (findMember == member)); } } 위 코드에서 디비에 넣은 멤버와 찾은 멤버가 같은지를 비교를 isEqualTo를 사용을 하셨는데 이것은 제가 알기론 value부분만 같다면 true라는것으로 알고 있습니다. 같은 영속성 컨텍스트 안에서의 객체가 같은지 확인하려면 isSameAs가 맞지 않은지 의문이 생겼습니다!
java spring 웹앱 spring-boot jpa
hello0718
2023-07-18T11:35:38.362Z
댓글 1
좋아요 0
조회수 342
미해결
호돌맨의 요절복통 개발쇼 (SpringBoot, Vue.JS, AWS)
게시글 조회5-페이징처리(QueryDSL) 강의 내용 이후 전체 테스트를 시행하면 PostControllerTest에서 test5, test6에서 id값을 검증할때에 이전의 테스트에서 PostRepository에 저장이 되어 test5에서는 id값이 30이어야하는데 33이고, test6에서는 30이어야하는데 63으로 이전 테스트 수행 결과가 롤백되지 않아요. BeforeEach랑 Transactional 둘 다 사용해보았는데 테스트가 독립적으로 수행되지 않습니다. 찾아보니 sequence는 롤백되지 않는 것이 DB의 기본 규칙이라는데 호돌맨님은 강의에서 전체 테스트를 수행하여도 모든 테스트케이스가 통과되어서 코드에서 어떤 부분이 다른지 궁금합니다. package com.blog.controller; import com.blog.domain.Post; import com.blog.repository.PostRepository; import com.blog.request.PostCreate; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; 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.context.SpringBootTest; import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest @AutoConfigureMockMvc @Transactional class PostControllerTest { // TODO ObjectMapper 공부하기 @Autowired private ObjectMapper objectMapper; @Autowired private MockMvc mockMvc; @Autowired private PostRepository postRepository; @Test @DisplayName("/posts 요청 시 출력한다.") void test() throws Exception { // given PostCreate request = PostCreate.builder() .title("제목입니다.") .content("내용입니다.") .build(); String json = objectMapper.writeValueAsString(request); // expected mockMvc.perform(post("/posts") .contentType(APPLICATION_JSON) .content(json)) .andExpect(status().isOk()) .andDo(print()); } @Test @DisplayName("/posts 요청 시 title 값은 필수다.") void test2() throws Exception { // given PostCreate request = PostCreate.builder() .content("내용입니다.") .build(); String json = objectMapper.writeValueAsString(request); // expected mockMvc.perform(post("/posts") .contentType(APPLICATION_JSON) .content(json)) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value("400")) .andExpect(jsonPath("$.message").value("잘못된 요청입니다.")) .andExpect(jsonPath("$.validation.title").value("타이틀을 입력해주세요.")) .andDo(print()); } @Test @DisplayName("/posts 요청 시 DB에 값이 저장된다.") void test3() throws Exception { // given PostCreate request = PostCreate.builder() .title("제목입니다.") .content("내용입니다.") .build(); String json = objectMapper.writeValueAsString(request); // when mockMvc.perform(post("/posts") .contentType(APPLICATION_JSON) .content(json)) .andExpect(status().isOk()) .andDo(print()); // then assertEquals(1L, postRepository.count()); Post post = postRepository.findAll().get(0); assertEquals("제목입니다.", post.getTitle()); assertEquals("내용입니다.", post.getContent()); } @Test @DisplayName("글 1개 조회") void test4() throws Exception { // given Post post = Post.builder() .title("123456789012345") .content("bar") .build(); postRepository.save(post); // expected mockMvc.perform(get("/posts/{postId}", post.getId()) .contentType(APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(post.getId())) .andExpect(jsonPath("$.title").value("1234567890")) .andExpect(jsonPath("$.content").value("bar")) .andDo(print()); } @Test @DisplayName("글 여러개 조회") void test5() throws Exception { // given List<Post> requestPosts = IntStream.range(1, 31) .mapToObj(i -> Post.builder() .title("호돌맨 제목 " + i) .content("반포자이 " + i) .build()) .collect(Collectors.toList()); postRepository.saveAll(requestPosts); // expected mockMvc.perform(get("/posts?page=1&size=10") .contentType(APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.length()", is(10))) // .andExpect(jsonPath("$[0].id").value(30)) // sequence는 롤백되지 않음(DB 기본규칙) - mysql 계열이라면 auto_increment로 생성한 값은 commit, rollback 에 상관없이 한번 생성한 값을 그대로 유지합니다. .andExpect(jsonPath("$[0].title").value("호돌맨 제목 30")) .andExpect(jsonPath("$[0].content").value("반포자이 30")) .andDo(print()); } @Test @DisplayName("페이지를 0으로 요청하면 첫 페이지를 가져온다") void test6() throws Exception { // given List<Post> requestPosts = IntStream.range(1, 31) .mapToObj(i -> Post.builder() .title("호돌맨 제목 " + i) .content("반포자이 " + i) .build()) .collect(Collectors.toList()); postRepository.saveAll(requestPosts); // expected mockMvc.perform(get("/posts?page=0&size=10") .contentType(APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.length()", is(10))) // .andExpect(jsonPath("$[0].id").value(30)) .andExpect(jsonPath("$[0].title").value("호돌맨 제목 30")) .andExpect(jsonPath("$[0].content").value("반포자이 30")) .andDo(print()); } }
vue.js aws spring-boot jpa spring-security
enimbe99
2023-07-18T09:27:53.290Z
댓글 2
좋아요 0
조회수 528
해결됨
스프링부트 시큐리티 & JWT 강의
13분 정도에 if (jwtHeader == null || !jwtHeader.startsWith("Bearer")) { chain.doFilter(request, response); // 다시 필터를 타게 하는 것이다. return; } 위 코드를 작성하시는데 강의에서는 정상적인 사용자가 아닐 경우 다시 필터를 타게 한다고 하시는데 질문1. 다시 필터를 타게 하는 것이 아니라 로그아웃을 시키는 것이 정상적인 방법이지 않을까요? (이유는 다시 필터를 타도 이미 정상적인 사용자가 아닌데 다시 필터를 타게 할 이유가 없다고 생각합니다.) 질문2. 스스로 학습하다가 든 생각인데 클라이언트가 로그인 시도를 하면서 서버로 넘긴 데이터이니까 JwtAuthenticationFilter에서 ObjectMapper om = new ObjectMapper(); User user = om.readValue(request.getInputStream(), User.class); System.out.println(user); 이렇게 entity 객체로 받는데 dto 객체로 받는 것이 안전하지 않은가요? (물론 강의에서 dto class를 만들지는 않았지만) 질문3. JwtAuthorizationFilter에서 Authentication authentication = new UsernamePasswordAuthenticationToken(principalDetails, null, principalDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); 강제로 authentication을 만들어서 security session영역에 authentication을 집어넣는 이유가 궁금합니다.
Lego
2023-07-18T08:52:02.699Z
댓글 2
좋아요 0
조회수 1001
미해결
스프링 시큐리티
선생님 안녕하세요. 질좋은 강의로 열심히 공부중에 질문이 생겨 글 남겨봅니다. 현재 ajaxLoginProcessingFilter 및 AjaxAuthenticationProvider 만들어서 http.addFilterBefore(ajaxLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class); 와 같이 설정해서 사용중인데 SessionManagementFilter까지 작동을 안하지는 sessionManagement 기능이 작동을 안합니다. 혹시 커스텀 객체들을 사용하면서 세션 제어를 할 수 있는 방법이 있을까요?
java spring-boot spring-security
soobin7999
2023-07-18T06:10:31.858Z
댓글 2
좋아요 0
조회수 842
해결됨
Java/Spring 주니어 개발자를 위한 오답노트
어디까지 추상화 해야 하는가? 강의에서 2분 17초에 toDomain()으로 바꿔서 반환을 했는데 이렇게하면 JPA 엔티티가 아니라서 변경감지를 사용하지 못하는거 아닌가요? 이러한 방식으로 작성한다면 업데이트가 필요할 때는 어떤식으로 처리하나요?
yhwjd
2023-07-18T01:30:06.360Z
댓글 2
좋아요 3
조회수 4737
미해결
호돌맨의 요절복통 개발쇼 (SpringBoot, Vue.JS, AWS)
main resources/data.sql로 설정한 파일이 테스트시 적용이 되지만 @BeforeEach 메소드 내부의 UserRepository.deleteAll()동작으로 인하여 테스트시 User테이블이 비어있는 것 같습니다. 알아낸게 기분좋아서 공유드립니다. 호돌님 정말 좋은 강의 만들어주셔서 많이 배우고있습니다. 감사합니다!!
vue.js aws spring-boot jpa spring-security
luxleo
2023-07-17T15:02:01.802Z
댓글 1
좋아요 2
조회수 538
미해결
스프링 시큐리티
@Slf4j public class FormAuthenticationProvider implements AuthenticationProvider { @Autowired private UserDetailsService userDetailsService; private PasswordEncoder passwordEncoder; public FormAuthenticationProvider(PasswordEncoder passwordEncoder) { this.passwordEncoder = passwordEncoder; } @Override @Transactional public Authentication authenticate(Authentication authentication) throws AuthenticationException { ... } @Autowired로 (private UserDetailsService userDetailsService 를)의존성 주입해서 쓰고 있는 FormAuthenticationProvider class는 왜 !? @Component annotation이 없는 거죠?!?! 의존성 주입해서 사용하려는 class는 같은 spring에서 관리해야 하는거 아닌가요?!?!
java spring-boot spring-security
youjin_419
2023-07-17T03:02:33.618Z
댓글 1
좋아요 0
조회수 383
미해결
더 자바, 코드를 조작하는 다양한 방법
일단 새로운 프로젝트 만들고 premain 추가해주는 것 까진 강의를 그대로 따라하시면 됩니다. manifest plugin 부터 조금 차이가 있어서 거기부터 설명하면, build gradle 에 다음과 같이 추가한다. tasks.named('jar') { manifest { attributes( 'Implementation-Title': project.name, 'Implementation-Version': project.version, 'Premain-Class' : "com.java.magicianAgent.MagicianAgent", 'Can-Redefine-Classes' : true, 'Can-Retransform-Classes' : true) } } 터미널에서 다음 명령어를 통해 build 한다 ./gradlew clean build build.libs file 안에 있는 jar file 을 확인한다. (옵션) 강의에서와 마찬가지로 zip file 로 변경하면 확인가능합니다. 저같은 경우 SNAPSHOT.jar 과 SNAPSHOT -plain.jar 이렇게 2개가 생겼는데 SNAPSHOT.jar 은 제가 spring boot 로 실행서 그런지 관련 설정들이 보이고 SNAPSHOT -plain.jar 이 맞는거 같더라구요. jar file 의 절대 경로를 복사해 VM option 에 추가한다. 여기서부터는 다시 강의와 같습니다. VM option 이 안보이시면 오른쪽에 Modify options 클릭하면 add vm options 라고 보이실 겁니다. 이상한거나 궁금한거 있으시면 말씀해주세요. gradle 을 쓰시는 모든 분들도 마술을 성공시킵시다 하하
안호성
2023-07-16T11:58:01.361Z
댓글 1
좋아요 4
조회수 790
미해결
호돌맨의 요절복통 개발쇼 (SpringBoot, Vue.JS, AWS)
안녕하세요 강의 정말 잘보고있습니다!! 쿠키의 수명이 30일인데 만약 토큰의 세션값(UUID)이 탈취된다면 해당방식에선 어떻게 대응할수있을까요? httpOnly나 Secure옵션 등으로인해 현실적으로 탈취는 쉽지않을것이다.. 로만 생각하기엔 뭔가 찝찝한부분이 있는것같은데 제가 놓치고있는부분이 있을까요?? 또한 호돌님이 생각하시는 해당강의에서 다뤘던 쿠키에 세션토큰을 담아서 인증하는 방식의 단점이 궁금합니다!!
vue.js aws spring-boot jpa spring-security
2023-07-16T03:48:15.591Z
댓글 1
좋아요 0
조회수 482
미해결
스프링 시큐리티
그래서 해당 강의에서 config 메서드를 제가 구글링 해보면서 해봤는데 인증을 아에 거치질 않네요.. 아래 메서드 한 번 확인해주시고 틀린 부분 있으면 말씀 부탁드립니다.. 추가로 앞으로 계속 코드가 달라질 거 같은데 이런 부분은 어떻게 해결해야 하는지.. 계속 구글링 해가면서 해야하는건가요..? @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeRequests(authorizeRequests -> authorizeRequests .anyRequest().authenticated() ) .httpBasic( withDefaults ()) .formLogin(formLogin -> formLogin .loginPage("/loginPage") .defaultSuccessUrl("/") .usernameParameter("userId") .passwordParameter("passwd") .loginProcessingUrl("/login_proc") .successHandler(new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System. out .println("authentication : "+ authentication.getName()); response.sendRedirect("/"); } }) .failureHandler(new AuthenticationFailureHandler() { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { System. out .println("exception : "+ exception.getMessage()); response.sendRedirect("/login"); } }) .permitAll() ); return http.build (); }
java spring-boot spring-security
ak4686
2023-07-15T12:56:10.644Z
댓글 1
좋아요 0
조회수 1368
미해결
스프링 프레임워크는 내 손에 [스프1탄]
현재 hikariconfig 에러가 나고 있습니다. 어떤 해결책이 있을까요? 현재 roo-context.xml 과 pom.xml 은 작성한 것을 지우고 깃헙에서 코드를 그대로 복사해왔습니다. - Class 'com.zaxxer.hikari.HikariConfig' not found [config set: SpringMVC01/web-context] - Class 'com.zaxxer.hikari.HikariConfig' not found
jsp spring mvc spring-security
Peter Yu
2023-07-15T12:03:33.175Z
댓글 2
좋아요 0
조회수 729
미해결
10주완성 C++ 코딩테스트 | 알고리즘 코딩테스트
2일째 고민을 해보았는데.. 2%까지만 맞고 틀리다고 합니당..ㅠㅠ 포기하고 싶지 않은 마음에 도움을 요청합니다!! 제가 짠 코드는 이렇습니다! import java.io.*; import java.util.*; // 불이 하나가 아닌 여러개일 수 있다. public class Main { public static int R; public static int C; public static String[][] map; public static int[][] visitedFire; public static int[][] visitedHuman; public static int[] dx; public static int[] dy; public static Node fireLocation; public static Node humanLocation; public static Queue<Node> fireQueue; public static void main(String[] args) throws IOException { // 초기화 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); StringTokenizer st = new StringTokenizer(br.readLine()); R = Integer.parseInt(st.nextToken()); C = Integer.parseInt(st.nextToken()); map = new String[R][C]; // 0으로 초기화 visitedFire = new int[R][C]; visitedHuman = new int[R][C]; dx = new int[]{0,1,0,-1}; dy = new int[]{-1,0,1,0}; fireQueue = new LinkedList<>(); // 맵 입력 for (int i = 0; i < R; i++) { String S = br.readLine(); for (int j = 0; j < C; j++) { // 불 좌표, 지훈이 좌표 찾기 map[i][j] = String.valueOf(S.charAt(j)); if(map[i][j].equals("J")){ humanLocation = new Node(j,i); map[i][j] = "."; // .으로 변경 }else if(map[i][j].equals("F")){ // 불이 여러개, 불이 아무것도 없을 수 있다. -> 반레 fireLocation = new Node(j,i); fireQueue.add(fireLocation); visitedFire[i][j] = 1; } } } // (1) 불이 이동할 수 있는 최단경로 fireBfs(); // (2) 사람이 이동할 수 있는 최단경로 + 길 비교해야함 humanBfs(humanLocation.y, humanLocation.x); // 가능한 길 찾아서 int result = Integer.MAX_VALUE; // (3) 가장 짧은 최단거리 찾기 (가장자리 찾기) for (int i = 0; i < visitedHuman.length; i++) { for (int j = 0; j < visitedHuman[i].length; j++) { if((0<i && i<R-1) && 0 < j && j < C-1)continue; // 가장자리가 아닌 경우 pass if(visitedHuman[i][j] > 0) { result = Math.min(result, visitedHuman[i][j]); } } } if(result == Integer.MAX_VALUE){ System.out.println("IMPOSSIBLE"); }else{ System.out.println(result); } } public static void humanBfs(int y, int x){ // tkfk visitedHuman[y][x] = 1; Node node = new Node(x,y); Queue<Node> queue = new LinkedList<>(); queue.add(node); while(queue.size()>0){ Node cur = queue.poll(); for (int i = 0; i < 4; i++) { int nx = cur.x + dx[i]; int ny = cur.y + dy[i]; if(nx < 0 || nx >= C || ny < 0 || ny >= R) continue; if(visitedHuman[ny][nx] == 0 && (map[ny][nx].equals("."))){ if(visitedHuman[ny][nx] < visitedFire[ny][nx] || visitedFire[ny][nx] == 0){ visitedHuman[ny][nx] = visitedHuman[cur.y][cur.x] + 1; Node next = new Node(nx,ny); queue.add(next); } } } } } public static void fireBfs(){ while(fireQueue.size()>0){ Node cur = fireQueue.poll(); for (int i = 0; i < 4; i++) { // 4방향 탐지 int nx = cur.x + dx[i]; int ny = cur.y + dy[i]; if(nx < 0 || nx >= C || ny < 0 || ny >= R) continue; if(visitedFire[ny][nx] == 0 && (map[ny][nx].equals("."))){ visitedFire[ny][nx] = visitedFire[cur.y][cur.x] + 1; // 이동함을 표현 Node next = new Node(nx,ny); fireQueue.add(next); } } } } public static class Node { int x; int y; Node(int x, int y){ this.x = x; this.y = y; } } }
2023-07-14T07:57:15.796Z
댓글 2
좋아요 0
조회수 363
미해결
스프링부트 시큐리티 & JWT 강의
강의 잘 들었습니다. 감사합니다.(_ _) 완강을 하고 마지막으로 postman으로 http://localhost:8080/api/v1/user/asdf로 send해서 실험을 하고 있었는데(login은 ROLE_USER 권한만 가진 아이디로 로그인 했음) 404가 에러가 뜹니다. (manager, admin은 권한이 없기에 당연하게 403에러가 뜸) 404에러가 뜨는 이유가 권한이 있지만 redirect할 곳이 없어서 404에러가 발생하는 것인가요?
Lego
2023-07-13T15:10:47.106Z
댓글 2
좋아요 0
조회수 4821
미해결
Practical Testing: 실용적인 테스트 가이드
안녕하세요 강의를 듣다가 궁금증이 생겨서 질문을 드립니다. void init() { // given List<Product> products = List.of( createProduct("001", 1000), createProduct("002", 2000) ); // when Order order = Order.create(products, LocalDateTime.now()); // then assertThat(order.getOrderStatus()).isEqualByComparingTo(OrderStatus.INIT); } 강의에서 OrderTest 클래스 테스트 과정에서 enum을 비교할때 isEqualByComparingTo를 사용하셨습니다. assertThat(order.getOrderStatus()).isEqualTo(OrderStatus.INIT); 위와 같이 isEqualTo를 사용해도 enum을 비교할 수 있는데, 혹시 isEqualTo 대신 isEqualByComparingTo를 사용한 이유를 알 수 있을까요? isEqualByComparing가 정의된 AbstractComparableAssert.java를 확인해보면 BigDecimal을 예시로 들어서 사용법을 설명하고 있습니다. BigDecimal의 예시로 Enum을 써야 하는 방법을 생각해보았는데, 유추(?)가 잘 되지 않아서 질문을 남깁니다. 항상 좋은 강의 제공해주셔서 감사합니다!!😀😀
amenable
2023-07-12T04:32:23.659Z
댓글 2
좋아요 1
조회수 847
미해결
스프링부트 시큐리티 & JWT 강의
SecurityConfig.java @Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig extends WebSecurityConfigurerAdapter { private final SecurityDetailsService securityDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http // .csrf().disable() .authorizeRequests() .antMatchers("/user/**").authenticated() .antMatchers("/admin/**").access("hasRole('ADMIN')") .anyRequest().permitAll() .and() .formLogin() .loginPage("/login") .loginProcessingUrl("/login") .defaultSuccessUrl("/home") .usernameParameter("userEmail") .and() .logout() .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .logoutSuccessUrl("/login") ; } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } SecurityDetails.java @RequiredArgsConstructor public class SecurityDetails implements UserDetails { private final UserEntity userEntity; @Override public Collection<? extends GrantedAuthority> getAuthorities() { Collection<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(new GrantedAuthority() { @Override public String getAuthority() { return userEntity.getAuthority().toString(); } }); return authorities; } @Override public String getPassword() { return userEntity.getUserPassword(); } @Override public String getUsername() { return userEntity.getUserEmail(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } } SecurityDetailsService.java @Service @RequiredArgsConstructor public class SecurityDetailsService implements UserDetailsService { private final UserRepository userRepository; @Override public UserDetails loadUserByUsername(String userEmail) throws UsernameNotFoundException { UserEntity userEntity = userRepository.findByUserEmail(userEmail); if(userEntity != null) return new SecurityDetails(userEntity); return null; } } SecurityDetails에서 return값이 boolean인 override 받는 메소드들 다 true로 해줬고 login페이지나 home페이지에는 이미지도 없어서 문제될 게 없다고 생각되는데 도통 이유를 모르겠습니다ㅠㅠㅠ
유은혜
2023-07-11T09:27:39.507Z
댓글 1
좋아요 0
조회수 1427
미해결
스프링 프레임워크는 내 손에 [스프1탄]
커리큘럼 소개하실 때 스프 2탄 mvc07부분에서 리액트와의 연동 방법을 간략하게나마 보여주신다고 하셨는데 스프2탄 커리큘럼에는 없는 것 같아서요! 스프2탄 mvc07 강의 듣다보면 내용이 나오나요??
jsp spring mvc spring-security
즐코
2023-07-11T07:16:52.040Z
댓글 1
좋아요 0
조회수 289