ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링부트 시큐리티 02 - 시큐리티 기본(회원가입/로그인/권한처리)
    스프링/시큐리티 2021. 9. 7. 22:21

    유투버 '데어프로그래밍'님 강의 참조

     

    01 - 로그인 페이지 설정

     

    1) IndexController에 @Responsebody를 다 지운 후 템플릿 세팅

    	@GetMapping("/loginForm")
    	public String loginForm() {
    		return "loginForm";
    	}
    • loginForm.html
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Login</title>
    </head>
    <body>
    	<h1>Login Page</h1>
    	<hr>
    	<form>
    		<input type="text" name="username" placeholder="Username" /><br /> <input
    			type="password" name="password" placeholder="Password" /><br />
    		<button>Login</button>
    	</form>
    </body>
    </html>

    굿!

     

     

    02 - 로그인 진행을 위한 모델 세팅 및 DB테스트

     

    • 모델 세팅

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

     

     

    • 로그인 진행을 위한 회원가입 세팅
    	@GetMapping("/joinForm")
    	public String joinForm() {
    		return "joinForm";
    	}
    • loginForm.html 에 회원가입 링크 추가
    <a href="/joinForm">Not a member yet? Sign up!</a>

    • JoinForm 추가
    <body>
    <h1>Sign-Up Page</h1>
    <hr>
    <form action="/join" method="post">
    	<input type="text" name="username" placeholder="Username" /><br /> 
    	<input type="password" name="password" placeholder="Password" /><br />
    	<input type="email" name="email" placeholder="Email" /><br />
    	<button>Sign-up</button>
    </form>
    </body>
    • 3개의 값을 들고 컨트롤러에서 회원가입 처리
    	@PostMapping("/join")
    	public @ResponseBody String join(User user) {
    		System.out.println(user);
    		return "joined Successfully!";
    	}

    굿!

     

     

    • DB에 넣기위한 Repository 설정
    public interface UserRepository extends JpaRepository<User, Integer>{}

    → JpaRepository 가 기본 CRUD 기능을 들고 있고 상속으로 어노테이션 생략 가능

     

    • 패스워드 암호화를 꼭 해야만 시큐리티에서 넘어간다!
    public class SecurityConfig extends WebSecurityConfigurerAdapter{
    	
    	@Bean
    	public BCryptPasswordEncoder encodePwd() {
    		return new BCryptPasswordEncoder();
    	}
        ...

    → @Bean을 통해 메서드의 리턴되는 오브젝트를 IoC로 등록하게 됨

     

    • IndexController
    @Controller
    public class IndexController {
    	
    	@Autowired
    	private UserRepository userRepository;
    	
    	@Autowired
    	private BCryptPasswordEncoder bCryptPasswordEncoder;
        
        ..
        
    	@PostMapping("/join")
    	public @ResponseBody 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);
    		return "redirect:/loginForm";
    	}
    }

     

    → 테스트!

     

     

     

     

    • 로그인 해보기

    • SecurityConfig에 코드 추가해 시큐리티가 로그인을 낚아채서 진행하도록 설정
      즉 컨트롤러에 로그인 함수를 안만들어도 됨
    .loginProcessingUrl("/login")
    .defaultSuccessUrl("/");
    • loginForm에서 로그인 처리 되도록 POST형식 추가
    <form action="/login" method="post">
    • 시큐리티에서 로그인을 낚아채서 자기가 하도록 클래스를 따로 설정 → 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() {
    		return true;
    	}
    }
    • 시큐리티가 로그인 주소 요청이오면 낚아채는데 로그인 진행이 정상적으로 진행되면 시큐리티만의 세션이 생김
      (Security ContextHolder)
    • 이때 세션에 들어 갈 수 있는 오브젝트 정보는 'Authentication' 타입이어야 함
    • Authentication안에는 User정보가 있어야하는데 User정보를 들고있는 객체의 타입은 반드시 'UserDetails' 타입의 객체이어야 함. 그래서 Implements를 통해 PrincipalDetails(UserDetails)가 되는 것
    • 그래서 User 객체를 꺼낼때 PrincipalDetails에 접근해 가능 한 것. 
    • 나머지 코드는 스프링에서 정해논것이니 따라치기만 하면 되고, 해당 유저 권한 리턴시에는 컬렉션으로 만들어놨고 현재 권한은 String 타입이므로 타입을 맞추기위한 코드라고 보면 됨
    • 계정에 대한 유효성은 휴먼계정을 만든다던지 할때 False로 바꿔서 사용

     

    • 정리하면 Security Session에 정보를 저장할때 정보는 반드시 Authentication 객체여야하고 이 객체안에 User정보를 저장할때는 반드시 UserDetails 타입이어야 한다는 것!
      즉 현재 Authentication에 넣을 유저 객체를 만든 것

     

     

     

    • 이제는 Authentication 객체를 만들어서 세션에 넣을 수 있도록 만들어야 한다 → PrincipalDetailsSerivce
    @Service
    public class PrincipalDetailsSerivce implements UserDetailsService{
    
    	@Autowired
    	private UserRepository userRepository;
    	
    	@Override
    	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    		User userEntity = userRepository.findByUsername(username);
    		if(userEntity != null) {
    			return new PrincipalDetails(userEntity);
    		}
    		return null;
    	}
    }
    • 로그인 요청이 오면 스프링이 데이터로 들어온 username을 들고 UserDetailsService 타입으로 IoC되어있는 loadUserByUsername함수를 실행 함 → 이때 username이 파라미터로 들어감
    • 이후 레파지토리를 통해서 username 체크를 하게되고 정상이면 PrincipalDetails(UserDetails 타입)에 user정보를 넣어서 리턴하게 됨

     

    • 즉 지금까지 프로세스는 Authentication 객체를 만들어 사용하기 위한 것! 위의 두가지 프로레스를 통해 시큐리티 세션에 Authentication 객체가 들어가지게 되는 것

     

     

    → 테스트

    굿!

    • 이제 /user 라고 주소를 쳐서 들어가도 로그인 성공하면 잘 들어가 진다!

     

     

     

    • 시큐리티 권한 처리 해보기 (Manager/Admin)
    • 권한을 주기 위해 매니저/어드민 아이디 생성

    • 권한을 MySQL을 통해 각각 부여
    update user set role = 'ROLE_MANAGER' where id=2;
    update user set role = 'ROLE_ADMIN' where id=3;
    commit;

    굿!

    • SecurityConfig에 설정되있는데로 어드민은 모든 곳 가능, 메니저는 어드민 제외 가능하게 된다. 

     

    • 특정 페이지에 인증 및 권한을 걸고 싶을 때!
    @EnableGlobalMethodSecurity(securedEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter{
    	@Secured("ROLE_ADMIN")
    	@GetMapping("/info")
    	public @ResponseBody String info() {
    		return "Personal Information";
    	}

    → 로그인을 요구하게되지만 ROLE이 ADMIN이 아닌사람은 못들어가게 된다! 참 편리하다

     

     

    • 또 다른 시큐리티 기능
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    	@PreAuthorize("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
    	@GetMapping("/data")
    	public @ResponseBody String data() {
    		return "Data Information";
    	}

    → Secured과 같지만 여러개를 지정 할 수 있음. @PreAuthorize는 함수 실행 전에 실행 됨 (@PostAuthorieze는 잘 안씀)

     

     

Designed by Tistory.