
와우~! 시큐리티 정말 재밌다!
재밌다고해서 안어렵다는건 아니지만 직접 짠 코드가 잘 동작하는게 눈으로 보이니까
훨씬 재밌는것 같은 느낌ㅎㅎ
그래도 아직 헷갈리는 부분이 많아서 혼자 간단한 프로젝트라도 해보면서 익혀야 될 것 같다..!
이전에 프로젝트 할 때, 시큐리티를 혼자 공부해볼려고 하다가 너무 어려워서 포기했는데
확실히 이해하기 쉽게 알려주시는 것 같다👍
인증과 인가 범위를 잘 설정해주어야함
SecurityFilterChainHttpSecurity 를 매개변수로 받음CORS
http
.cors( cors -> cors.disable() )
CSRF
http
.cors( cors -> cors.disable() )
authorizeHttpRequests
어떤 URL에 대해 로그인 여부와 어떤 권한이 있는지 확인
.authorizeHttpRequests( auth -> {
auth.requestMatchers("/singup", "/login")
.anonymous()
.anyRequest()
.authenticated();
})
requestMatchers()에 접근 URL 작성anyRequest 는 requestMatchers에 해당되는 URL을 제외한 모든 URL에 대한 설정formLogin
.formLogin(Customizer.withDefaults())
.formLogin(form->{
form.loginPage("/signin");
})
.formLogin(form->{
form.defaultSuccessUrl("/")
.usernameParameter("loginId")
.passwordParameter("loginPwd")
.loginProcessingUrl("/signin")
.loginPage("/signin")
.permitAll();
})
loginProcessingUrl 진짜 로그인을 수행할 URL 설정 → POST MappingusernameParameter 와 passwordParameter 로 로그인 시 매칭될 id와 password의 명칭을 정해줄 수 있음defaultSuccessUrl → 로그인이 성공되면 이동할 URLsuccessForwardUrl → 로그인 성공하고 후처리.logout(logout->{
logout.logoutUrl("/signout")
.logoutSuccessUrl("/")
.clearAuthentication(true)
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
})
invalidateHttpSession → HTTP 세션 무효화deleteCookies("JSESSIONID") → 쿠키 삭제여기까지는 인증 🔐
.authorizeHttpRequests(
auth -> {
auth.requestMatchers("/signup", "/signin")
.anonymous()
.requestMatchers("/user/**")
// .hasRole("MEMBER")
.hasAnyAuthority("MEMBER", "MANAGER", "ADMIN")
.requestMatchers("/manager/**", "/user/**")
.hasAnyAuthority("MANAGER", "ADMIN")
.requestMatchers("/admin/**", "/manager/**", "/user/**")
.hasAuthority("ADMIN")
.anyRequest()
// .denyAll();
.authenticated();
}
)
authenticated → 인증만 되면 접근 가능
hasAnyRole → 권한을 여려개 넣는게 가능
hasAuthority → ROLE_ 없이 진짜 문자열 그대로 검사
hasRole, hasAnyRole은 내가 넣어준 글자 앞에 ROLE_을 붙여서 검사
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
String encode = passwordEncoder().encode(targetPwd);
manager.createUser(
User.withUsername("user")
.password(encode)
// .roles("MEMBER")
.authorities("ADMIN")
.build()
);
return manager;
}
manager.createUser() →UserDetails를 만들고 등록해줌
그렇기 때문에 roles() 에서 검사를 할때는 ROLE_ 이 있다고 가정하고 검사
→ authorities() 를 사용하면 문자열 그대로를 저장하고 검사함
Spring Security가 사용자 정보를 얻기 위해 꼭 구현해야 하는 인터페이스
@Service
@Transactional
@RequiredArgsConstructor
public class MemberService implements UserDetailsService {
private final PasswordEncoder passwordEncoder;
private final MemberRepository memberRepository;
public void save(SignUpForm signUpForm) {
Member member = Member.builder()
.username(signUpForm.getUsername())
.password(passwordEncoder.encode(signUpForm.getPassword()))
.email(signUpForm.getEmail())
.build();
memberRepository.save(member);
}
//id로 UserDetails 타입을 가져올 수 있어야함
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return null;
}
}
loadUserByUsername → 사용자가 로그인할 때 Spring Security가 자동으로 호출, username으로 DB에서 사용자를 찾고 결과를 UserDetails로 감싸서 반환
public class MemberDetails implements UserDetails {
private final String username;
private final String password;
private final String role;
public MemberDetails(Member member) {
this.username = member.getUsername();
this.password = member.getPassword();
this.role = member.getRole();
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
//인가 검사
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority roleMember = new SimpleGrantedAuthority(this.role);
return List.of(roleMember);
}
생성자 → DB에서 유저를 조회한 뒤 그걸 MemberDetails 객체로 감쌈
getAuthorities() → 유저가 어떤 권한을 가졌는지 반환
oauth2Login(Customizer.withDefaults()) 을 통해 사용 가능
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.oauth2Login(Customizer.withDefaults())
.authorizeHttpRequests(auth -> {
auth.requestMatchers("/login", "signup")
.anonymous()
.requestMatchers("/user/**")
.hasAnyAuthority("ADMIN", "USER")
.requestMatchers("/admin")
.hasAnyAuthority("ADMIN")
.anyRequest()
.authenticated();
})
.build();
}
클라이언트 Id와 secret 설정파일에 작성
권한부여 승인코드를 받아올 URL이 필요 → GET 방식으로 파라미터에 담아서 보내줌
http://localhost:8080/login/oauth2/code/google → 링크를 ‘승인된 리디렉션 URI’에 추가
UserDetails 사용
@Service
@Transactional
@RequiredArgsConstructor
public class MemberService extends DefaultOAuth2UserService {
private final MemberRepository memberRepository;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
log.info("loadUser: {}", oAuth2User);
return super.loadUser(userRequest);
}
}
DefaultOAuth2UserService 를 상속받아 이용로그를 출력해보면
User Attributes: [{sub=116923500273585730404, name=봉봉,
given_name=봉봉, picture=https://lh3.googleusercontent.com/a/ACg8ocLN25toYSbEq1JQb3DgongyWtrIssN-Mw74GReXVkWrcML8exw2=s96-c,
email=aladinsue@gmail.com, email_verified=true}]
❗️User Attributes 값을 가져오는 걸 확인 → 회원을 만들어서 등록하는게 가능해진다
OAuth2UserRequest → 에 ClientRegistration과 AccessToken이 들어있음
userRequest.getClientRegistration().getRegistrationId() 을 통해 가져온다.
Map<String, Object> attributes = oAuth2User.getAttributes();
String findName = attributes.get("name").toString();
Member member = Member.builder()
.name(findName)
.nickname(findName)
.email(attributes.get("email").toString())
.provider(userRequest.getClientRegistration().getRegistrationId())
.build();
memberRepository.save(member);
oAuth2User 에서 Attributes를 가져와서 해당 정보로 회원가입
DB에 저장 완료!

@Getter
public class MemberDetails implements OAuth2User {
private String name;
private Map<String, Object> attributes;
@Setter
private String role;
@Builder
public MemberDetails(String name, String role, Map<String, Object> attributes) {
this.name = name;
this.role = role;
this.attributes = attributes;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority(role));
}
}
OAuth2User를 구현해서 OAuth2 로그인을 처리할 때 사용자 정보를 다룬다.
attributes→ OAuth2에서 받은 모든 사용자 정보
getAuthorities → 사용자의 권한을 가져옴
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class MemberService extends DefaultOAuth2UserService {
private final MemberRepository memberRepository;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
log.info("loadUser: {}", oAuth2User);
Map<String, Object> attributes = oAuth2User.getAttributes();
String findName = attributes.get("name").toString();
String email = attributes.get("email").toString();
Optional<Member> memberOptional = memberRepository.findByEmail(email);
Member member = memberOptional.orElseGet(
() -> {
Member saved = Member.builder()
.email(email)
.name(findName)
.provider(userRequest.getClientRegistration().getRegistrationId())
.build();
return memberRepository.save(saved);
}
);
return MemberDetails.builder()
.name(member.getName())
.role(member.getRole())
.attributes(attributes)
.build();
}
}
findByEmail로 사용자가 이미 가입되었는지 확인memberOptional.orElseGet()) Member 객체를 만들어서 DB에 저장