일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- springboot 게시판만들기
- spring jpa 사이드프로젝트
- 타입스크립트 기초
- 스프링부트 미니프로젝트
- typescript 기초문법
- 스프링부트 블로그만들기
- springboot 사이드프로젝트
- python 괄호 회전하기
- 프로그래머스 괄호 회전하기 python
- 타입스크립트 기초문법
- springboot 미니프로젝트
- 유니티기초
- 파이썬 괄호 회전하기
- 스프링 게시판 만들기
- 유니티Material
- 유니티Cube
- springboot 게시판
- spring jpa 게시판
- 스프링부트 회원가입
- jpa 게시판
- 타입스크립트 기본문법
- JS기초
- 유니티
- 스프링부트 게시판만들기
- springboot 게시판 프로젝트
- 파이썬 기초
- 스프링부트 update
- 스프링부트 블로그
- 괄호 회전하기 파이썬
- 스프링게시판프로젝트
- Today
- Total
Digking's cave
My First Blog Project (14) : SpringSecurity 로그인 구현 본문
My First Blog Project (14) : SpringSecurity 로그인 구현
디깅 2022. 12. 21. 14:09loginForm.java
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ include file="../layout/header.jsp" %>
<div class="container">
<form action="/auth/loginProc" method="post">
<div class="form-group">
<label for="username">Username:</label>
<input type="text" name="username" class="form-control" placeholder="Enter Username" id="username">
</div>
<div class="form-group">
<label for="pwd">Password:</label>
<input type="password" name="password" class="form-control" placeholder="Enter password" id="pwd">
</div>
<button id="btn-login" class="btn btn-primary">로그인</button>
</form>
</div>
<%@ include file="../layout/footer.jsp" %>
<script src="/js/user.js"></script>을 지움
user.js에서도 login부분 지움 (login ajax연결이 필요없음 이제)
SecurityConfig.java
package com.cos.blog.config;
import com.cos.blog.config.auth.PrincipalDetailService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration //빈등록 (IoC관리)
@EnableWebSecurity //security 필터 등록
@EnableGlobalMethodSecurity(prePostEnabled = true) //특정 주소를 접근을 하면 권한 및 인증을 미리 체크하겠다는 뜻
public class SecurityConfig {
private PrincipalDetailService principalDetailService;
@Bean
public BCryptPasswordEncoder encodePWD(){
return new BCryptPasswordEncoder();
}
//시큐리티가 대신 로그인해주는데 password를 가로채기 하는데,
//해당 password가 뭘로 해쉬가 되어 회원가입이 되었는지 알아야 같은 해쉬로 암호화해서 DB에 있는 해쉬랑 비교할 수 있음
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(principalDetailService).passwordEncoder(encodePWD());
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable() //csrf 토큰 비활성화(테스트 시에는 걸어두는게 좋다)
.authorizeRequests()
.antMatchers("/","/auth/**","/js/**","/css/**","/image/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/auth/loginForm")
.loginProcessingUrl("/auth/loginProc") //springSecurity 가 해당 주소로 요청오는 로그인을 가로채서 대신 로그인
.defaultSuccessUrl("/"); //정상적으로 로그인 되었을 때는 기본주소로 이동
return http.build();
}
}
.andMatchers() 에 설정된 주소를 제외하고 나머지 주소는 모두 loginPage로 이동한다.
loginPage에서 로그인을 시도하면 SrpingSecurity가 loginProceessingUrl로 요청오는 로그인을 가로채서 대신 로그인 한다.
정상적으로 로그인이 되었을 경우에는 defaultSuccessUrl에 설정된 기본주소로 이동한다.
+ 실패했을 때 이동할 페이지를 .failureUlr()로 설정할 수 있다.
config/auth 패키지 생성
auth/PrincipalDetail.java
package com.cos.blog.config.auth;
import com.cos.blog.model.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
// 스프링시큐리티가 로그인 요청을 가로채서 로그인을 진행하고 완료가 되면
// UserDetails 타입의 오브젝트를 스프링 시큐리티의 고유한 세션저장소에 저장해준다.
// (이때 PrincipalDetail 이 저장된다)
public class PrincipalDetail implements UserDetails {
private User user; //객체를 품고 있는 것을 콤포지션 이라고 한다.
public PrincipalDetail(User user){
this.user = user;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
//계정이 만료되지 않았는지 리턴한다 (true:만료안됨)
@Override
public boolean isAccountNonExpired() {
return true;
}
//계정이 잠겨있는지 확인 (true: 안잠김)
@Override
public boolean isAccountNonLocked() {
return true;
}
//비밀번호 만료 여부 (true:만료 안됨)
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//계정활성화 여부 (true:활성화)
@Override
public boolean isEnabled() {
return true;
}
//계정이 갖고있는 권한 목록을 리턴한다. (권한이 여러개 있을 수 있어서 루프를 돌아야 하는데 우리는 한개만)
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collectors = new ArrayList<>();
//화살표 함수로 바로 함수를 넣을 수 있다.
collectors.add(()->{return "ROLE_"+user.getRoles(); });
return collectors;
}
}
- 객체를 품고 있는 것을 콤포지션 이라고 한다.
- 스프링시큐리티가 로그인 요청을 가로채서 로그인을 진행하고 완료가 되면 UserDetails 타입의 오브젝트를 스프링 시큐리티의 고유한 세션저장소에 저장해준다. (이때 PrincipalDetail 이 저장된다)
//계정의 권한을 리턴
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collectors = new ArrayList<>();
//자바에서는 함수를 넣을 수 없기 때문에 object로 담아서 넣어준다.
collectors.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return "ROLE_"+user.getRoles();
// 앞에 "ROLE_" 붙이는게 스프링 role이다.
}
});
//화살표 함수로 바로 함수를 넣을 수 있다.
collectors.add(()->{return "ROLE_"+user.getRoles()});
return collectors;
}
- 자바에서는 함수를 넣을 수 없기 때문에 object로 담아서 넣어주는데, 더 편하게 화살표함수로 바로 함수를 넣어줄 수 있다.
auth/PrincipalDetailService.java
package com.cos.blog.config.auth;
import com.cos.blog.model.User;
import com.cos.blog.repository.UserRepository;
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;
@Service
public class PrincipalDetailService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
// 스프링이 로그인 요청을 가로챌 때, username과 password 변수 2개를 가로채는데 password는 알아서 처리함
// 우리는 username이 DB에 있는지만 확인해주면 된다.
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User principal = userRepository.findByUsername(username)
.orElseThrow(()->{
return new UsernameNotFoundException("해당 사용자를 찾을 수 없습니다.");
});
//바로 user타입의 principal을 return할 수 없고, PrincipalDetail을 새로 생성해서 return
return new PrincipalDetail(principal); // 이 때 시큐리티의 세션에 유저 정보가 저장된다
}
}
로그인요청이 오는 순간 SecurityConfig에 .loginProcessingUrl의 주소로 시큐리티가 가로채서
가로챈 username을 PrincipalDetailService에 loadUserByUsername으로 던짐
SecurityConfig에 configure에서 우리가 등록한 auth.userDetailsService을 통해서 principalDetailService 가 로그인 요청을 하고
loadUserByUsername에서 username을 비교해서 return할 때 passwordEncoder(encodePWD()) 를 통해서 password 암호화한 후 DB랑 비교한다.
비교랑 끝나서 name, password가 정상인걸 확인하면
PrincipalDetail(principal) 로 감싸서 다시 return
완료되고나면 "/" 기본주소로 이동
이 때는 이미 세션이 만들어진 후이다.
BoardController controller에서 세션을 어떻게 찾나요?
'Spring > My First Blog Project' 카테고리의 다른 글
My First Blog Project (16) : 게시판 글 목록 보기 (0) | 2022.12.26 |
---|---|
My First Blog Project (15) : 게시판 글쓰기 (0) | 2022.12.21 |
My First Blog Project (13) : 비밀번호 해쉬화 회원가입 / 권한에 따른 페이지 처리 (0) | 2022.12.20 |
My First Blog Project (12) : SpringSecurit 적용 및 로그인 페이지 생성 (0) | 2022.12.19 |
My First Blog Project (11) : 회원가입 / 로그인 처리 완료 (0) | 2022.12.18 |