기초/SPRING

[Spring] SpringBoot Security 폼 로그인 (react, nginx)

장동규 2022. 3. 27. 21:35

*표시는 배경지식

*인프런 강좌

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0/dashboard

[구성도]

더보기

nginx > 로그인 페이지  ( react:3000 

>>> 로그인 >>> 로그인처리 ( springboot:8081 )

>>> 성공,실패 리다이렉트 >>> nginx 주소

 

*Spring Security 참고자료

https://spring.io/guides/topicals/spring-security-architecture

https://godekdls.github.io/Spring%20Security/authentication/

https://jeong-pro.tistory.com/205

 

더보기

SecurityContextHolder :  security가 인증한 내용 + SecurityContext를 포함하고 있고 현재스레드와 securityCotnext를 연결

로그인이 완료되면 시큐리티 session을 만드는데 Security session은 SecurityContextHolder에 저장

Session Object는 Authentication Type으로 저장되며 Authentication 내부에서 사용자정보를 UserDetails 인터페이스로 저장

 

UserDetailsService 인터페이스는 DB와 연결해 User정보를 체크

AuthenticationProvider 인터페이스에서는 UserDetailsService 에서 리턴한 정보와 사용자가 입력한 정보를 비교

 

구현할 객체

WebSecurityConfigurerAdapter Extends Class : 스프링 시큐리티 적용

UserDetails Implements Class : 사용자의 정보

UserDetailsService Implements Class :  DB에서 사용자를 가져올 서비스

 

SecurityConfig Class

@Configuration
@EnableWebSecurity //스프링 시큐리티 필터가 스프링 필터체인에 등록
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) 
public class SecurityConfig extends WebSecurityConfigurerAdapter{

	@Bean
	public BCryptPasswordEncoder encodePassword() {
		return new BCryptPasswordEncoder();
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.csrf().disable();
		http
			.authorizeRequests()
				.antMatchers("/comment/commentSave").authenticated()
				.antMatchers("/user/**").authenticated()
				.antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
				.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
				.anyRequest().permitAll()
				.and()
			.formLogin()
				.defaultSuccessUrl("/")
				.failureUrl("/login?error")
				.loginPage("/login")
				.loginProcessingUrl("/dologin")
				.and()
			.logout()
				.logoutSuccessUrl("/")
		        .deleteCookies("JSESSIONID")
				.logoutUrl("/dologout");
	}
}

 

PrincipalDetails Class

public class PrincipalDetails implements UserDetails{
	private static final long serialVersionUID = 1L;
	
	private UserInfo userInfo; // composition
	
	//일반로그인
	public PrincipalDetails(UserInfo userInfo) {
		this.userInfo = userInfo;
	}
	
	//해당 User의 권한을 리턴하는 곳 
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		Collection<GrantedAuthority> collect = new ArrayList<>();
		collect.add(new GrantedAuthority() {
			
			@Override
			public String getAuthority() {
				return userInfo.getUserRole();
			}
		});
		return collect;
	}

	@Override
	public String getPassword() {
		return userInfo.getUserPassword();
	}

	@Override
	public String getUsername() {
		return userInfo.getInsUserName();
	}

	@Override
	public boolean isAccountNonExpired() {
		//계정이 만료되었는가?
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		//계정이 잠금상태인가?
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		//계정 비밀번호가 몇일이 지났나?
		return true;
	}

	@Override
	public boolean isEnabled() {
		//계정이 활성화 되있나??
		return true;
	}
}

 

PrincipalDetailsService Class

@Service
public class PrincipalDetailsService implements UserDetailsService{

	@Autowired
	private UserInfoRepository userInfoRepository;
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		if(username == null || username.equals("")) {
			throw new UsernameNotFoundException(username);
		}
		
		UserInfo userInfo = userInfoRepository.findByInsUserName(username);
		if(userInfo == null) {
			throw new UsernameNotFoundException(username);
		}
		return new PrincipalDetails(userInfo);
	}
}

 

*react form 전송방식

import axios from "axios";
import qs from 'qs';

const axiosConfig = {
    headers:{
        "Content-Type": "application/x-www-form-urlencoded"
    }
}
const axiosBody = {
  username:userNameValue,
  userpassword:userPasswordValue
}

axios
  .post("http://localhost/dologin",
  qs.stringify(axiosBody),
    axiosConfig)
  .then((response) => {
    console.log(response.data);
  })
  .catch((error) => {
    alert('에러가 발생했습니다.');
  });
};

json > form 전송타입으로 변경

Content-type 변경 : application/x-www-form-urlencoded

body 변환

qs사용 , qs.stringfy

 

 

*nginx.conf

location = / {
        proxy_pass http://127.0.0.1:3000/;
}
location = /login {
        proxy_pass http://127.0.0.1:3000/;
}
location = /registry {
        proxy_pass http://127.0.0.1:3000/;
}

location ~ .static/(js|css|media)/(.+)$ {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

location / {
    proxy_pass http://localhost:8081;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

react에서 페이지로 사용되는 location들은 절대경로로 지정

react build시 나오는 static / js,css,media파일들을 예외로 지정

나머지 경로를 Spring Boot 경로로 지정