Spring - Security

Spring - Security


Spring - Security๋ž€?

์Šคํ”„๋ง์œผ๋กœ ๊ฐœ๋ฐœ์„ ํ•˜๋ฉด ์„ธ์…˜์„ ์ด์šฉํ•œ ๋กœ๊ทธ์ธ ๋ฐฉ๋ฒ•์„ ๋งŽ์ด ์ด์šฉํ•œ๋‹ค.

ํ•˜์ง€๋งŒ ์„ธ์…˜์„ ์ด์šฉํ•˜์—ฌ ๊ฐœ๋ฐœ์„ ํ•˜๋ฉด ์ฝ”๋“œ๋กœ ์ž‘์„ฑํ•ด์•ผํ•˜๋Š” ๋ถ€๋ถ„๊ณผ ๋ณด์•ˆ์ ์œผ๋กœ ๋…ธ์ถœ๋˜๋Š” ๋ถ€๋ถ„์ด ๋งŽ์•„์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ๋œ๋‹ค.

์ด๋Ÿฌํ•œ ๋ถ€๋ถ„์„ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์„ธ์…˜ / ํ† ํฐ ๋ฐฉ์‹์„ ์‰ฝ๊ณ  ๊ฐ„ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•œ๋‹ค.

Dependency ์ถ”๊ฐ€


gradle ๋นŒ๋“œ ๊ธฐ์ค€

implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'

build.gradle ์— ์œ„์™€ ๊ฐ™์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ๋˜๋Š” ํ”„๋กœ์ ํŠธ ์…‹ํŒ…์‹œ ์‹œํ๋ฆฌํ‹ฐ ์ฒดํฌ๋ฅผ ํ•ด์ค€๋‹ค.

์ ์šฉ


SecurityConfig

@Configuration
@EnableWebSecurity  // ํ•„ํ„ฐ ์ฒด์ธ ๊ด€๋ฆฌ ์‹œ์ž‘ ์–ด๋…ธํ…Œ์ด์…˜
@EnableGlobalMethodSecurity(securedEnabled = true , prePostEnabled = true) // secured ์–ด๋…ธํ…Œ์ด์…˜ ํ™œ์„ฑํ™” , preAuthorize / PostAuthorize ์–ด๋…ธํ…Œ์ด์…˜ ํ™œ์„ฑํ™”
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	
    // ์†Œ์ŠคํŒŒ์ผ ์ ‘๊ทผ ์ถ”๊ฐ€
	@Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/js/**","/css/**","/images/**","/font/**" , "/assets/**" );
    }

    @Override
	protected void configure(HttpSecurity http) throws Exception {
		
		http.csrf().disable();
		http.authorizeRequests()
			.antMatchers("/user/**").authenticated() // ์ธ์ฆ๋งŒ ๋˜๋ฉด ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋Š” ์ฃผ์†Œ!!
			.antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
			.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
			.anyRequest().permitAll()
		.and()
			.formLogin() 					// ๊ธฐ๋ณธ ์ธ์ฆ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€
			.loginPage("/login")
			.usernameParameter("username") // ์ž„์˜์˜ ์œ ์ € ํŒŒ๋ผ๋ฉ”ํ„ฐ๊ฐ’
			.passwordParameter("password") // ์ž„์˜์˜ ์œ ์ € ํŒจ์Šค์›Œ๋“œ๊ฐ’
			.loginProcessingUrl("/login") // ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ์ฃผ์†Œ๋ฅผ ๋‚š์•„์ฑ„ ์‹œํ๋ฆฌํ‹ฐ ๋กœ๊ทธ์ธ์„ ์ง„ํ–‰ํ•ด์ค๋‹ˆ๋‹ค.
			.defaultSuccessUrl("/")
		.and()
			.oauth2Login()
			.loginPage("/login"); // ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ์ด ์™„๋ฃŒ๋œ ๋’ค์˜ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•จ.
	}
	
	// ํ•ด๋‹น ๋ฉ”์„œ๋“œ์˜ ๋ฆฌํ„ด๋˜๋Š” ์˜ค๋ธŒ์ ํŠธ๋ฅผ IoC๋กœ ๋“ฑ๋กํ•ด์ค€๋‹ค. 
	@Bean
	public BCryptPasswordEncoder encodePwd() {
		return new BCryptPasswordEncoder();
	}
}

์‹œํ๋ฆฌํ‹ฐ๋ฅผ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ํ”„๋กœ์ ํŠธ ๋‚ด์— Config ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

Config๋Š” WebSecurityConfigurerAdapter์„ ์ƒ์†๋ฐ›์•„ ์‚ฌ์šฉํ•˜๋ฉฐ @Configuration ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

์œ„์™€ ๊ฐ™์ด ์„ค์ •ํ›„ ํ”„๋กœ์ ํŠธ์— ์ ‘์†ํ•˜๊ฒŒ ๋˜๋ฉด ๊ธฐ๋ณธ์ ์œผ๋กœ ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์ œ๊ณตํ•˜๋Š” ๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•˜๊ฒŒ๋œ๋‹ค.
์‹œํ๋ฆฌํ‹ฐ์—์„œ ์ œ๊ณตํ•˜๋Š” ๋กœ๊ทธ์ธ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋Ÿฌ๋ฉด @EnableWebSecurity ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

ํด๋ž˜์Šค๋ฅผ ์…์„ฑํ•œ ํ›„ configure ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•˜์—ฌ ๊ถŒํ•œ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์—ฌ ์‚ฌ์šฉํ•œ๋‹ค.

๊ถŒํ•œ ์„ค์ •

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable() // .csrf().disable() / Cross-Site Request Forgery ์›น์ทจ์•ฝ์„ฑ ํ† ํฐ
              .authorizeRequests() 
              .antMatchers("/login").permitAll()
  //          .antMatchers("/user/**").hasRole("ADMIN")
              .anyRequest().authenticated()
            .and()
                .formLogin()
                .loginPage("/login")
                .usernameParameter("user_id")
                .passwordParameter("user_password")
                .successHandler(sHandler) // ๋กœ๊ทธ์ธ ์„ฑ๊ณต์‹œ
            .and()
                .logout()
                .logoutSuccessUrl("/login")
    			.deleteCookies("JSESSIONID") // ์„ธ์…˜ ์ฟ ํ‚ค ์‚ญ์ œ
                .invalidateHttpSession(true)
            .and()
                .exceptionHandling().accessDeniedPage("/login/denied");
                
    }

antMatchers()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ ‘๊ทผ ๊ถŒํ•œ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

  • hasRole() or hasAnyRole() : ํŠน์ • ๊ถŒํ•œ์„ ๊ฐ€์ง€๋Š” ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • hasAuthority() or hasAnyAuthority() : ํŠน์ • ๊ถŒํ•œ์„ ๊ฐ€์ง€๋Š” ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • hasIpAddress() : ํŠน์ • ์•„์ดํ”ผ ์ฃผ์†Œ๋ฅผ ๊ฐ€์ง€๋Š” ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • permitAll() or denyAll() : ์ ‘๊ทผ์„ ์ „๋ถ€ ํ—ˆ์šฉํ•˜๊ฑฐ๋‚˜ ์ œํ•œํ•ฉ๋‹ˆ๋‹ค.
  • rememberMe() : ๋ฆฌ๋ฉค๋ฒ„ ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • anonymous() : ์ธ์ฆ๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • authenticated() : ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ํŠน์ • ๋ฉ”์„œ๋“œ์— ๊ด€๋ จํ•˜์—ฌ ๊ถŒํ•œ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค

@EnableGlobalMethodSecurity(securedEnabled = true , prePostEnabled = true)
  • securedEnabled : @Secured(โ€œROLE_[]โ€) ์–ด๋…ธํ…Œ์ด์…˜ ํ™œ์„ฑํ™”
  • prePostEnabled : @PreAuthorize(โ€œhsaRole(โ€˜ROLE_[]โ€™) or hsaRole(โ€˜ROLE_[]โ€™)โ€) / @PostAuthorize ์–ด๋…ธํ…Œ์ด์…˜ ํ™œ์„ฑํ™”

์œ„์™€ ๊ฐ™์ด ํŠน์ • ๋ฉ”์„œ๋“œ์— ๊ถŒํ•œ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ตœ๊ทผ์— ๋‚˜์˜จ @Secured ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์‰ฝ๊ฒŒ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
๋งŒ์•ฝ 2๊ฐœ ์ด์ƒ์˜ ๊ถŒํ•œ ์ฒ˜๋ฆฌ์‹œ์—๋Š” @PreAuthorize๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ or ์„ ํ†ตํ•ด ๊ถŒํ•œ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

http ์ ‘๊ทผ ๊ด€๋ จ

์‹œํ๋ฆฌํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ POST , PUT๊ณผ ๊ฐ™์€ ์ ‘๊ทผ์„ํ•˜๊ฒŒ ๋˜๋ฉด ํ†ต์‹ ์ด ๋ถˆ๊ฐ€ํ•˜๋‹ค๋Š” ์—๋Ÿฌ๋ฅผ ๋งŒ๋‚˜ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ ๋ณด์•ˆ์„ ์œ„ํ•ด POST์™€ ๊ฐ™์€ ์ ‘๊ทผ์„ ์ œํ•œํ•ด๋†จ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ ‘๊ทผ์„ ํ—ˆ์šฉ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ์„ค์ •์„ ์ถ”๊ฐ€ํ•ด์ค˜์•ผํ•œ๋‹ค.

http.csrf().disable() // .csrf().disable() / Cross-Site Request Forgery ์›น์ทจ์•ฝ์„ฑ ํ† ํฐ

์ตœ๊ทผ์—๋Š” ์•ฑ์—์„œ ํ™”๋ฉด์„ ๊ตฌ์„ฑํ•˜๊ณ  , React๋ฅผ ํ†ตํ•ด ํ™”๋ฉด์„ ๊ตฌ์„ฑํ•˜๋ฉด์„œ form์ด ์•„๋‹Œ script ์š”์ฒญ์„ ํ•˜๊ธฐ ๋–„๋ฌธ์— ์ž์ฃผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.

ํšŒ์›๊ฐ€์ž…


ํšŒ์›๊ฐ€์ž…์€ ๊ฐ„๋‹จํ•˜๊ฒŒ ์•”ํ˜ธํ™”๋ฅผ ์ง„ํ–‰ํ•˜์—ฌ ๋“ฑ๋กํ•˜๊ฒŒ๋œ๋‹ค.

@Autowired
private BCryptPasswordEncoder passwordEncoder;

// ํšŒ์›๊ฐ€์ž…
	@PostMapping("join")
	public String join(User user) {
		
		user.setRole("ROLE_USER");
		String rawPassword = user.getPassword();
		String encPassword = passwordEncoder.encode(rawPassword);
		user.setPassword(encPassword);
		
		userRepository.save(user); // ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™”๊ฐ€ ํ•„์—ฌํ•˜๋‹ค. ์‹œํ๋ฆฌํ‹ฐ ๋กœ๊ทธ์ธ ๋ถˆ๊ฐ€๋Šฅ
		
		return "redirect:/login";
	}

์œ„์™€ ๊ฐ™์ด ์ ‘๊ทผํ•˜๋ฉฐ IoC์— ๋“ฑ๋กํ•˜์—ฌ BCryptPasswordEncoder ์ธ์ฝ”๋”ฉ์„ ํ†ตํ•ด ์•”ํ˜ธํ™”ํ›„ ๋“ฑ๋ก์„ ํ•ด์ค€๋‹ค.

UserDetail


์‹œํ๋ฆฌํ‹ฐ๋Š” Authentication ๊ฐ์ฒด๋ฅผ ์„ธ์…˜์— ๋„ฃ๊ฒŒ๋˜๋Š”๋ฐ ์ด๊ฒƒ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ UserDetail ์„ ์ƒ์†๋ฐ›์•„ ์‚ฌ์šฉํ•˜๊ฒŒ๋œ๋‹ค.

User Entity

@Entity
@Data
public class User {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;
	private String username;
	private String password;
	private String email;
	private String role;
	
	@CreationTimestamp
	private Timestamp createDate;
}

Authentication ๊ฐ์ฒด

// ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ /login ์ฃผ์†Œ ์š”์ฒญ ์˜ค๋ฉด ๋‚š์•„์ฑ„์„œ ๋กœ๊ทธ์ธ์„ ์ง„ํ–‰์‹œํ‚จ๋‹ค.
// ๋กœ๊ทธ์ธ์„ ์ง„ํ–‰์ด ์™„๋ฃŒ๊ฐ€ ๋˜๋ฉด ์‹œํ๋ฆฌํ‹ฐ session์œผ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค (Security ContextHolder)
// ์˜คํ”„์ ํŠธ ํƒ€์ž… => Authentication ํƒ€์ž… ๊ฐ์ฒด
// Authentication ์•ˆ์— User ์ •๋ณด๊ฐ€ ์žˆ์–ด์•ผ ๋จ.
// User ์˜ค๋ธŒ์ ํŠธํƒ€์ž… => UserDetails ํƒ€์ž… ๊ฐ์ฒด

// Security Session => Authentication => UserDetails(PrincipalDetails)

public class PrincipalDetails implements UserDetails{
	
	private User user; // ์œ ์ € ๊ฐ์ฒด
	
	public PrincipalDetails(User user) {
		this.user = user;
	};
	
	// ํ•ด๋‹น User์˜ ๊ถŒํ•œ์„ ๋ฆฌํ„ดํ•˜๋Š” ๊ณณ!
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		Collection<GrantedAuthority> collect = new ArrayList<GrantedAuthority>();
		collect.add(new GrantedAuthority() {
			@Override
			public String getAuthority() {
				return user.getRole();
			}
		});
		
		return collect;
	}

	@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() {
		
		// ์‚ฌ์ดํŠธ์—์„œ ์œ ์ €๊ฐ€ 1๋…„๋™์•ˆ ๋กœ๊ทธ์ธ์„ ์•ˆํ–ˆ๋‹ค๋ฉด!! ํœด๋จผ ๊ณ„์ •์œผ๋กœ ํ•˜๊ธฐ๋กœ ํ•จ
		// ํ˜„์žฌ์‹œ๊ฐ„ - ๋กœ๊ธด์‹œ๊ฐ„ => 1๋…„์„ ์ดˆ๊ณผํ•˜๋ฉด false
		
		return true;
	}

}

๋กœ๊ทธ์ธ


UserDetailsService ์„œ๋น„์Šค

// ์‹œํ๋ฆฌํ‹ฐ ์„ค์ •์—์„œ loginProcessingUrl("/login")
// /login ์š”์ฒญ์ด ์˜ค๋ฉด ์ž๋™์œผ๋กœ UserDetailsService ํƒ€์ž…์œผ๋กœ IoC๋˜์–ด ์žˆ๋Š” loadUserByUsername ํ•จ์ˆ˜ ์‹คํ–‰
@Service
public class PricipalDetailsService implements UserDetailsService{
	
	@Autowired
	private UserRepository userRepository;
	
	// ์‹œํ๋ฆฌํ‹ฐ session = Authentication = UserDetails
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		
		User user = userRepository.findByUsername(username);
		
		if(user != null) {
			return new PrincipalDetails(user);
		}
		
		return null;
	}

}
[๋กœ๊ทธ์ธ์‹œ๋„ -> SecurityConfig loginProcessingUrl ๋‚š์•„์ฑ” -> PrincipalDetailsService ์—์„œ ์ •๋ณด ํ™•์ธ -> UserDetails์ƒ์„ฑ -> PrincipalDeatail ์ƒ์„ฑ -> session(Authentication)์•ˆ์— ๋ง์•„์„œ ๋ฆฌํ„ด]

์œ„์™€ ๊ฐ™์€ ๋กœ์ง์„ ํ†ตํ•ด ๋กœ๊ทธ์ธ์„ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

Categories:

Updated:

Leave a comment