Framework/Spring

Spring Security 설정 6.x.x 버전 이상

mart3n 2024. 10. 14. 14:43

Entity

@Data
@Entity
@Table(name="member")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq")
	@SequenceGenerator(name = "seq", sequenceName = "seq", allocationSize = 1)
    private Long id;

    private String username;
    private String name;
    private String password;
    private String role; 
    private LocalDateTime regdate;
}

 

UserDetails

public class SecurityUserDetails implements UserDetails {

	private final User user;

	public SecurityUserDetails(User user) { this.user = user;}

	public Member getMember() {return user;}

	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		List<GrantedAuthority> authorities = new ArrayList<>();
		authorities.add(new SimpleGrantedAuthority(user.getRole()));
		return authorities;
	}

	@Override
	public String getPassword() {
		return user.getPassword();
	}

	@Override
	public String getUsername() {
		return user.getUsername();
	}

	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	@Override
	public boolean isEnabled() {
		return true;
	}
}

UserDetailService

@Service
public class SecurityUserDetailsService implements UserDetailsService {

    @Autowired
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userService.findByUsername(username);  // DB에서 사용자 정보 가져오기
        if (user == null) {
            throw new UsernameNotFoundException("User not found.");
        }

        // Spring Security UserDetails 객체 반환
        return new SecurityUserDetails(user);
    }
}

 

AuthenticationProvider

@Component
public class SecurityProvider implements AuthenticationProvider {

	@Autowired
	private SecurityUserDetailsService userDetailsService;

	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		String username = authentication.getName();
		String password = (String) authentication.getCredentials();

		SecurityUserDetails userDetails = (SecurityUserDetails)userDetailsService.loadUserByUsername(username);

		String dbPassword = userDetails.getPassword();
		PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

		if(!passwordEncoder.matches(password, dbPassword)) {
			System.out.println("[사용자] 비밀번호가 일치하지 않습니다.");
			throw new BadCredentialsException("[사용자] 아이디 또는 비밀번호가 일치하지 않습니다.");
		}

		User member = userDetails.getMember();
		if (member == null || "N".equals(member.getUsername())) {
			System.out.println("[사용자] 사용할 수 없는 계정입니다.");
			throw new BadCredentialsException("[사용자] 사용할 수 없는 계정입니다.");
		}

		// 인증이 성공하면 UsernamePasswordAuthenticationToken 객체를 반환한다.
		// 해당 객체는 Authentication 객체로 추후 인증이 끝나고 SecurityContextHolder.getContext() 에 저장된다.
		return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
	}

	@Override
	public boolean supports(Class<?> authentication) {
		return authentication.equals(UsernamePasswordAuthenticationToken.class);
	}
}

SecurityConifg (SecurityFilterChain) 

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    SecurityUserDetailsService securityUserDetail;

    @Autowired
    SecurityProvider securityProvider;

    @Autowired
    public void configure (AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(securityProvider);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)  // 세션이 필요할 때 생성
                .maximumSessions(1)  // 최대 세션 수
                .expiredUrl("/member/login")  // 세션 만료 시 이동할 URL
            )
            //권한설정
            .securityMatcher("/api/**","/user/**","/manager/**","/admin/**")
            .authorizeHttpRequests(authorize ->
                authorize
                    .requestMatchers("/api/*", "/member/login/**", "/error").permitAll() //해당 경로는 권한 X
                    //앞에 ROLE_ 이 무조건 있어야 됨 ROLE_USER , ROLE_MANAGER 등
                    .requestMatchers("/user/**").hasRole("USER") // USER 권한을 가진 아디디만
                    .requestMatchers("/manager/**").hasRole("MANAGER") // MANAGER 권한을 가진 아디디만
                    .requestMatchers("/admin/**").hasRole("ADMIN") // ADMIN 유저 권한을 가진 아디디만
            )
            //로그인
            .formLogin(form ->
                form
                    .loginPage("/member/login/loginForm") // 로그인 페이지 설정
                    .loginProcessingUrl("/member/login/login") // 로그인 처리 URL 설정 Controller 설정 X
//                 	.defaultSuccessUrl("/api/board") // 로그인 성공 후 이동할 페이지
                  	.successHandler(new MemberAuthSuccessHandler()) // 로그인 성공 후 처리할 핸들러
                    .failureHandler(new MemberAuthFailureHandler())// 로그인 실패 후 처리할 핸들러
                    .permitAll()
            )
            //로그아웃
            .logout(logout ->
                logout
                    .logoutUrl("/member/login/logout") // 로그아웃 처리 URL 설정 Controller 설정 X
                    .logoutSuccessUrl("/member/login/loginForm?logout=1") // 로그아웃 성공 후 이동할 페이지
                    .deleteCookies("JSESSIONID") // 로그아웃 후 쿠키 삭제
            )
            //자동 로그인 7일 동안
            .rememberMe(remember ->
                remember
                    .key("jj") // 인증 토큰 생성시 사용할 키
                    .tokenValiditySeconds(60 * 60 * 24 * 7) // 인증 토큰 유효 시간 (초)
                    .userDetailsService(securityUserDetail) // 인증 토큰 생성시 사용할 UserDetailsService
                    //로그인 페이지에 form에 <input type="checkbox" name="remember-me" id="rememberMe"> 이런식으로 사용
                    .rememberMeParameter("remember-me") // 로그인 페이지에서 사용할 파라미터 이름
            )
            //csrf 해제
            .csrf(AbstractHttpConfigurer::disable);

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

 

SuccessHandler

public class MemberAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        // 로그인 성공시 이동할 페이지
        setDefaultTargetUrl("/api/board");
        // 로그인 성공시 이동할 페이지로 이동
        super.onAuthenticationSuccess(request, response, authentication);
    }
}

FailHandler

public class MemberAuthFailureHandler extends SimpleUrlAuthenticationFailureHandler {


    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        // 실패 메세지를 담기 위한 세션 선언
        HttpSession session = request.getSession();
        // 세션에 실패 메세지 담기
        session.setAttribute("loginErrorMessage", exception.getMessage());
        // 로그인 실패시 이동할 페이지
        setDefaultFailureUrl("member/login?error=true&t=h");

        // 로그인 실패시 이동할 페이지로 이동
        super.onAuthenticationFailure(request, response, exception);
    }
}