묻고 답해요
131만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결스프링 프레임워크는 내 손에 [스프1탄]
github repository 어떤 것일까요?
현재 스프1탄 mvc01버전 듣고 있는데 hikari관련 오류가 나서 깃허브가서 소스코드를 가져오려고 합니다.깃 들어가니 레포지토리가 정리가 잘 안되어있어서 찾지를 못하겠는데어떤 레포지토리를 봐야하나요???
-
해결됨스프링 시큐리티
경로설정
protected AjaxLoginProcessingFilter(String defaultFilterProcessesUrl) {super(new AntPathRequestMatcher("/xxx"));}이렇게 설정하는건 자바에서 직접 설정하는거고이전에 설정파일에서http.authorizeRequests().antMatchers("/xxx") 경로 설정한거랑두개다 같다고 생각하면 되는거죠 ?
-
미해결스프링 시큐리티
인증 거부 처리 강의 중 질문
인증 거부 처리 -8:42 ) LoginController 에서 Account account = (Account) authentication.getPrincipal();이 가능 한 이유가 저희가 인증 객체를CustomAuthenticationProvider코드에서 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(accountContext.getAccount(), null, accountContext.getAuthorities()); 로 토큰을 만들어서 스프링이 내부적으로 SecurityContext 안에 accountContext.getAccount(); (인증객체) 을 저장했기 때문에 가능하다고 이해했는데 맞나요 ?
-
미해결스프링 시큐리티
CustomAuthenticationProvider 추가
안녕하세요기존에는 CustomUserDetailsService 로 인증처리를 완료했는데 이때 AuthenticationProvider의 anthenticate 메서드 역할은 우리가 만든 loadUserByUsername + 스프링 시큐리티가 기본으로 제공하는 역할을 이용하였고 이번에 새로 추가한 CustomAuthenticationProvider 는AuthenticationProvider의 anthenticate 역할을 커스터마이징 했다고 생각하면 되는걸까요 ?
-
미해결스프링부트 시큐리티 & JWT 강의
권한 인증 403가 뜹니다
https://github.com/bgseong/Security-test public class JwtAuthorizationFilter extends BasicAuthenticationFilter { private TokenService tokenService; public JwtAuthorizationFilter(AuthenticationManager authenticationManager, TokenService tokenService) { super(authenticationManager); this.tokenService = tokenService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { super.doFilterInternal(request, response, chain); String token = tokenService.resolveToken(request); if(token == null){ chain.doFilter(request, response); return; } if (tokenService.validateToken(token)) { Authentication authentication = tokenService.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authentication); System.out.println(SecurityContextHolder.getContext().getAuthentication()); } chain.doFilter(request,response); } }@EnableWebSecurity @Configuration @RequiredArgsConstructor @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig { @Autowired TokenService tokenService; @Autowired CorsConfig corsConfig; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{ http .csrf().disable() .httpBasic().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .formLogin().disable() .apply(new MyCustomDsl()) .and() .authorizeHttpRequests(authorize -> authorize .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // 특정 정적 리소스 허용 .requestMatchers("/api/v1/user/**").hasAnyRole("ADMIN", "MANAGER") .requestMatchers("/api/v1/manager/**").hasRole("ADMIN") .requestMatchers("/api/v1/admin/**").hasRole("ROLE_ADMIN") .anyRequest().permitAll()); return http.build(); } public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> { @Override public void configure(HttpSecurity http) throws Exception { AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class); http .addFilter(corsConfig.corsFilter()) .addFilter(new LoginFilter(authenticationManager,tokenService)) .addFilter(new JwtAuthorizationFilter(authenticationManager,tokenService)); } } }@Component public class TokenService implements InitializingBean { private final UserRepository usersrepository; private final Logger logger = LoggerFactory.getLogger(TokenService.class); private static final String AUTHORITIES_KEY = "auth"; private final String secret; private final long accessTokenValidityInMilliseconds; private final long refreshTokenValidityInMilliseconds; public static final String AUTHORIZATION_HEADER = "Authorization"; public static final String REFRESHTOKEN_HEADER = "RefreshToken"; private Key key; public TokenService( UserRepository usersrepository, @Value("${spring.jwt.secret}") String secret, @Value("${spring.jwt.token-validity-in-seconds}") long tokenValidityInSeconds) { this.usersrepository = usersrepository; this.secret = secret; this.accessTokenValidityInMilliseconds = tokenValidityInSeconds * 500; this.refreshTokenValidityInMilliseconds = tokenValidityInSeconds * 1000 * 336; } @Override public void afterPropertiesSet() { byte[] keyBytes = Decoders.BASE64.decode(secret); this.key = Keys.hmacShaKeyFor(keyBytes); } public String createAccessToken(PrincipalDetails principalDetails) { return createAccessToken(principalDetails.getUser().getEmail(), principalDetails.getAuthorities()); } public String createRefreshToken(PrincipalDetails principalDetails) { return createRefreshToken(principalDetails.getUser().getEmail(), principalDetails.getAuthorities()); } public String createAccessToken(String email, Collection<? extends GrantedAuthority> inputAuthorities) { String authorities = inputAuthorities.stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(",")); long now = (new Date()).getTime(); String accessToken = Jwts.builder() .setSubject(email) .claim(AUTHORITIES_KEY, authorities) .signWith(key, SignatureAlgorithm.HS512) .setExpiration(new Date(now + this.accessTokenValidityInMilliseconds)) .compact(); return accessToken; } public String createRefreshToken(String email, Collection<? extends GrantedAuthority> inputAuthorities) { String authorities = inputAuthorities.stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(",")); long now = (new Date()).getTime(); String Token = Jwts.builder() .setSubject(email) .claim(AUTHORITIES_KEY, authorities) .signWith(key, SignatureAlgorithm.HS512) .setExpiration(new Date(now + this.refreshTokenValidityInMilliseconds)) .compact(); return Token; } public Authentication getAuthentication(String token) { Claims claims = Jwts .parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody(); User user = usersrepository.findByEmail(claims.get("sub",String.class)); Collection<? extends GrantedAuthority> authorities = Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(",")) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); PrincipalDetails principal = new PrincipalDetails(user); return new UsernamePasswordAuthenticationToken(principal, null, authorities); } public String resolveToken(HttpServletRequest request) { String bearerToken = request.getHeader(AUTHORIZATION_HEADER); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } public boolean validateToken(String token) { try { Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); return true; } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { logger.info("worng JWT sign"); } catch (ExpiredJwtException e) { logger.info("expire JWT"); } catch (UnsupportedJwtException e) { logger.info("No support JWT"); } catch (IllegalArgumentException e) { logger.info("JWT is worng"); } return false; } }이렇게 구성해 놨습니다. 그런데 모든 권한이 적용된 url에 접근을 하면 403 에러가 뜹니다.필터에서 SecurityContextHolder를 출력하면 아래와 같이 출력이 되는 걸 확인했고[Principal=com.securitytest.Securitytest.auth.PrincipalDetails@45095607, Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_ADMIN]]컨트롤러에서 PrincipalDetails를 호출해보니, null이라서 오류가 난다고 뜹니다. 무엇이 문제일까요ㅠㅠ..
-
미해결스프링부트 시큐리티 & JWT 강의
1강 환경설정을 따라하는데 run하면 에러가 나요
설정 후 run 하면If you want an embedded database (H2, HSQL or Derby), please put it on the classpath. 라는 문구가 뜹니다. 그래서 mysql에 다시 한번 sql문구를 쓰고 다시 처음부터 강의대로 해봤는데도 안되서 구글링해도 모르겠어서요ㅠㅠ
-
해결됨호돌맨의 요절복통 개발쇼 (SpringBoot, Vue.JS, AWS)
호돌센세 안녕하세요 auth.http에서 로그인시 자꾸 500 에러가 뜹니다..
https://github.com/incheol789/hodolog.git깃허브에 파일 올려두었습니다..날이 많이 덥습니다.. 더위 조심하세요!
-
해결됨스프링 시큐리티
스프링부트 버전 2.7이상 authenticationManagerBean 등록방법을 잘 모르겠습니다.
안녕하세요 선생님 일단 좋은 강의 감사합니다.선생님 강의를 들으면서 2.7버전으로 진행하고 있는데매니저 를 등록할때부터 막혔습니다. 제 나름대로 소스도 좀 제가 평소에 하던 방식으로 하고있었는데 강의 버전이랑 맞지 않으니 힘드네요 ㅠ 이 부분은 스프링부트 2.7이상에서는 어떻게 하면될까요? 깃허브 주소 : https://github.com/PeachCoolPiece/corespringsecurity 입니다.
-
미해결스프링부트 시큐리티 & JWT 강의
소셜 로그인 후 JWT 발급
아무리 해도 해결이 안되네요...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.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.security.crypto.bcrypt.BCryptPasswordEncoder; 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; @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 in PrincipalOauth2UserService : " + userRequest.getClientRegistration() ); log.info("accessToken in PrincipalOauth2UserService : " + userRequest.getAccessToken().getTokenValue() ); OAuth2User oAuth2User = super.loadUser(userRequest); // 구글 로그인 버튼 클릭 →구글 로그인 창 → 로그인 완료 → code 를 리턴(OAuth-Client 라이브러리) → AccessToken 요청 // userRequest 정보 → 회원 프로필 받아야함(loadUser 함수 호출) → 구글로부터 회원 프로필을 받아준다. log.info("getAttributes in PrincipalOauth2UserService : " + 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("구글과 네이버만 지원합니다."); } // 사용자가 로그인한 소셜 서비스(provider)를 가져옵니다. // 예를 들어, "google" 또는 "naver"와 같은 값을 가질 수 있습니다. String provider = oAuth2UserInfo.getProvider(); // 사용자의 소셜 서비스(provider)에서 발급된 고유한 식별자를 가져옵니다. // 이 값은 해당 소셜 서비스에서 유니크한 사용자를 식별하는 용도로 사용됩니다. String providerId = oAuth2UserInfo.getProviderId(); // 예) google_109742856182916427686 String userName = provider + "_" + providerId; String password = bCryptPasswordEncoder.encode("get"); // 사용자의 이메일 주소를 가져옵니다. 소셜 서비스에서 제공하는 이메일 정보를 사용합니다. String email = oAuth2UserInfo.getEmail(); // 사용자의 권한 정보를 설정합니다. UserType. // 여기서는 소셜로그인으로 가입하면 무조건 User로 권한을 주는 방식으로 했습니다. UserType role = UserType.USER; // 이메일 주소를 사용하여 이미 해당 이메일로 가입된 사용자가 있는지 데이터베이스에서 조회합니다. MemberEntity member = memberRepository.findByUserEmail(email); if(member == null) { log.info("OAuth 로그인이 최초입니다."); log.info("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓"); log.info("OAuth 자동 회원가입을 진행합니다."); member = MemberEntity.builder() .userName(userName) .userPw(password) .userEmail(email) .userType(role) .provider(provider) .providerId(providerId) .build(); log.info("userEmail in PrincipalOauth2UserService : " + member.getUserEmail()); log.info("userName in PrincipalOauth2UserService : " + member.getUserName()); log.info("userPw in PrincipalOauth2UserService : " + member.getUserPw()); log.info("userType in PrincipalOauth2UserService : " + member.getUserType()); log.info("provider in PrincipalOauth2UserService : " + member.getProvider()); log.info("providerId in PrincipalOauth2UserService : " + member.getProviderId()); memberRepository.save(member); } else { log.info("로그인을 이미 한적이 있습니다. 당신은 자동회원가입이 되어 있습니다."); MemberEntity findUser = memberRepository.findByUserEmail(email); log.info("findUser in PrincipalOauth2UserService : " + findUser); } OAuth2User oAuth2User1 = super.loadUser(userRequest); log.info("getAttributes in PrincipalOauth2UserService : " + oAuth2User1.getAttributes()); // attributes가 있는 생성자를 사용하여 PrincipalDetails 객체 생성 // 소셜 로그인인 경우에는 attributes도 함께 가지고 있는 PrincipalDetails 객체를 생성하게 됩니다. PrincipalDetails principalDetails = new PrincipalDetails(member, oAuth2User.getAttributes()); log.info("principalDetails in PrincipalOauth2UserService : " + principalDetails); return principalDetails; } }package com.example.project1.config.oauth2.provider; public interface OAuth2UserInfo { String getProviderId(); String getProvider(); String getEmail(); String getName(); }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"); } }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"); } }package com.example.project1.config.auth; import com.example.project1.entity.member.MemberEntity; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; 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 @NoArgsConstructor @Slf4j public class PrincipalDetails implements UserDetails, OAuth2User { // 일반 로그인 정보를 저장하기 위한 필드 private MemberEntity member; // OAuth2 로그인 정보를 저장하기 위한 필드 // attributes는 Spring Security에서 OAuth2 인증을 수행한 후에 사용자에 대한 추가 정보를 저장하는 데 사용되는 맵(Map)입니다. // OAuth2 인증은 사용자 인증 후에 액세스 토큰(Access Token)을 발급받게 되는데, // 이 토큰을 사용하여 OAuth2 서비스(provider)로부터 사용자의 프로필 정보를 요청할 수 있습니다. // 예를 들어, 소셜 로그인을 사용한 경우에는 attributes에는 사용자의 소셜 서비스(provider)에서 제공하는 프로필 정보가 담겨 있습니다. // 소셜 로그인 서비스(provider)마다 제공하는 프로필 정보가 다를 수 있습니다. // 일반적으로 attributes에는 사용자의 아이디(ID), 이름, 이메일 주소, 프로필 사진 URL 등의 정보가 포함됩니다. /* * 구글의 경우 * { "sub": "100882758450498962866", // 구글에서 발급하는 고유 사용자 ID "name": "John Doe", // 사용자 이름 "given_name": "John", // 이름(이름 부분) "family_name": "Doe", // 성(성(성) 부분) "picture": "https://lh3.googleusercontent.com/a/AAcHTtdzQomNwZCruCcM0Eurcf8hAgBHcgwvbXEBQdw3olPkSg=s96-c", // 프로필 사진 URL "email": "johndoe@example.com", // 이메일 주소 "email_verified": true, // 이메일 주소 인증 여부 "locale": "en" // 지역 설정 } * */ 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())); log.info("collection : " + collection); return collection; } // 사용자 패스워드를 반환 @Override public String getPassword() { log.info("password : "+ member.getUserPw()); return member.getUserPw(); } // 사용자 이름 반환 @Override public String getUsername() { log.info("id : " + member.getUserEmail()); 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() { log.info("attributes : " + attributes); return attributes; } @Override // OAuth2 인증에서는 사용되지 않는 메서드이므로 null 반환 public String getName() { return null; } } // Oauth2 google로 JWT 발급 @GetMapping("/success-oauth") public ResponseEntity<?> createTokenForGoogle(@AuthenticationPrincipal OAuth2User oAuth2User , @RequestBody MemberDTO member) { Object email = oAuth2User.getAttribute("email"); log.info("oAuth2User : " + email); if(oAuth2User == null) { log.info("받아올 정보가 없습니다 ㅠㅠ"); return ResponseEntity.status(HttpStatus.NOT_FOUND).body("정보가 없어...."); } else { // OAuth2User에서 필요한 정보를 추출하여 UserDetails 객체를 생성합니다. ResponseEntity<TokenDTO> token = memberService.createToken((String) email, member); log.info("token : " + token); return ResponseEntity.ok().body(token); } } // 소셜 로그인 성공시 jwt 반환 // OAuth2User에서 필요한 정보를 추출하여 UserDetails 객체를 생성하는 메서드 public ResponseEntity<TokenDTO> createToken(String userEmail, MemberDTO member) { MemberEntity findUser = memberRepository.findByUserEmail(userEmail); log.info("findUser in MemberService : " + findUser); findUser = MemberEntity.builder() // id를 식별해서 수정 // 이거 없으면 새로 저장하기 됨 // findUser꺼를 쓰면 db에 입력된거를 사용하기 때문에 // 클라이언트에서 userEmail을 전달하더라도 서버에서 기존 값으로 업데이트가 이루어질 것입니다. // 이렇게 하면 userEmail을 수정하지 못하게 할 수 있습니다. .userId(findUser.getUserId()) .userEmail(findUser.getUserEmail()) .userPw(passwordEncoder.encode(findUser.getUserPw())) .userName(findUser.getUserName()) .nickName(member.getNickName()) .userType(findUser.getUserType()) .address(AddressEntity.builder() .userAddr(member.getAddressDTO().getUserAddr()) .userAddrDetail(member.getAddressDTO().getUserAddrDetail()) .userAddrEtc(member.getAddressDTO().getUserAddrEtc()) .build()) .build(); memberRepository.save(findUser); // 권한 정보 추출 List<GrantedAuthority> authorities = getAuthoritiesForUser(findUser); // UserDetails 객체 생성 (사용자의 아이디 정보를 활용) // 첫 번째 인자 : username 사용자 아이디 // 두 번째 인자 : 사용자의 비밀번호 // 세 번째 인자 : 사용자의 권한 정보를 담은 컬렉션 UserDetails userDetails = new User(userEmail, null, authorities); log.info("userDetails in MemberService : " + userDetails); TokenDTO token = jwtProvider.createToken2(userDetails); log.info("token in MemberService : " + token); return ResponseEntity.ok().body(token); } // 소셜 로그인 성공시 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(AUTHORITIES_KEY, userDetails.getAuthorities()); log.info("claims : " + claims); // 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(); TokenDTO tokenDTO = TokenDTO.builder() .grantType("Bearer ") .accessToken(accessToken) .refreshToken(refreshToken) .userEmail(userDetails.getUsername()) .build(); log.info("tokenDTO in JwtProvider : " + tokenDTO); return tokenDTO; } http // oauth2Login() 메서드는 OAuth 2.0 프로토콜을 사용하여 소셜 로그인을 처리하는 기능을 제공합니다. .oauth2Login() // OAuth2 로그인 성공 이후 사용자 정보를 가져올 때 설정 담당 .userInfoEndpoint() // OAuth2 로그인 성공 시, 후작업을 진행할 서비스 .userService(principalOauth2UserService) .and() .defaultSuccessUrl("/success-oauth");소셜 로그인 후 쇼핑몰 프로젝트라 주소가 필 수라서 JSON을 추가로 받아서 주소를 DB에 저장 후 JWT를 반환해주는 로직을 구성하려고 합니다. 소셜 로그인을 했을 때 PrincipalOauth2UserService여기서 모든 값이 제대로 들어갔고 PrincipalDetails principalDetails = new PrincipalDetails(member, oAuth2User.getAttributes()); log.info("principalDetails in PrincipalOauth2UserService : " + principalDetails); return principalDetails;로그를 찍어본 결과 제대로 값이 로그에 찍혔습니다. 이거를 principalDetails에 보내줬으니 principalDetails 클래스에서도 제대로 받아졌는지 확인해봤습니다. Oaut2User를 상속받아서 오버라이드 한 @Override public Map<String, Object> getAttributes() { log.info("attributes : " + attributes); return attributes; }여기서도 로그에 제대로 값이 나왔습니다.여기서부터가 문제인데 컨트롤러에서 @AuthenticationPrincipal OAuth2User oAuth2User로 소셜 로그인한 정보를 받아와서 토큰을 만들려고 하는데 null이 뜹니다. 별방법을 다했는데 다른 부분은 다 해결을 했는데 이부분이 해결이 안되네요 ㅠㅠ
-
미해결스프링 시큐리티
SecurityContextHolder.clearContext()
는 최종 응답시 공통으로 실행되는데는모든 정보를 삭제하는게 아니라 현재 사용자의 로컬 쓰레드만 삭제하는것이죠 ?(다른 사용자들의 로그인 인증정보까지 삭제하면 안되니깐)제 생각이 맞는건가요?
-
미해결스프링 시큐리티
SecurityContext
SecurityContext 안에 null 이 저장된 상태라는 것은SecurityContext 안에 Anonymous AuthenticationToken 을 저장했다고 생각하면 일치한가요 ? 이 그림에서 SecurityContext 안에 null 이라는말을Anonymous AuthenticationToken 이 저장되어 있다고 대체가 가능하냐는 질문이였습니다 ! 감사합니다
-
미해결스프링 시큐리티
ConcurrentSessionFilter 매 요청 시 만료 여부 판단
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요.ConcurrentSessionFilter는 매 요청 시 세션 만료 여부 즉 session.isExpired를 판단한다고 하셨습니다. 이 Filter를 설명해주실 때 사용자1, 2를 나눠서 설명해주셨는데 사용자1이 Logout 처리가 된 이후 사용자2가 요청을 보낼 때에도 사용자2의 세션 만료 여부를 체크하는지 궁금합니다.
-
미해결스프링 시큐리티
인가 프로세스 DB 연동 웹 계층 구현 - 2) 관리자 시스템 - 권한 도메인, 서비스, 리포지토리 구성 질문
안녕하세요 강사님. 인가 프로세스 DB 연동 웹 계층 구현 섹션에서 "2) 관리자 시스템 - 권한 도메인, 서비스, 리포지토리 구성" 강의 수강 중 궁금한 점이 있어 질문 드립니다. Resources 테이블과 연관된 메뉴인 "리소스 관리"에서 순서 필드는 어떤 용도로 사용될까요?단순히 리소스 데이터를 조회할 때 order by 를 통한 정렬을 위한 필드인지, 다른 용도로도 사용되는지 궁금합니다.
-
미해결스프링부트 시큐리티 & JWT 강의
마지막 강의 질문드립니다.
여기서 super.doFilterInternal(request, response, chain);위 문장을 지워주셨는데 해당 줄을 지우면 회원가입 로직이 컨트롤러를 타지 않습니다.회원가입은 /join으로 매핑되어 있는데 JwtAuthorizationFilter의 doFilterInternal() 메소드를 타고jwtHeader 값이 없기 때문에 return을 만나 컨트롤러를 타지 않는 것 같습니다. 반대로 super.doFilterInternal(request, response, chain); 주석 해제하면 회원가입 로직은 진행됩니다만 마지막 강의에서 인증이 되지 않는 문제가 계속 일어나고 있습니다! SecurityConfig 클래스 코드입니다! 부족한 지식으로 제 생각이 다를 수 있지만 문제를 해결하지 못하고 있어 질문 드립니다ㅠㅠ
-
미해결스프링 시큐리티
세션 클러스터링을 한 이후 동시로그인 제한이 안되는데요
안녕하십니까강사님 강의를 듣고 여기 커뮤니티에 나름 최신 소스 받아서 시큐리티 구현중인데요이중화 환경에서 톰캣을 통해 세션을 공유하는것까지는 되었는데요동시 로그인제한이 안되는데 추가적인 설정을 무엇을 해야할지 모르겠는데도움좀 받을수 있을까요? 스프링 2.7.10스프링 시큐리티 5.7.7 // 시큐리티 설정부분@Slf4j @RequiredArgsConstructor @EnableWebSecurity @Configuration public class WebSecurityConfig { private final MemberRepository memberRepository; @Bean public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { // 경로 설정 http. authorizeRequests() .antMatchers().permitAll() .anyRequest().authenticated() ; // 동시 로그인 제한을 설정 http .sessionManagement() .maximumSessions(1) .expiredUrl("/error/expired") .and().sessionCreationPolicy(SessionCreationPolicy.ALWAYS) ; http.logout() .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .invalidateHttpSession(true) .clearAuthentication(true) .deleteCookies("JSESSIONID") ; // 로그인... 인증 권한 체크... // 로그인 return http.build(); } /** * 여기에 Authentication 을 설정하도록 */ @Bean AuthenticationManager authenticationManager(AuthenticationConfiguration authConfiguration) throws Exception { return authConfiguration.getAuthenticationManager(); } @Bean public static PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } // 톰캣 클러스터링 부분 (https://happy-jjang-a.tistory.com/155를 참조했습니다.)@Configuration public class TomcatClusterConfig implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> { @Override public void customize(final TomcatServletWebServerFactory factory) { factory.addContextCustomizers(new TomcatClusterContextCustomizer()); } } class TomcatClusterContextCustomizer implements TomcatContextCustomizer { @Override public void customize(final Context context) { context.setDistributable(true); DeltaManager manager = new DeltaManager(); manager.setExpireSessionsOnShutdown(false); manager.setNotifyListenersOnReplication(true); context.setManager(manager); configureCluster((Engine) context.getParent().getParent()); } private void configureCluster(Engine engine) { //cluster SimpleTcpCluster cluster = new SimpleTcpCluster(); cluster.setChannelSendOptions(6); //channel GroupChannel channel = new GroupChannel(); //membership setting McastService mcastService = new McastService(); mcastService.setAddress("228.0.0.4"); mcastService.setPort(45564); // TCP&UDP port 오픈 필요 mcastService.setFrequency(500); mcastService.setDropTime(3000); channel.setMembershipService(mcastService); //receiver NioReceiver receiver = new NioReceiver(); receiver.setAddress("auto"); receiver.setMaxThreads(6); receiver.setPort(5000); // TCP port 오픈 필요 channel.setChannelReceiver(receiver); //sender ReplicationTransmitter sender = new ReplicationTransmitter(); sender.setTransport(new PooledParallelSender()); channel.setChannelSender(sender); //interceptor channel.addInterceptor(new TcpPingInterceptor()); channel.addInterceptor(new TcpFailureDetector()); channel.addInterceptor(new MessageDispatchInterceptor()); cluster.addValve(new ReplicationValve()); cluster.addValve(new JvmRouteBinderValve()); cluster.setChannel(channel); cluster.addClusterListener(new ClusterSessionListener()); engine.setCluster(cluster); } }
-
미해결호돌맨의 요절복통 개발쇼 (SpringBoot, Vue.JS, AWS)
Count 쿼리 발생에 대해 궁금합니다.
안녕하세요 호돌맨님 ! 질문이 있습니다 !!Pageable 을 사용해서 페이징 구현 시 Full Scan 을 하기 위해 Count 쿼리가 발생하는걸 확인했는데, Pageable 에서는 페이징 처리를 위해 총 데이터의 개수를 파악해야 하기 때문이니까 Count 쿼리가 필요한거고 Querydsl 은 직접 limit 값, offset 값을 지정하기 때문에 Full Scan 할 필요가 없으니 Count 쿼리가 필요없는게 맞을까요 ?
-
미해결스프링부트 시큐리티 & JWT 강의
[5강] admin, manager로 로그인시 403에러
안녕하세요! 강의 잘 듣고 있습니다.다름이 아니라 5강을 진행하던 중, DB에서 ROLE_ADMIN, ROLE_MANAGER로 바꾸어 주었는데도 403에러가 떠서 질문드립니다.<SecurityConfig>package com.example.SpringSecurity.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;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.configurers.AbstractHttpConfigurer;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.web.SecurityFilterChain;@Configuration@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록이 됨.@EnableMethodSecurity(securedEnabled = true)public class SecurityConfig {@Bean public BCryptPasswordEncoder encodePwd(){return new BCryptPasswordEncoder(); }@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {return http.csrf(AbstractHttpConfigurer::disable) // 사이트 위변조 요청 방지 .authorizeHttpRequests((authorizeRequests) -> { // 특정 URL에 대한 권한 설정. authorizeRequests.requestMatchers("/user/**").authenticated(); authorizeRequests.requestMatchers("/manager/**").hasAnyRole("ADMIN", "MANAGER"); // ROLE_은 붙이면 안 된다. hasAnyRole()을 사용할 때 자동으로 ROLE_이 붙기 때문이다. authorizeRequests.requestMatchers("/admin/**").hasRole("ADMIN"); // ROLE_은 붙이면 안 된다. hasRole()을 사용할 때 자동으로 ROLE_이 붙기 때문이다. authorizeRequests.anyRequest().permitAll(); }).formLogin((formLogin) -> {formLogin.loginPage("/loginForm") // 권한이 필요한 요청은 해당 url로 리다이렉트 .loginProcessingUrl("/login") // login 주소가 호출되면 시큐리티가 낚아채서 대신 로그인을 해준다. .defaultSuccessUrl("/"); //로그인 성공시 /주소로 이동 }).build(); }} <IndexController> package com.example.SpringSecurity.controller;import com.example.SpringSecurity.model.User;import com.example.SpringSecurity.repository.UserRepository;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controllerpublic class IndexController {@Autowired private UserRepository userRepository; @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; @GetMapping({"/",""})public String index(){return "index"; }@GetMapping("/user")public @ResponseBody String user(){return "user"; }@GetMapping("/admin")public @ResponseBody String admin(){return "admin"; }@GetMapping("/manager")public @ResponseBody String manager(){return "manager"; }@GetMapping("/loginForm")public String loginForm(){return "loginForm"; }@GetMapping("/joinForm")public String joinForm(){return "joinForm"; }@PostMapping("/join")public String join(User user){System.out.println(user); user.setRole("ROLE_USER"); String rawPassword = user.getPassword(); String encPassword = bCryptPasswordEncoder.encode(rawPassword); user.setPassword(encPassword); userRepository.save(user); // 패스워드가 암호화가 안되어있으면, 회원가입은 잘되나 Security를 이용해 로그인 할 수 없다. return "redirect:/loginForm"; }@GetMapping("/info")public @ResponseBody String info(){return "개인정보"; }} 코드는 이러합니다. SecurityConfig는 v6이어서 수정하였습니다. 에러 해결에 도움 부탁드려요,,항상 좋은 강의 감사합니다!
-
미해결호돌맨의 요절복통 개발쇼 (SpringBoot, Vue.JS, AWS)
Controller Test 시 @WebMvcTest 사용 관련 질문입니다.
안녕하세요 호돌맨님 :) 좋은 강의 늘 감사드립니다.Controller Test 할 때 질문이 있습니다. 강의에서 호돌맨님께서는 @SpringBootTest 사용하셨는데 저는 한 번 @WebMvcTest를 사용해보고 있습니다.그러다보니 문제가 하나 발생을 하더라구요. 우선 PostController 쪽 코드를 보여드리겠습니다.@GetMapping("/posts") public ApiResponse<List<PostResponse>> getAll(@ModelAttribute PostSearch postSearch) { List<PostResponse> posts = postService.getAllPost(postSearch); return ApiResponse.ok(posts); }보시면 게시글을 전체 조회하는 이 메서드의 경우에는 인증이 따로 필요없이 바로 조회가 되게 했는데요. Insomnia 라는 프로그램을 통해서 테스트했을 때 따로 인증하지 않아도 전체 조회 요청이 성공하는 것을 확인했습니다.그런데 테스트코드를 작성할 때 인증 처리를 해주지 않으면 계속해서 401 에러가 발생을 하더라구요.@DisplayName("게시글을 전체 조회한다.") @CustomMockUser //todo 실제로는 인증이 필요없는데도 테스트 통과를 위해서 인증 처리를 해줘야 하는 것인가? @Test void getAllPosts() throws Exception { // given List<Post> posts = IntStream .range(1, 5) .mapToObj(i -> Post.of(FREE, i + "번 제목입니다.", i + "번 내용입니다.")) .toList(); List<PostResponse> response = changePostListToPostResponseList(posts); // when when(postService.getAllPost(any())).thenReturn(response); //then mockMvc.perform( MockMvcRequestBuilders.get("/api/posts") ) .andDo(MockMvcResultHandlers.print()) .andExpect(MockMvcResultMatchers.jsonPath("$.code").value(200)) .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("OK")) .andExpect(MockMvcResultMatchers.jsonPath("$.status").value("OK")) .andExpect(MockMvcResultMatchers.jsonPath("$.data").isArray()) .andExpect(MockMvcResultMatchers.status().isOk()); }@CustomMockUser 어노테이션을 붙여줘야 테스트 통과가 되는 상황입니다. 실제 원하는 기능은 인증없이 전체 조회 되게 하는 것이 목표인데 테스트 통과를 위해서 @CustomMockUser 어노테이션을 붙여주는 것이 올바른 테스트인지 궁금합니다! 혹시 이게 @WebMvcTest와 관련없는 다른 문제라면 알려주시면 한 번 제가 더 찾아보겠습니다..!
-
미해결스프링 시큐리티
강의 ppt 그림 질문입니다.
강의안 ppt를 보면서 진행중인데그림이 상이해서 헷갈려 질문드립니다.67번 슬라이드에서 인증 결과를 저정하는 그림에서는ThreadLocal > SecurityContextHolder > SecurityContext 처럼 표기되어있는데74번 슬라이드에서는SecurityContextHolder > ThreadLocal > SecurityContext 처럼 표기되어있어서 혼란이오는데 어느 슬라이드의 그림이 맞는건가요?
-
해결됨스프링 시큐리티
Ajax Custom DSLs - customConfigurerAjax(HttpSecurity http) 에서 loginProcessingUrl(/api/login) 처리할 때 null
customConfigurerAjax(HttpSecurity http) 에서 loginProcessingUrl("/api/login") 처리할 때private void customConfigurerAjax(HttpSecurity http) throws Exception { http .apply(new AjaxLoginConfigurer<>()) .successHandlerAjax(ajaxAuthenticationSuccessHandler()) .failureHandlerAjax(ajaxAuthenticationFailureHandler()) .setAuthenticationManager(authenticationManager(authenticationConfiguration)) .loginProcessingUrl("/api/login") ; }AbstractAuthenticationFilerConfigurer 클래스의 loginProcessingUrl을 호출하던데 여기서 authFilter 가 null 이어서 예외가 발생하는데public T loginProcessingUrl(String loginProcessingUrl) { this.loginProcessingUrl = loginProcessingUrl; this.authFilter.setRequiresAuthenticationRequestMatcher(createLoginProcessingUrlMatcher(loginProcessingUrl)); return getSelf(); }어떻게 해결해야할지 모르겠습니다. ㅠㅠhttps://github.com/pdh9311/core-spring-security추가로 ajax security 시작한 이후로 localhost:8080 으로 접속하니까 화면이 안나오고 401 에러가 발생하는데 어디가 문제인지 모르겠습니다. ㅠㅠ