시큐리티 로그인과 권한 처리
시큐리티 로그인
1. 시큐리티가 /login 주소 요청이 오면 낚아채서 로그인을 진행시킨다.
@Configuration
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록이 됩니다.
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
@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("/loginForm")
.loginProcessingUrl("/login") // /login 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행해줍니다.
.defaultSuccessUrl("/");
}
}
formLogin().loginPage().loginProcessingUrl("/login").defaultSuccessUrl()
2. 로그인 진행이 완료되면 session을 만들어 줍니다.
시큐리티 별도의 session 공간이 있음.
- Security ContextHolder라는 key 값으로 session을 저장함.
- 그런데 시큐리티 session에 저장 가능한 객체 타입이 정해져 있음. → "Authentication 객체"
- 우리는 이 Authentication 객체에 User 정보를 담아야 하는데 그러기 위해서는 UserDetails 객체 타입으로 저장해야함.
2-1. UserDetails 객체 만들기
이렇게 UserDetails를 상속받아서 같은 타입 객체로 만들 수 있음.
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<>();
collect.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole();
}
});
return collect;
}
...
}
2-2. Authentication 객체 만들기
SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
...
http.authorizeRequests()
...
.loginProcessingUrl("/login") // /login 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행해줍니다.
...
}
}
PrincipalDetailsService.java
@Service
public class PrincipalDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
// 시큐리티 session(내부 Authentication(내부 UserDetails))
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("username :" + username);
User userEntity = userRepository.findByUsername (username);
if(userEntity != null) {
return new PrincipalDetails(userEntity);
}
return null;
}
}
/login 요청이 오면 자동으로 UserDetailsService 타입으로 IoC 되어있는 loadByUsername 함수가 실행된다.
이 때, 파라미터로 username 정보를 가지고 온다.
loadByUsername()의 파라미터로 넘어오는 username은 loginForm에서 넘어오는 username이다.
loginForm.html
<form action = "/login" method="POST">
<input type="text" name="username" placeholder="Username" /> <br/>
<input type="password" name="password" placeholder="Password" /> <br/>
<button>로그인</button>
</form>
그렇게 username을 받아서 findByUsername()을 통해 해당 username을 가진 객체가 userRepository에 있는지 탐색한 후 있는 경우에 UserDetails 타입 객체를 리턴하는데,
이 때, 시큐리티 세션 내부에 해당 UserDetails 객체를 포함하는 Authentication 타입 객체가 들어가게된다.
시큐리티 권한 처리
1. 비밀번호 인코딩
회원가입에서 기본 비밀번호를 인코딩하여 DB에 저장하여야 한다. 이 때, encodePwd() 함수를 IoC에 등록해준다.
indexController.java
@Controller //View를 리턴하겠다!!
public class indexController {
@Autowired
private UserRepository userRepository;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@PostMapping("/join")
public String join(User user) {
System.out.println(user);
user.setRole("ROLE_USER");
String rawPassword = user.getPassword();
String encPassword = bCryptPasswordEncoder.encode(rawPassword);
user.setPassword(encPassword);
userRepository.save(user);// 회원가입 잘됨. 비밀번호 : 1234 => 시큐리티로 로그인을 할 수 없음. 이유는 패스워드가 암호화가 안되어있기때문이다.
return "redirect:/loginForm";
}
...
}
SecurityConfig.java
@Configuration
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록이 됩니다.
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) //secured 어노테이션 활성화, preAuthorize 어노테이션 활성화
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 해당 메서드의 리턴되는 오브젝트를 IoC로 등록해준다.
@Bean
public BCryptPasswordEncoder encodePwd() {
return new BCryptPasswordEncoder();
}
...
}
2. api 별 권한 처리 (1)
http.authorizeRequests()
.antMatchers() 함수를 통해 특정 api url 설정
- .authenticated() → 시큐리티 인증을 완료한 모든 사용자 허용
- .access() → 인자에 hasRole() 메서드를 사용하여 해당하는 역할의 사용자에게만 권한을 허용
.anyRequest()를 통해 다른 모든 api url 설정
- .permitAll() → 모든 사용자에게 인증없이 허용
SecurityConfig.java
@Configuration
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록이 됩니다.
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) //secured 어노테이션 활성화, preAuthorize 어노테이션 활성화
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
@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("/loginForm")
.loginProcessingUrl("/login") // /login 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행해줍니다.
.defaultSuccessUrl("/");
}
}
3. api 별 권한 처리 (2)
SecurityConfig.java
@Configuration
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록이 됩니다.
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) //secured 어노테이션 활성화, preAuthorize 어노테이션 활성화
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
}
indexController.java
@Controller //View를 리턴하겠다!!
public class indexController {
@Autowired
private UserRepository userRepository;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
...
@Secured("ROLE_ADMIN")
@GetMapping("/info")
public @ResponseBody String info() {
return "개인정보";
}
@PreAuthorize("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
@GetMapping("/data")
public @ResponseBody String data() {
return "데이터정보";
}
}
@EnableGlobalMethodSecurity(securedEnabled = true)
: @Secured() 어노테이션을 통해 하나의 역할에 시큐리티 권한을 허용하는 기능을 사용할 수 있다.
@EnableGlobalMethodSecurity(prePostEnabled = true)
: @PreAuthorize() 어노테이션을 통해 복수개의 역할에 시큐리티 권한을 허용하는 기능을 사용할 수 있다.