์ด๋ณด๊ฐ ์ดํดํ๋ ์คํ๋ง ์ํ๋ฆฌํฐ
๋ณธ ๊ธ์ OKKY์ ๊ณต์ ๋ ์ด๋ณด๊ฐ ์ดํดํ๋ ์คํ๋ง ์ํ๋ฆฌํฐ ์ ๋๋ค.
๋ค์ด๊ฐ๋ฉฐ
์คํ๋ง ์ํ๋ฆฌํฐ๋ ๋ฌด์์ธ๊ฐ?
์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ์ดํดํ๊ธฐ ์ํด์ ์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ ๋ฌด์์ธ์ง๋ฅผ ์์์ผํฉ๋๋ค. ์คํ๋ง ์ํ๋ฆฌํฐ ๋ ํผ๋ฐ์ค์์๋ ์๋ฐ EE ๊ธฐ๋ฐ์ ์ํฐํ๋ผ์ด์ฆ ์ํํธ์จ์ด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ ํฌ๊ด์ ์ธ ๋ณด์ ์๋น์ค๋ค์ ์ ๊ณตํ๊ณ ์คํ ํ๋ซํผ์ด๋ฉด์ ์์ ๋ง์ ์ธ์ฆ ๋งค์ปค๋์ฆ์ ๊ฐ๋จํ๊ฒ ๋ง๋ค ์ ์๋ค๊ณ ์๋(?)ํ๊ณ ์์ต๋๋ค.
ํ์ง๋ง, ์ ์ ๊ฐ๋ฐ์๋ค ์์ค์์ ์คํ๋ง ์ํ๋ฆฌํฐ์ ๊ฐ์ ๋ณด์ ๊ธฐ์ ์ ์ดํดํ๊ธฐ๋ ์ฐธ ํ๋ ๊ณผ์ ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค.
์ ๋ง ์ดํดํ๊ธฐ ํ๋ค ์ ์์ด์ ใ ใ
์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ์ดํดํ๊ธฐ ์ํด์๋ ์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ ์ ํ๋ฆฌ์ผ์ด์
๋ณด์์ ๊ตฌ์ฑํ๋ ๋๊ฐ์ง ์์ญ์ ๋ํด์ ์์์ผ ํฉ๋๋ค. ๋ฐ๋ก ์ธ์ฆ(Authentication)๊ณผ ๊ถํ(Authorization)์ด๋ผ๋ ๊ฒ์
๋๋ค. ์ด ๋ ์์ญ์ ์คํ๋ง ์ํ๋ฆฌํฐ์ ๋ชฉํ์ด๊ธฐ ๋๋ฌธ์ ๋ฐ๋์ ์ดํดํ๊ณ ๋์ด๊ฐ์ผ ํฉ๋๋ค. ์ธ์ฆ
์ ์ ํ๋ฆฌ์ผ์ด์
์ ์์
์ ์ํํ ์ ์๋ ์ฃผ์ฒด(์ฌ์ฉ์)๋ผ๊ณ ์ฃผ์ฅํ ์ ์๋ ๊ฒ์ ๋งํ๋ฉฐ ๊ถํ์ ์ธ์ฆ๋ ์ฃผ์ฒด๊ฐ ์ ํ๋ฆฌ์ผ์ด์
์ ๋์์ ์ํํ ์ ์๋๋ก ํ๋ฝ๋์๋์ง๋ฅผ ๊ฒฐ์ ํ๋ ๊ฒ์ ๋งํฉ๋๋ค. ๋ฐ๋ผ์, ๊ถํ ์น์ธ์ด ํ์ํ ๋ถ๋ถ์ผ๋ก ์ ๊ทผํ๋ ค๋ฉด ์ธ์ฆ ๊ณผ์ ์ ํตํด ์ฃผ์ฒด๊ฐ ์ฆ๋ช
๋์ด์ผ๋ง ํ๋ค๋ ๊ฒ์
๋๋ค.
์์ ์คํ๋ง ์ํ๋ฆฌํฐ๋ ์์ ๋ง์ ์ธ์ฆ ๋งค์ปค๋์ฆ์ ๊ฐ๋จํ๊ฒ ๋ง๋ค ์ ์๋ค๊ณ ์๋ํ๋ค๊ณ ํ๋๋ฐ์. ์คํ๋ง ์ํ๋ฆฌํฐ๋ ์ด์ ๊ด๋ จํด์ ์ธ์ฆ ๋งค์ปค๋์ฆ๊ณผ ๊ด๊ณ์์ด ์๋นํ ๊น์ ๊ถํ ๋ถ์ฌ๋ฅผ ์ ๊ณตํ๊ณ ์์ต๋๋ค. ๊ถํ ๋ถ์ฌ์๋ ๋๊ฐ์ง ์์ญ์ด ์กด์ฌํ๋๋ฐ ์น ์์ฒญ ๊ถํ, ๋ฉ์๋ ํธ์ถ ๋ฐ ๋๋ฉ์ธ ์ธ์คํด์ค์ ๋ํ ์ ๊ทผ ๊ถํ ๋ถ์ฌ์ ๋๋ค. ์คํ๋ง ์ํ๋ฆฌํฐ๋ ์ด๋ ๊ฒ ๋ชจ๋ ์ค์ํ ์์ญ์ ํ์ํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๊ณ ์์ต๋๋ค.
์ ํ๊ฑด๋ด๋์ ๊ธ์์๋ ๋ก๊ทธ์ธ ํ๋ฉด์ ํตํด์ ์์ด๋์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ ๋ ฅ๋ฐ์ ๋ก๊ทธ์ธํ๋ ๊ณผ์ ์ ์ธ์ฆ์ด๋ผ๊ณ ์์ ํ๊ณ ์์ต๋๋ค. ์ด์ ๊ด๋ จ๋ ์ฉ์ด๊ฐ ๋ฐ๋ก HTTP ๊ธฐ๋ณธ ์ธ์ฆ(HTTP Basic Authentication) ๋งค์ปค๋์ฆ์ด๋ผ๊ณ ํ ์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด HTTP ๊ธฐ๋ณธ ์ธ์ฆ ๋งค์ปค๋์ฆ์ ์ด์ฉํ๋ ๋ฐฉ์์ด ๋ฐ๋ก ์คํ๋ง ์ํ๋ฆฌํฐ ๋ ํผ๋ฐ์ค์์ ์ค๋ช ํ๋ ํผ ๊ธฐ๋ฐ ๋ก๊ทธ์ธ์ด ๋ฉ๋๋ค.
์ ๋ฌผ๋ก , HTTP ๊ธฐ๋ณธ ์ธ์ฆ == ํผ ๊ธฐ๋ฐ ๋ก๊ทธ์ธ์ด๋ผ๋ ๋ง์ ์๋๋๋ค. ํผ ์์์ ํตํ ๋ก๊ทธ์ธ๋ username๊ณผ password๋ฅผ ํตํด ์ธ์ฆํ๊ธฐ ๋๋ฌธ์ HTTP ๊ธฐ๋ณธ ์ธ์ฆ ๋งค์ปค๋์ฆ์ ์ด์ฉํ๋ ๋ฐฉ์์ด๋ผ๊ณ ์ดํดํ ์ ์๋ค๋ ๊ฒ์ ๋๋ค.
์ค์ ๋ก ์คํ๋ง ์ํ๋ฆฌํฐ ๋ ํผ๋ฐ์ค ๋ฌธ์์ Basic and Digest Authentication์์๋ ๋ค์์ ์ธ๊ธํ๊ณ ์์ต๋๋ค.
Basic authentication is often used with stateless clients which pass their credentials on each request. Itโs quite common to use it in combination with form-based authentication where an application is used through both a browser-based user interface and as a web-service.
์ธ์ฆ๊ณผ ๊ถํ์ด๋ผ๋ ๊ฐ๋ ์ ์ดํดํ์ จ๋ค๋ฉด ์คํ๋ง ์ํ๋ฆฌํฐ์ ๋ํด์ ๋ฐฐ์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
์คํ๋ง ์ํ๋ฆฌํฐ ์์ํ๊ธฐ
์คํ๋ง ์ํ๋ฆฌํฐ๋ ๋ฉ์ด๋ธ์ด๋ ๊ทธ๋๋ค๊ฐ์ ๋น๋๋๊ตฌ๋ฅผ ํตํด ์ฝ๊ฒ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.
with Maven
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
</dependencies>
with Gradle
dependencies {
compile 'org.springframework.security:spring-security-web:4.2.2.RELEASE'
compile 'org.springframework.security:spring-security-config:4.2.2.RELEASE'
}
์ ๋ ๋ฉ์ด๋ธ ๋์ ๊ทธ๋๋ค์ ์ฌ์ฉํด๋ดค์ต๋๋ค.
Java Configuration
์คํ๋ง ์ํ๋ฆฌํฐ ๋ ํผ๋ฐ์ค์์๋ ์๋ฐ ๊ธฐ๋ฐ์ ์ค์ ์ผ๋ก ์ค๋ช ํ๊ณ ์์ต๋๋ค. ๊ทธ ์ด์ ๋ ๋ฌด์์ผ๊น์?
์คํ๋ง ํ๋ ์์ํฌ 3.1์์๋ถํฐ ์ด๋ ธํ ์ด์ ์ ํตํ ์๋ฐ ์ค์ ์ ์ง์ํ๊ธฐ ๋๋ฌธ์ ์คํ๋ง ์ํ๋ฆฌํฐ 3.2๋ถํฐ๋ XML๋ก ์ค์ ํ์ง ์๊ณ ๋ ๊ฐ๋จํ๊ฒ ์ค์ ํ ์ ์๋๋ก ์ง์ํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
์๋ XML ๊ธฐ๋ฐ์ ์ค์ ์์๋ web.xml์ org.springframework.web.filter.DelegatingFilterProxy๋ผ๋ springSecurityFilterChain์ ๋ฑ๋กํ๋ ๊ฒ์ผ๋ก ์์ํฉ๋๋ค๋ง, ์๋ฐ ๊ธฐ๋ฐ์ ์ค์ ์์๋ WebSecurityConfigurerAdapter๋ฅผ ์์๋ฐ์ ํด๋์ค์ @EnableWebSecurity ์ด๋ ธํ ์ด์ ์ ๋ช ์ํ๋ ๊ฒ๋ง์ผ๋ก๋ springSecurityFilterChain๊ฐ ์๋์ผ๋ก ํฌํจ๋์ด์ง๋๋ค.
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
}
๊ทธ๋ฆฌ๊ณ ํฌํจ๋ springSecurityFilterChain์ ๋ฑ๋กํ๊ธฐ ์ํด์๋ AbstractSecurityWebApplicationInitializer๋ฅผ ์์๋ฐ์ WebApplicationInitializer๋ฅผ ๋ง๋ค์ด๋๋ฉด ๋ฉ๋๋ค.
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
}
XML์ ์์ฑํ๋ ๊ฒ๋ณด๋ค๋ ์๋ฐ ๊ธฐ๋ฐ์ ๊ตฌ์ฑ์ด ๋์ฑ ์ฝ์ต๋๋ค.
๊ทธ ๋ค์์๋ ๋ณดํต ์คํ๋ง MVC๋ฅผ ์ด์ฉํด์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ฑํ๊ธฐ ๋๋ฌธ์ ApplicationIniticalizer์ WebSecurityConfigurerAdapter๋ฅผ ์์๋ฐ์ ํด๋์ค๋ฅผ getRootConfigClasses() ๋ฉ์๋์ ์ถ๊ฐํ๋ ๊ฒ์ผ๋ก ์คํ๋ง ์ํ๋ฆฌํฐ์ ๋ํ ๊ธฐ๋ณธ์ ์ธ ์ ์ฉ์ ๋์ ๋๋ค.
public class ApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { WebSecurityConfig.class };
}
// ... other overrides ...
}
์ด๋ก์จ ์ ์ถ ํด๋ณผ๋, springSecurityFilterChain์ ๋ฃจํธ ์ปจํ ์คํธ์์ ๋ฑ๋ก๋์ด์ผํ๋ ๊ฒ์ ์ ์ ์๊ฒ ์ต๋๋ค. ๊ฐ ํํฐ์ ๋ํ ์ฐ์ ์์์ ๋ํด์๋ ์ถ๊ฐ์ ์ผ๋ก ์ ํ์์ฑ์ด ์๊ธฐ๋ ๋ถ๋ถ์ ๋๋ค.
HttpSecurity
๊ทธ๋ฆฌ๊ณ ๋์ configure(HttpSecurity http) ๋ฉ์๋๋ฅผ ํตํด์ ์ฐ๋ฆฌ๋ง์ ์ธ์ฆ ๋งค์ปค๋์ฆ์ ๊ตฌ์ฑํด์ผํฉ๋๋ค. ๊ทธ๋ฐ๋ฐ ์์ง๊น์ง๋ ์ด๋ป๊ฒ ๊ตฌ์ฑํด์ผํ ์ง ๋ง๋งํ๊ธฐ๋ง ํฉ๋๋ค. ๊ด๋ จ๋ ์ ๋ณด๋ฅผ ์ข๋ ์ฐพ์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/users/{userId}").access("@authenticationCheckHandler.checkUserId(authentication,#userId)")
.antMatchers("/admin/db/**").access("hasRole('ADMIN_MASTER') or hasRole('ADMIN') and hasRole('DBA')")
.antMatchers("/register/**").hasRole("ANONYMOUS")
.and()
.formLogin()
.loginPage("/login")
.usernameParameter("email")
.passwordParameter("password")
.successHandler(successHandler())
.failureHandler(failureHandler())
.permitAll();
}
๋ญ ์ฌ์ค์ ๋ณ๊ฒ ์์ต๋๋ค. HttpSecurity์ ์ธ์คํด์ค๋ฅผ ํตํด ์์ ๋ง์ ์ธ์ฆ ๋งค์ปค๋์ฆ์ ๋ง๋ค ์ ์๋ ๊ฒ์ด๋ผ๊ณ ๋ณด๋ฉด ๋ฉ๋๋ค. ๋๋ถ๋ถ์ ์ค์ ์ ์ฌ๊ธฐ๋ฅผ ํตํ๊ฒ ๋๋๊น์ .authorizeRequests()๋ฅผ ํตํด ์์ฒญ์ ๋ํ ๊ถํ์ ์ง์ ํ ์ ์๊ฒ ๋๋ ๊ฒ ๊ฐ์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ๊ธฐ๋ณธ์ ์ธ ํํ์ .anyRequest().authenticated()
๋ผ๋ ์๋ฏธ๋ ์ด๋ ํ ์์ฒญ์ด๋ ์ง ์ธ์ฆ๋์ด์ผํ๋ค๋ ๊ฒ์ด๊ฒ์ฃ ? .formLogin()์ ํผ์ ํตํ ๋ก๊ทธ์ธ์ ์ด์ฉํ๋ค๋ ์๋ฏธ์ด๋ฉฐ, .loginPage(โ/loginโ)์ ํตํด ๋ก๊ทธ์ธ ํ์ด์ง๋ /login ๊ฒฝ๋ก๋ก ์ ๊ณตํ๋ฉฐ /login์ POST ์์ฒญ์ด ๋ฐ๋ก ๋ก๊ทธ์ธ ์ฒ๋ฆฌ ๊ณผ์ ์ด๋ผ๋ ๊ฒ์
๋๋ค.
ํ์ฅ๋ ํํ์์์ .antMatchers().hasRole() ๋๋ .antMatchers().access()๋ ํด๋น ๊ฒฝ๋ก์ ๋ํด์ ์ด๋ ํ ๊ถํ์ ๊ฐ์ ธ์ผ๋ง ์ ๊ทผํ ์ ์๋๊ฐ๋ฅผ ํํํ๋ ๊ฒ์ ๋๋ค. ๋ค์์ antMatchers() ๋ค์์ผ๋ก ์ง์ ํ ์ ์๋ ํญ๋ชฉ๋ค์ ๋๋ค.
- anonymous()
์ธ์ฆ๋์ง ์์ ์ฌ์ฉ์๊ฐ ์ ๊ทผํ ์ ์์ต๋๋ค. - authenticated()
์ธ์ฆ๋ ์ฌ์ฉ์๋ง ์ ๊ทผํ ์ ์์ต๋๋ค. - fullyAuthenticated()
์์ ํ ์ธ์ฆ๋ ์ฌ์ฉ์๋ง ์ ๊ทผํ ์ ์์ต๋๋ค(?) - hasRole() or hasAnyRole()
ํน์ ๊ถํ์ ๊ฐ์ง๋ ์ฌ์ฉ์๋ง ์ ๊ทผํ ์ ์์ต๋๋ค. - hasAuthority() or hasAnyAuthority()
ํน์ ๊ถํ์ ๊ฐ์ง๋ ์ฌ์ฉ์๋ง ์ ๊ทผํ ์ ์์ต๋๋ค. - hasIpAddress()
ํน์ ์์ดํผ ์ฃผ์๋ฅผ ๊ฐ์ง๋ ์ฌ์ฉ์๋ง ์ ๊ทผํ ์ ์์ต๋๋ค. - access()
SpEL ํํ์์ ์ํ ๊ฒฐ๊ณผ์ ๋ฐ๋ผ ์ ๊ทผํ ์ ์์ต๋๋ค. - not()
์ ๊ทผ ์ ํ ๊ธฐ๋ฅ์ ํด์ ํฉ๋๋ค. - permitAll() or denyAll()
์ ๊ทผ์ ์ ๋ถ ํ์ฉํ๊ฑฐ๋ ์ ํํฉ๋๋ค. - rememberMe()
๋ฆฌ๋ฉค๋ฒ ๊ธฐ๋ฅ์ ํตํด ๋ก๊ทธ์ธํ ์ฌ์ฉ์๋ง ์ ๊ทผํ ์ ์์ต๋๋ค.
Role์ ์ญํ ์ด๊ณ Authority๋ ๊ถํ์ด์ง๋ง ์ฌ์ค์ ํํ์ ์ฐจ์ด์ ๋๋ค. Role์ โADMINโ์ผ๋ก ํํํ๊ณ Authority๋ โROLE_ADMINโ์ผ๋ก ํ๊ธฐํ๋ ๊ฒ ๋ฟ์ ๋๋ค. ์ค์ ๋ก hasRole()์ ROLE_ADMIN์ผ๋ก ํ๊ธฐํ๋ฉด ROLE์ ์ง์ฐ๋ผ๋ ์๋ฌ๋ฅผ ๋ณผ์ ์๊ฒ ๋ฉ๋๋ค.
์คํ๋ง ์ํ๋ฆฌํฐ ํ๊ทธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ํ SpEL ํํ์์ ์ฌ์ฉํ ์ ์์ต๋๋ค. <sec:authentication /> ์ด๋ ๊ฒ ๋ง์ ๋๋ค.
AuthenticationManagerBuilder
AuthenticationManagerBuilder๋ฅผ ํตํด ์ธ์ฆ ๊ฐ์ฒด๋ฅผ ๋ง๋ค ์ ์๋๋ก ์ ๊ณตํ๊ณ ์์ต๋๋ค. ์๋๋ ์คํ๋ง ์ํ๋ฆฌํฐ ๋ ํผ๋ฐ์ค์์ ์๋ ค์ฃผ๋ ๋ฐฉ์์ธ๋ฐ AuthenticationManagerBuilder๋ฅผ ๋ฉ์๋๋ฅผ ํตํด ์ฃผ์ ๋ฐ์ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ ๋๋ค. WebSecurityConfigurerAdapter์ configure(AuthenticationManagerBuilder auth)๋ฅผ ์ค๋ฒ๋ผ์ด๋ฉํ๋ ๊ฒ์ ๋ํ ์ฐจ์ด๋ ์์ต๋๋ค.
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("scott").password("tiger").roles("ROLE_USER");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// TODO Auto-generated method stub
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN", "DBA");
auth.inMemoryAuthentication().withUser("scott").password("tiger").roles("USER", "SETTING");
}
์ํ๋ ๋ฐฉ์์ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค. ํ์ง๋ง, ์คํ๋ง ์ํ๋ฆฌํฐ ๋ ํผ๋ฐ์ค์ ์ํ์ ๋ง๋ ๊ฐ๋ฐ์๊ฐ ์ ์ฒซ๋ฒ์งธ ๋ฐฉ์์ผ๋ก ์ค๋ช ํ๋์ง์ ๋ํ ์๋ฏธ๋ฅผ ์๊ฐํด๋ด ์๋ค.
์์๋ ๋ถ์ด ์๋ค๋ฉด ์๋ ค์ฃผ์๋ฉด ๊ฐ์ฌํ๊ฒ ์ต๋๋ค.
์คํ๋ง ์ํ๋ฆฌํฐ ์์ผ๋ก ๋ค์ด๊ฐ๊ธฐ
์ง๊ธ๊น์ง ํ๋ ์ค์ ๋ค์ ์์ฃผ ๊ธฐ๋ณธ์ ์ธ ํํ๋ค์ด๊ธฐ ๋๋ฌธ์ ์ค์ ๋ก ์ฐ๋ฆฌ๋ง์ ์ธ์ฆ ๋งค์ปค๋์ฆ์ผ๋ก ์ปค์คํฐ๋ง์ด์ง์ ํ ๋ค ์คํ๋ง ์ํ๋ฆฌํฐ์์ ์ ๊ณตํ๋ ๋ถ๊ฐ์ ์ธ ๊ธฐ๋ฅ์ ์ถ๊ฐํด์ผํฉ๋๋ค.
Method Security
์คํ๋ง ์ํ๋ฆฌํฐ 2.0 ์์๋ถํฐ ์๋น์ค ๊ณ์ธต์ ๋ฉ์๋์ ๋ณด์์ ์ถ๊ฐํ ์ ์๋๋ก ์ง์ํฉ๋๋ค. @Secured ์ด๋ ธํ ์ด์ ๋ฟ๋ง ์๋๋ผ JSR-250 ์ด๋ ธํ ์ด์ ๋ ์ง์ํ๋๋ก ์ ๊ณต๋ฉ๋๋ค. ์คํ๋ง ์ํ๋ฆฌํฐ 3.0์์๋ ํํ ๊ธฐ๋ฐ์ ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ ์ ์๊ฒ ๋ฉ๋๋ค.
์ฐ๋ฆฌ๋ Configuration ํด๋์ค์ @EnableGlobalMethodSecurity๋ฅผ ์ ์ฉํจ์ผ๋ก์จ ์ด๋ ธํ ์ด์ ๊ธฐ๋ฐ์ ๋ณด์์ ํ์ฑํ์ํฌ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์คํ๋ง ์ํ๋ฆฌํฐ์ @Secured ์ด๋ ธํ ์ด์ ์ ํ์ฑํ์ํค๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์ฑํ๊ฒ ๋ฉ๋๋ค.
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig {
// ...
}
๊ทธ๋ฆฌ๊ณ ๋์ ํด๋์ค ๋๋ ์ธํฐํ์ด์ค์ ๋ฉ์๋์ @Secured ์ด๋ ธํ ์ด์ ์ ์ถ๊ฐํ๋ฉด ๊ทธ์ ๋ฐ๋ฅธ ํด๋น ๋ฉ์๋์ ๋ํ ์ก์ธ์ค๊ฐ ์ ํ๋ฉ๋๋ค.
public interface BankService {
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();
@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}
์ญํ ๋จ์๋ก ์ ์ฝ์กฐ๊ฑด์ ์ง์ ํ ์ ์์์ ์ ์ ์์ต๋๋ค.
์คํ๋ง ์ํ๋ฆฌํฐ 3.0๋ถํฐ ์ง์ํ๋ ํํ์ ๊ธฐ๋ฐ์ ๋ฌธ๋ฒ์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์ฑํฉ๋๋ค.
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// ...
}
public interface BankService {
@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);
@PreAuthorize("isAnonymous()")
public Account[] findAccounts();
@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
}
Remember-Me
๋ฆฌ๋ฉค๋ฒ-๋ฏธ ๊ธฐ๋ฅ์ ๋๋์ฒด ๋ฌด์์ผ๊น์? ๊ตฌ๊ธ์ what is remember me ๋ผ๋ ํค์๋๋ก ๊ฒ์์ ํด๋ณด์์ต๋๋ค.
๋จ์ํ ์์ด๋๋ฅผ ๊ธฐ์ตํด๋๋ ๊ฒ์ด ์๋๋ผ ๋ก๊ทธ์ธ ์ ๋ณด๋ฅผ ์ ์งํ๋ ๊ฒ์ ๋งํฉ๋๋ค.
Do NOT use โRemember Meโ on any public computer, on campus, in Internet cafeโs, or anywhere else where you cannot control access!
@Override
protected void configure(HttpSecurity http) throws Exception {
http.rememberMe().rememberMeParameter("remember-me").key(REMEMBER_ME_KEY).rememberMeServices(persistentTokenBasedRememberMeServices());
}
@Bean
public PersistentTokenBasedRememberMeServices persistentTokenBasedRememberMeServices(){
PersistentTokenBasedRememberMeServices persistentTokenBasedRememberMeServices =
new PersistentTokenBasedRememberMeServices(REMEMBER_ME_KEY, userDetailsService, persistentTokenRepository());
return persistentTokenBasedRememberMeServices;
}
@Bean
public PersistentTokenRepository persistentTokenRepository(){
TokenRepositoryImpl tokenRepositoryImpl = new TokenRepositoryImpl();
return tokenRepositoryImpl;
}
์ ๋ ์์์ฑ ๊ธฐ๋ฐ์ ๋ฆฌ๋ฉค๋ฒ-๋ฏธ ๊ธฐ๋ฅ์ ์ถ๊ฐํ์์ต๋๋ค. ์ด๋, ๋ฆฌ๋ฉค๋ฒ-๋ฏธ ํ ํฐ์ ์ ์ฅํ ์ ์๋๋ก TokenRepository ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํด์ผํ๋๋ฐ์. ์ผ์ข ์ ์๋น์ค ๊ฐ์ฒด๋ผ๊ณ ์๊ฐํ์๋ฉด ๋ฉ๋๋ค.
@Transactional
public class TokenRepositoryImpl implements PersistentTokenRepository {
@Autowired
private TokenRepository tokenRepository;
@Override
public void createNewToken(PersistentRememberMeToken token) {
// TODO Auto-generated method stub
Token newToken = new Token();
newToken.setEmail(token.getUsername());
newToken.setToken(token.getTokenValue());
newToken.setLast_used(token.getDate());
newToken.setSeries(token.getSeries());
tokenRepository.save(newToken);
}
@Override
public void updateToken(String series, String tokenValue, Date lastUsed) {
// TODO Auto-generated method stub
Token updateToken = tokenRepository.findOne(series);
updateToken.setToken(tokenValue);
updateToken.setLast_used(lastUsed);
updateToken.setSeries(series);
tokenRepository.save(updateToken);
}
@Override
public PersistentRememberMeToken getTokenForSeries(String series) {
// TODO Auto-generated method stub
Token token = tokenRepository.findOne(series);
PersistentRememberMeToken persistentRememberMeToken = new PersistentRememberMeToken(token.getEmail(), series, token.getToken(), token.getLast_used());
return persistentRememberMeToken;
}
@Override
public void removeUserTokens(String username) {
// TODO Auto-generated method stub
tokenRepository.deleteByEmail(username);
}
}
๋ณธ ์ ํ๋ฆฌ์ผ์ด์
์์์ Email์ ์ ๋ํฌํ ์์ฑ์ ๊ฐ์ง๋๋ค.
Password Encoding
AuthenticationManagerBuilder.userDetailsService().passwordEncoder()๋ฅผ ํตํด ํจ์ค์๋ ์ํธํ์ ์ฌ์ฉ๋ PasswordEncoder ๊ตฌํ์ฒด๋ฅผ ์ง์ ํ ์ ์์ต๋๋ค. PasswordEncoder ์ธํฐํ์ด์ค๋ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์ฑ๋์ด ์์ต๋๋ค.
public interface PasswordEncoder {
/**
* Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or
* greater hash combined with an 8-byte or greater randomly generated salt.
*/
String encode(CharSequence rawPassword);
/**
* Verify the encoded password obtained from storage matches the submitted raw
* password after it too is encoded. Returns true if the passwords match, false if
* they do not. The stored password itself is never decoded.
*
* @param rawPassword the raw password to encode and match
* @param encodedPassword the encoded password from storage to compare with
* @return true if the raw password, after encoding, matches the encoded password from
* storage
*/
boolean matches(CharSequence rawPassword, String encodedPassword);
}
๋จ์ํ ์ธ์ฝ๋ฉํ๋ ํจ์์ ํ๋ฌธ์ผ๋ก ์ ๊ณต๋๋ ํจ์ค์๋์ ์ธ์ฝ๋ฉ๋์ด์๋ ํจ์ค์๋(์๋ฅผ๋ค์ด, ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ธ์ฝ๋ฉ๋์ด ์ ์ฅ๋์ด ์๋ ํจ์ค์๋)๋ฅผ ๋น๊ตํ ์ ์์ต๋๋ค.
์ ๋ PasswordEncoder ๊ตฌํ์ฒด์ธ BCryptPasswordEncoder๋ฅผ ์ง์ ํ์ต๋๋ค. ๋น์ฐํ ์ง์ ๊ตฌํ์ฒด๋ฅผ ๋ง๋ค์ด์ ์ ์ฉํด๋ ๋ฉ๋๋ค! (์๋ฌด๋๋ ์ง์ ๋ง๋ค์ด์ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ์ข๊ฒ์ฃ ?)
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// TODO Auto-generated method stub
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
PasswordEncoder๋ฅผ ๋น์ผ๋ก ๋ฑ๋กํด๋๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ ์ฅ๋ ํจ์ค์๋๋ฅผ ๋น๊ตํ ์ ์์ต๋๋ค. ์ ์ฅ๋ ํจ์ค์๋๋ PasswordEncoder์ ์ํด ์ํธํ๋ ํ๋ฌธ์ด๊ธฐ ๋๋ฌธ์ ๋๋ค.
@RequestMapping(value="/users/{userId}", method=RequestMethod.POST)
@PreAuthorize("#updateUser.email == authentication.name")
public String update(@PathVariable("userId") Long id, @ModelAttribute @Valid UserDTO.Update updateUser, Model model){
User currentUser = userService.findOne(id);
if(!passwordEncoder.matches(updateUser.getPassword(), currentUser.getPassword())){
throw new RuntimeException("Not password equals...");
}
//...
}
WebSecurity Ignoring
์คํ๋ง ์ํ๋ฆฌํฐ ๋ ํผ๋ฐ์ค์๋ ๋ฌ๋ฆฌ ๋ฆฌ์์ค์ ๊ด๋ จํด์๋ WebSecurity.ignoring()
๋ฅผ ์ด์ฉํด์ ๋ณด์์ด ์ ์ฉ๋์ง ์๋๋ก ํ ์ ์๋๋ก ์ง์ํฉ๋๋ค. ์คํ๋ง ์ํ๋ฆฌํฐ API ๋ฌธ์์์ ํ์ธํ ์ ์๋๋ฐ ๋ ํผ๋ฐ์ค์ ์ค๋ช
์ด ์๋ค๋ ๊ฒ์ด ์ข ์์ฌ์ด ๋ถ๋ถ์
๋๋ค.
์... permitAll๊ณผ ignoring์ ์ฐจ์ด๊ฐ ์๋๊ฐ๋ฅผ ์์๋๋ถ์ ๋๊ธ ๋จ๊ฒจ์ฃผ์๊ธฐ ๋ฐ๋๋๋ค ใ
ใ
Localization
์คํ๋ง ์ํ๋ฆฌํฐ๋ ๋ฉ์์ง์ ๋ํ ํ์งํ๋ฅผ ์ง์ํฉ๋๋ค. ๋ฉ์์ง ์์ค ๊ด๋ จ ํ๋กํผํฐ ํ์ผ๋ค์ spring-security-core.jar์ ํฌํจ ๋์ด์ ธ์์ต๋๋ค.
์ฐ๋ฆฌ๋ ๋ฉ์์ง ํ๋กํผํฐ ํ์ผ๋ค์ ๋ฉ์์ง์์ค๋ก ๋ฑ๋กํ๋ฉด ๋ฉ๋๋ค.
@Configuration
public class ApplicationConfiguration extends WebMvcConfigurerAdapter {
@Bean
public MessageSource messageSource(){
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.addBasenames("security/messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor(){
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("lang");
return localeChangeInterceptor;
}
@Bean
public LocaleResolver localeResolver(){
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
localeResolver.setDefaultLocale(Locale.KOREAN);
return localeResolver;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// TODO Auto-generated method stub
registry.addInterceptor(localeChangeInterceptor());
}
}
๋ฉ์์ง ํ๋กํผํฐ ํ์ผ๋ค์ src/main/resource/security ํด๋์ ์์นํ๊ณ ์์ต๋๋ค.
AuthenticationSuccessHandler & AuthenticationFailureHandler
OKKY์ ์ง๋ฌธ๊ฒ์ํ์์ ๋ก๊ทธ์ธ ์คํจ๋ฅผ ์ด๋ป๊ฒ ์ฒดํฌํ๋๊ฐ์ ๋ํด์ ์ง๋ฌธํ๋ ๊ธ์ ๋ณด์์ต๋๋ค. ์ ๋ ์คํ๋ง ์ํ๋ฆฌํฐ์์ ์ฌ์ฉํ๋ AuthenticationFailureHandler ๊ตฌํ์ฒด๋ฅผ ์์๋ฐ์์ ์ฒ๋ฆฌํ๋๋ก ํ๊ฒ ์ต๋๋ค.
public class AuthenticationSuccessHandlerImpl extends SavedRequestAwareAuthenticationSuccessHandler {
private static final Logger logger = LoggerFactory.getLogger(AuthenticationSuccessHandlerImpl.class);
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
logger.info("Login Success... - {}", authentication.getPrincipal());
response.sendRedirect("/?login");
}
}
AuthenticationSuccessHandler ๊ตฌํ์ฒด์์๋ ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ์๋ ํธ์ถ(์ธ์ฆ ๊ฐ์ฒด๊ฐ ์์ฑ๋์ด์ง ํ)๋๊ธฐ ๋๋ฌธ์ Authentication ์ธ์คํด์ค ํ๋ผ๋ฏธํฐ๋ฅผ ์ด์ฉํ ์ ์์ต๋๋ค. authentication.getPrincipal()์ ํธ์ถํ๊ฒ ๋๋ฉด ์ ๊ฐ์ ๊ฒฝ์ฐ์๋ org.springframework.security.core.userdetails.User ๊ฐ ์๋ com.kdev.app.security.userdetails.UserDetails๋ฅผ ์ด์ฉํ ์ ์์ต๋๋ค.
public class AuthenticationFailureHandlerImpl extends SimpleUrlAuthenticationFailureHandler {
private static final Logger logger = LoggerFactory.getLogger(AuthenticationFailureHandlerImpl.class);
public AuthenticationFailureHandlerImpl() {
this.setDefaultFailureUrl("/login?error");
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
logger.info("Login Failed... - {}",request.getParameter("email"));
super.onAuthenticationFailure(request, response, exception);
}
}
๋ก๊ทธ์ธ ์คํจ ์ SimpleUrlAuthenticationFailureHandler์ defaultFailureUrl๋ฅผ ์ง์ ํ๋ฉด SPRING_SECURITY_LAST_EXCEPTION์ ๋ํ ์ ๋ณด๋ฅผ ๊ฐ์ง๋ฉด์ ํด๋น ๊ฒฝ๋ก๋ก ์ด๋ํ๊ฒ ๋ฉ๋๋ค.
AuthenticationException์ผ๋ก ๋ก๊ทธ์ธ ์คํจ์ ์ด์ ๋ ์ฒดํฌํ ์ ์๊ฒ ๋์ฃ
์คํ๋ง ์ํ๋ฆฌํฐ ํ์ด์ง
์คํ๋ง ์ํ๋ฆฌํฐ์์ ์ ๊ณตํด์ฃผ๋ ๊ธฐ๋ณธ ๋ก๊ทธ์ธ ํ์ด์ง ๋์ ์ ์ฐ๋ฆฌ๋ง์ ๋ก๊ทธ์ธ ํ์ด์ง๋ฅผ ๋ง๋ค์ด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--
<meta id="_csrf" name="_csrf" content="${_csrf.token}" />
<meta id="_csrf_header" name="_csrf_header" content="${_csrf.headerName}" />
-->
<sec:csrfMetaTags />
<title>Spring Security + JPA</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="page-header">
<h1>Login Page <small>with Bootstrap</small></h1>
</div>
<div class="container-fluid">
<form method="post" action="/login">
<%-- <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> --%>
<sec:csrfInput />
<div class="form-group">
<label for="email">์ด๋ฉ์ผ</label>
<input type="text" class="form-control" name="email" placeholder="Email" required>
</div>
<div class="form-group">
<label for="password">๋น๋ฐ๋ฒํธ</label>
<input type="password" class="form-control" name="password" placeholder="Password" required>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="remember-me" > Remember me
</label>
</div>
<div class="text-center">
<c:if test="${not empty SPRING_SECURITY_LAST_EXCEPTION}">
<span class="text-danger"><c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}"/></span>
<c:remove var="SPRING_SECURITY_LAST_EXCEPTION" scope="session"/>
</c:if>
</div>
<hr>
<button type="submit" class="btn btn-default">๋ก๊ทธ์ธ</button>
<a class="btn btn-default" href="/register">ํ์๊ฐ์
</a>
</form>
</div>
<div class="container">
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
SPRING_SECURITY_LAST_EXCEPTION
์ ์กด์ฌ ์ฌ๋ถ์ ๋ฐ๋ผ์ ์คํ๋ง ์ํ๋ฆฌํฐ์์ ๋ฐ์ํ๋ ์์ธ์ ๋ํ ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํ ์ ์์ต๋๋ค.
์คํ๋ง ์ํ๋ฆฌํฐ ๋ ํผ๋ฐ์ค์์๋ ๋จ์ํ ํ๋ผ๋ฏธํฐ์ ์กด์ฌ์ฌ๋ถ์ ๋ฐ๋ผ์ ์ถ๋ ฅํํ๋ฅผ ์ง์ ํ๊ณ ์์ต๋๋ค. ์ด ๋ถ๋ถ๋ ์ฐธ ์์ฝ์ต๋๋ค.
UserDetails & UserDetailsService
์ ์ฃ์กํฉ๋๋ค. UserDetails ์ธํฐํ์ด์ค์ ๋ํด์ ์ค๋ช ์ ์ํ๋ค์ ใ ใ
์คํ๋ง ์ํ๋ฆฌํฐ๋ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ UserDetails ๊ตฌํ์ฒด๋ก ์ฌ์ฉํฉ๋๋ค. ๊ทธ๋์ ์คํ๋ง ์ํ๋ฆฌํฐ๋ org.springframework.security.core.userdetails.User๋ผ๋ ํด๋์ค๋ฅผ ์ ๊ณตํฉ๋๋ค. ๊ทธ๋ฌ๋, ์ด๋ฆ๊ณผ ํจ์ค์๋ ๊ทธ๋ฆฌ๊ณ ๊ถํ๋ค์ ๋ํ ํ๋๋ง ์กด์ฌํ๊ธฐ ๋๋ฌธ์ ์ด๋ฉ์ผ ์ ๋ณด ๋๋ ํ๋กํ ์ด๋ฏธ์ง ๊ฒฝ๋ก ๋ฑ๊ณผ ๊ฐ์ ๋ถ๊ฐ์ ์ธ ์ ๋ณด๋ฅผ ๋ด์ ์ ์์ต๋๋ค.
๋ฐ๋ผ์, UserDetails ๊ตฌํ์ฒด๋ฅผ ์ง์ ๋ง๋ค์ด์ผ ํฉ๋๋ค. org.springframework.security.core.userdetails.User ์์ฒด๋ UserDetails ๊ตฌํ์ฒด์ด๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ ์์๋ฐ์์ ๊ตฌํํด๋ ๋ฉ๋๋ค.
public class UserDetails extends org.springframework.security.core.userdetails.User {
private static final long serialVersionUID = -4855890427225819382L;
private Long id;
private String nickname;
private String email;
private Date createdAt;
public UserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
// TODO Auto-generated constructor stub
}
public UserDetails(User user){
super(user.getEmail(), user.getPassword(), user.isAccountNonExpired(), user.isAccountNonLocked(), user.isCredentialsNonExpired(), user.isEnabled(), authorities(user));
this.id = user.getId();
this.nickname = user.getNickname();
this.email = user.getEmail();
this.createdAt = user.getCreatedAt();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
private static Collection<? extends GrantedAuthority> authorities(User user) {
// TODO Auto-generated method stub
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
user.getAuthorities().forEach(a -> {
authorities.add(new SimpleGrantedAuthority(a.getAuthority()));
});
return authorities;
}
public UserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities,
String nickname) {
super(username, password, authorities);
this.nickname = nickname;
this.email = username;
}
public UserDetails(String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
// TODO Auto-generated constructor stub
}
}
๊ทธ๋ฌ๋ฉด ์ฐ๋ฆฌ๋ Authentication.getPrincipal() ๋ฉ์๋๋ฅผ ํตํด ์ป์ Principal ๊ฐ์ฒด๋ฅผ ํตํด์๋ ๋ถ๊ฐ์ ์ธ ํ๋์ ์ ๊ทผํ ์ ์์ต๋๋ค.
<sec:authentication property="principal.email"/>
org.springframework.security.core.userdetails.UserDetailsService ๊ตฌํ์ฒด๋ ์คํ๋ง ์ํ๋ฆฌํฐ ์ธ์ฆ ์์ ์ฌ์ฉ๋ฉ๋๋ค. UserRepository๋ฅผ ํตํด ์์์ฑ์ผ๋ก ์ ์ฅ๋ ์ธ์ฆ์ ๋ณด๋ฅผ ๊ฒ์ํ ํ ์กด์ฌํ์ง ์๋ค๋ฉด UsernameNotFoundException์ ๋์ง๊ณ ์๋ค๋ฉด UserDetails ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค.
@Service
public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
// TODO Auto-generated method stub
User user = userRepository.findByEmail(email);
if(user == null){
throw new UsernameNotFoundException(email);
}
com.kdev.app.security.userdetails.UserDetails userDetails = new com.kdev.app.security.userdetails.UserDetails(user);
return userDetails;
}
}
๊ทธ๋ฐ๋ฐ ์ข ์ด์ํ๋ค๊ณ ๋๊ปด์ง๋๋ค. ๊ทธ๋ฌ๋ฉด ํจ์ค์๋ ๊ฒ์ฆ์ ์ธ์ ํ๋๊ฒ์ผ๊น์? ๋ฐ๋ก AuthenticationProvider ๊ตฌํ์ฒด์์ ์งํํ๊ฒ ๋ฉ๋๋ค. AuthenticationProvider ๊ตฌํ์ฒด์์๋ authenticate() ๋ฉ์๋๋ฅผ ํตํด์ Authentication ๊ฐ์ฒด(UsernamePasswordAuthentication)๋ฅผ ๋ฐํํฉ๋๋ค. ์ฆ, ๋ฐํํ๊ธฐ ์ง์ ์ ํจ์ค์๋๋ฅผ ๊ฒ์ฆํ๋ ๊ฒ์ ๋๋ค.
์ฐ๋ฆฌ๋ AuthenticationProvider๋ฅผ ์ง์ ์ ์ผ๋ก ๊ตฌํํ์ง ์์์ผ๋๊น ์ด ๋ถ๋ถ์ ๋ํด์ ๋ชจ๋ฅด๊ณ ๋์ด๊ฐ ๋ป ํ์ต๋๋ค.
์ค์ฟ ๋์ ์คํ๋ง ์ํ๋ฆฌํฐ ์ปค์คํ ๋ก๊ทธ์ธ์ด๋ผ๋ ๊ธ์์ ์ง์ AuthenticationProvider๋ฅผ ๊ตฌํํด์ ์ฌ์ฉํ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
๋๋ง์น๋ฉฐ
์ฐ๋ฆฌ๊ฐ ์์๋ณธ ๊ฒ ์ด์ธ์๋ ์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ ์ ๊ณตํ๋ ๊ธฐ๋ฅ๋ค์ด ๋ ์กด์ฌํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์น์์ผ, RESTful API๋ฑ์๋ ๋ณด์์ ์ ์ฉํ ์ ์์ต๋๋ค.
๊ณต๋ถํ๋ฉด์ ์๊ฒ๋ ๋ถ๋ถ์ธ๋ฐ, ์คํ๋ง ํ๋ ์์ํฌ์์ ์ ๊ณตํ๋ ์ธ-๋ฉ๋ชจ๋ฆฌ ๊ธฐ๋ฐ ์ธ์ฆ ๊ฐ์ฒด(inMemoryAuthentication())๋ ํ๋ผ๋ฏธํฐ๋ก ์ ๊ณต๋๋ UserDetails์ ์ ๋ณด๋ฅผ ํ์ฉํ์ฌ User ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ธฐ ๋๋ฌธ์ UserDetails์ ๊ตฌํ์ฒด๋ฅผ ๋ง๋ ๋ค ํ๋๋ผ๋ ์๋ฏธ๊ฐ ์๊ฒ ๋ฉ๋๋ค. ๋ง์ฝ, ์ธ ๋ฉ๋ชจ๋ฆฌ ๋ฐฉ์๊ณผ ์์์ฑ ๋ฐฉ์์ ํผ์ฉํด์ ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉด ์ด ๋ถ๋ถ์ ๋ํด์ ์ฐ๊ตฌํด๋ณผ ํ์๊ฐ ์๊ฒ ์ต๋๋ค.
๋ค์์ ์คํ๋ง ์ํ๋ฆฌํฐ์ ๋ํด์ ์์๋ณผ ๋ ์ข์ ํฌ์คํธ ๋ฐ ๋์์๋ค์ ๋๋ค.
- ์ค์ฟ ๋์ ์คํ๋ง ์ํ๋ฆฌํฐ ์ปค์คํ ๋ก๊ทธ์ธ
- ์ ํ๊ฑด๋ด๋์ ์คํ๋ง ์ํ๋ฆฌํฐ
- ์๋ผํ์ฌ๋์ ์คํ๋ง ์ํ๋ฆฌํฐ ๋ฐ๋ผํด๋ณด๊ธฐ
- ๋ฐฑ๊ธฐ์ ๋์ ์คํ๋ง ์ํ๋ฆฌํฐ
- ํ๋ง๋์ ์คํ๋ง ์ํ๋ฆฌํฐ
์๋๊ฒ ์์ผ๋ ์ฝ๊ฒ ์ดํดํ์ง ๋ชปํ๋ ๋ถ๋ถ์ด ๋ง์ต๋๋ค. ๋ง์ ์ ์ ๊ฐ๋ฐ์๋ค์ ์์ํฉ๋๋ค ^ใ ก^