@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/**").permitAll()
)
.csrf((csrf) -> csrf
.ignoringRequestMatchers(new AntPathRequestMatcher("/h2-console/**")))
.headers((headers) -> headers
.addHeaderWriter(new XFrameOptionsHeaderWriter(
XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)))
;
return http.build();
}
}
여기서 antMatchers()가 requestMatchers로 바뀌었다는것을 몰라서 한참이 걸렸다. 더 이상 antMatchers는 존재하지 않는다. 추가적으로 h2-console도 설정해주었다.
Login 구현하는 순서
@Getter
public enum UserRole {
ADMIN("ROLE_ADMIN"),
USER("ROLE_USER");
UserRole(String value) {
this.value = value;
}
private String value;
}
간단하게 Admin, User로 이루어진 권한을 만들었다.
package com.hada.portfolio.user;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
@Entity
@Getter
@Setter
public class SiteUser implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
ArrayList<GrantedAuthority> auth = new ArrayList<GrantedAuthority>();
auth.add(new SimpleGrantedAuthority(authorities));
return auth;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
권한이 여러가지가 아니기 때문에 String을 통해 문자열로 저장하였다.
@Service
public class SiteUserService implements UserDetailsService {
private final SiteUserRepository siteUserRepository;
private final PasswordEncoder passwordEncoder;
public SiteUserService(SiteUserRepository siteUserRepository, PasswordEncoder passwordEncoder){
this.siteUserRepository = siteUserRepository;
this.passwordEncoder = passwordEncoder;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SiteUser siteUser = siteUserRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));
return new User(
siteUser.getUsername(), siteUser.getPassword(), siteUser.getAuthorities());
}
public SiteUser save(String username, String password) {
SiteUser siteUser = new SiteUser();
siteUser.setUsername(username);
siteUser.setPassword(passwordEncoder.encode(password));
if(username.equals("admin"))
siteUser.setAuthorities(UserRole.ADMIN.getValue());
else
siteUser.setAuthorities(UserRole.USER.getValue());
return siteUserRepository.save(siteUser);
}
public boolean findByUsername(String username) {
return siteUserRepository.findByUsername(username).isPresent();
}
}
중복확인 후 회원가입을 진행하기 위해서 findByUsername을 구현하였다. ADMIN의 권한을 가지는 아이디는 admin으로 하였다.
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/**").permitAll()
)
.csrf((csrf) -> csrf
.ignoringRequestMatchers(new AntPathRequestMatcher("/h2-console/**")))
.headers((headers) -> headers
.addHeaderWriter(new XFrameOptionsHeaderWriter(
XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)))
.formLogin((formLogin) -> formLogin
.loginPage("/user/login")
.defaultSuccessUrl("/"))
.logout((logout) -> logout
.logoutRequestMatcher(new AntPathRequestMatcher("/user/logout"))
.logoutSuccessUrl("/")
.invalidateHttpSession(true))
;
return http.build();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Controller
@RequestMapping("/user")
public class UserController {
private final SiteUserService siteUserService;
public UserController(SiteUserService siteUserService){
this.siteUserService = siteUserService;
}
@GetMapping("/signup")
public String signup(UserCreateForm userCreateForm) {
return "signup_form";
}
@PostMapping("/signup")
public String signup(@Valid UserCreateForm userCreateForm, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "signup_form";
}
if (!userCreateForm.getPassword1().equals(userCreateForm.getPassword2())) {
bindingResult.rejectValue("password2", "passwordInCorrect",
"2개의 패스워드가 일치하지 않습니다.");
return "signup_form";
}
if (siteUserService.findByUsername(userCreateForm.getUsername())){
bindingResult.rejectValue("username", "usernameDuplicated",
"이미 사용중인 사용자ID입니다.");
return "signup_form";
}
siteUserService.save(userCreateForm.getUsername(), userCreateForm.getPassword1());
return "redirect:/";
}
@GetMapping("/login")
public String login() {
return "login_form";
}
}
회원가입 부분은 userCreateFrom을 만들어서 처리하였다.