1. loginForm 수정
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 페이지</title>
</head>
<body>
<h1>로그인 페이지</h1>
<hr>
<form action="/login" method="POST">
<input type="text" name="username" placeholder="Username"> <br>
<input type="password" name="password" placeholder="Password"> <br>
<button>로그인</button>
</form>
<a href="/joinForm">회원가입</a>
</body>
</html>
/login 주소로 로그인을 실행할 것이므로 action에 /login을 넣어준다. input text의 name을 꼭 username으로 해주어야 한다. (시큐리티 기본설정이기 때문. 바꿀 수 있음.)
2. SecurityConfig 수정
package com.pure.security.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity //스프링 시큐리티 필터 등록
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Bean
public BCryptPasswordEncoder encodePwd() {
return new BCryptPasswordEncoder();
};
@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") // "/login"주소가 올 때는 로그인 페이지 보여주기
.loginProcessingUrl("/login") //login 주소가 호출되면 시큐리티가 대신 로그인 진행
.defaultSuccessUrl("/"); //loginForm에서 로그인을 하면 인덱스로 가지만 특정페이지에서 로그인으로 넘어온 경우에는 로그인 후 이전페이지를 다시 보여줌.
}
}
.loginProcessingUrl("/login")을 추가했음. /login 주소 요청이 오면 시큐리티가 낚아채서 대신 로그인을 진행해 줌.
.defaultSuccessUrl("/")은 기존 방문 페이지가 없는 상태에서 로그인을 하면 인덱스로 가지만 user 등 기존 페이지에서 로그인을 진행하면 로그인폼 이전 페이지로 돌려주는 기능.
3. PrincipalDetails, PrincipalDetailsService 클래스 만들기
시큐리티가 로그인을 진행하는 과정.
Security ContextHolder 안에 Security Session이 있다.
Security Session 안에는 Authentication 타입만 들어가고 Authentication 안에 UserDetails 타입으로 User 정보가 들어간다.
그래서 로그인 했을 때의 유저 정보를 받기 위해 PrincipalDetails라는 클래스를 만들고 UserDetails를 구현해준다.
package com.pure.security.config.auth;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.pure.security.model.User;
// 시큐리티가 /login 주소 요청이 오면 로그인을 진행시킴.
// 로그인이 완료되면 Security session에 로그인 정보를 넣어줌. 이 정보는 Security ContextHolder에 들어감.
// Security는 Authentication 타입만 들어감.
// Authentication 안에 들어갈 User 정보는 UserDetails 타입 객체만 가능.
// Security Session 안에 Authentication 안에 UserDetils
public class PrincipalDetails implements UserDetails {
private User user;
public PrincipalDetails(User user) {
this.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;
}
@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;
}
}
유저 권한의 경우 우리는 User 오브젝트에 스트링 타입으로 저장했기 때문에 Collection 타입으로 바꿔줘야 getAuthorities()를 작동시킬 수 있다.
나머지는 그냥 그대로 리턴해주면 되고 아래쪽 불리언들은 Non이 들어가 있기 때문에 true로 바꿔줘야 됨.
PrincipalDetailsService.java
- 로그인 로직이다.
package com.pure.security.config.auth;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.pure.security.model.User;
import com.pure.security.repository.UserRepository;
//시큐리티 설정에서 loginProcessingUrl("/login");
// /login 요청이 오면 자동으로 UserDetialsService 타입으로 IoC되어 있는 loadUserByUsername 함수가 실행됨.
@Service
public class PrincipalDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 로그인 폼에서 넘어온 input의 name이 꼭 username이어야 함. (인자가 String username이기 때문)
// 만약이 이름을 다르게 하고 싶다면 securityConfig에서 .usernameParameter("")를 넣어서 이름을 바꿔주어야 함.
User userEntity = userRepository.findByUsername(username);
if(userEntity != null) {
return new PrincipalDetails(userEntity);
}
return null;
}
}
PrincipalDetailsService는 UserDetailsService를 구현해 준 클래스임.
DB에 로그인폼에서 받은 username이 있는지 찾아서 로그인 해준다.
(현재는 아이디가 틀렸거나 비번이 틀렸을 경우 주소에 ?error가 추가되고 별도로 오류를 알려주지 않는 상태임.)
userRepository.findByUsername()은 기본메서드가 아니라 직접 작성해야한다.
package com.pure.security.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.pure.security.model.User;
// @Repository 없어도 IoC됨. JpaRepostory를 상속받았기 때문
public interface UserRepository extends JpaRepository<User, Integer> {
//findBy 규칙
// select * from user where username=1?
public User findByUsername(String username); //JPA 쿼리 메서드
}
이렇게 하면 된다. JPA 쿼리 메서드라고 하는 findBy어쩌구 라는 문법이 있다. select * from where 어쩌구=1? 라는 쿼리문과 같은 뜻이다.
'취업 준비 > Spring security' 카테고리의 다른 글
6. OAuth2 구글 로그인 (0) | 2022.02.17 |
---|---|
5. 시큐리티 권한처리 (0) | 2022.02.16 |
3. 회원가입 만들기 (0) | 2022.02.10 |
2. 시큐리티 기본설정 (0) | 2022.02.10 |
1. 프로젝트 생성 및 설정 (0) | 2022.02.10 |