메뉴가 많아지면 로그인 한 사용자만 할 수 있도록 할 것임.
특정 권한.
사용자와 권한에 따라서 웹페이지 구성을 할 수 있음.
Spring을 사용하게 되면, Spring Security를 사용해서 인증처리를 할 수 있음.
기존에 생성할 때,
pom.xml에 추가가 됨
추가를 하지 않았음.
Secureing web
예제에서는 클릭하면 로그인 페이지가 뜸.
Sign Out 표시가 있음
메이븐 프로젝트로 구성했기 때문에
적당한 곳에 pom.xml안에 붙인다.
spring-boot-starter-security
spring-security-test
추가한 라이브러리를 적용하기 위해서는 상속을 받아서 http설정을 해줄 수 있다.
어노테이션 2개를 추가해주고 있다.
configuration을 설정해주면 Bean 설정이 가능하다.
Bean 설정된 클래스가 관리를 해주는 Bean이 됨.
객체를 사용할 수 있게 됨.
사용자 유저네임과 패스워드가 있다.
임시로 user/password로 설정된다.
USER라는 권한을 주고 있다.
임시 테스트를 위해 예제로 구성되어 있는 것이다.
DB를 사용하고 있기 때문에 DB에 사용자 테이블을 만들 것이다.
상속받은 클래스에서오버라이드를 하고 있다.
파라미터로 HttpSecurity를 전달 받음.
홈페이지에 어떤 보안 설정을 할 것인가 설정을 해주면 됨.
authorizeRequests()
antMatchers(URL).permitAll()
-해당 URL은 로그인 없이도 누구나 접근 가능함.
간편한 설정을 위해서 .을 사용하여 필요한 설정을 할 수 있음.
.anyRequest().authenticated()
antMatchers의 URL을 제외한 주소는 모두 인증된 유저만 접근할 수 있음.
.and()를 하면 authorizeRequests()의 설정이 끝난 것임.
.formLogin()
.loginPage("/login")
로그인 페이지를 설정해줌.
위에 설정한 URL외에 접속하면 자동으로 리다이렉트가 됨.
로그인하게되면 기존에 요구했던 주소로 돌아감.
.permitAll()
로그인 하지 않은 사용자이니 모두 접근 가능하게 함.
.logout()
누구나 로그아웃 할 수 있게 함.
package com.example.securingweb;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
)
.formLogin((form) -> form
.loginPage("/login")
.permitAll()
)
.logout((logout) -> logout.permitAll());
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
복사할 것임.
설정이므로 config패키지 생성.
WebSecurityConfig클래스 생성.
패키지에 맞게 수정.
임시유저 아이디 부분은 필요없으므로 제거
예제와 동일하게 처리해줌.
DB에서 사용하기 위해 사용자테이블을 만듦.
user라는 테이블을 생성
id 는 BIGINT 그리고 Primary Key로 지정
Spring에서는 사용자ID를 username으로 사용.
동일하게 username으로 컬럼 생성.
그리고 username은 unique 속성을 가져야 함.
비밀번호같은 경우는 스프링에서 제공해주는 password인코더를 이용해서 암호화를 할 것임.
짧게 입력을 해도 길어짐.
인증할 때 enabled가 있다.
활성화된 사용자인지 그 값을 같이 넘겨줘야 한다.
True False를 나타낼 수 있는 BIT.
전부 NULL 허용하지 않음.
id는 자동증가
그리고 나머지는 기본값 없음
테이블이 만들어짐
정리된 페이지를 찾기 힘드므로 구글링을 함.
JDBC Authentication
설정할 수 있는 샘플 예제
해당 사이트를 자주 보게 됨.
자신은 믿을 만한 사이트라고 생각함.
h2 - 임베디드 DB를 사용하는 방법이 처음에 나옴.
mysql을 사용함.
동일하게 참조해서 설정하면 됨.
커스터마이징 부분이 필요함.
해당 부분을 복사해옴
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select email,password,enabled "
+ "from bael_users "
+ "where email = ?")
.authoritiesByUsernameQuery("select email,authority "
+ "from authorities "
+ "where email = ?");
}
인스턴스를 가지고 스프링 내부에서 인증처리를 함.
datasource
2.2 JDBC Authentication 설정
DataSource를 Autowired 해줌
dataSource 같은 경우는 application 파일에 설정된 부분을 사용할 수 있게 인스턴스를 주입 해주는 것이다.
데이터를 넘겨주면 spring에서 알아서 데이터 소스를 사용해서 인증 처리를 하는 것이다.
필요한 정보를 전달해주면 되는 것이다.
패스워드 인코더도 있다.
Bean을 설정한다.
안전하게 암호화를 하도록 함.
스프링에서 제공해줌
passwordEncoder를 추가.
스프링에서 인증 처리를 할 때, 인코더를 이용을 해서 비밀번호 암호화를 알아서 하게 됨.
쿼리가 2개가 나와있음.
로그인을 해도 그 사용자의 권한을 처리할 수 있다.
용어에 대한 이해를 해야 한다.
위의 쿼리가 인증처리고,
두번째가 권한 처리이다.
DB에 맞게 조정할 것임.
예제에서는 email, password, enabled를 사용했음.
from user.
해당 순서대로 셀렉트를 해야 함.
String으로 쿼리를 작성할 때에는 여백이 중요함.
여백을 넣어야 문장이 붙지 않음.
?에는 알아서 username이 들어감.
두번째에는 권한 처리를 해야함.
role 테이블을 생성해볼 것임.
id하나 지정, BIGINT, 프라이머리 키, Auto Increment
name 권한 이름 지정, 기본값 없음.
사용자 table과 role 테이블이 연결되지 않았음.
사용자 table에는 사용자 정보만, role 테이블에는 권한 정보만 있음.
조인을 해줘야 함.
사용자와 권한의 관계가 N:N이 되어야 함.
JPA를 통해서 설정을 해야 함.
JPA에 Join이 들어가면 설정이 복잡해짐.
간단하게 맵핑할 수 있는 설정을 봐야 함.
board는 join이 없다. 그래서 간단하게 설정이 됨.
여러 테이블이 조인이 걸리면, 복잡하게 됨.
@OneToOne
사용자 상세 정보 등 설계
@OneToMany
사용자와 게시글
@ManyToOne
여러개의 보드와 사용자
@ManyToMany
사용자와 권한
사용자와 권한 테이블, 그리고 연결해주는 하나의 매핑 테이블.
user_role 테이블.
user_id는 프라이머리 key가 됨
role_id 롤테이블의 id에 맵핑이 됨. 이것도 프라이머리키 지정
외래키도 설정을 해주면, 그 테이블의 무결성을 유지하는 데 도움이 됨.
user_id가 참조할 테이블은 user의 id.
key이름도 설정이 됨.
role_id가 참조할 테이블은 role의 id.
user_role 테이블을 이용을 해서 join작성을 하면 됨.
다대다 설정을 통해서
사용자, 사용자 권한, 권한 테이블까지 3개의 테이블로 설정.
Join이 들어가면 데이터 무결성이 보장은 되겠지만, 성능에 불이익이 있다. 이를 트레이드오프라고 한다.
요즘 NoSQL을 사용하다보면 다소 중복되는 데이터가 들어가더라도 성능을 위해 join이 없는 데이터베이스를 사용하기도 함.
사용자 테이블 설계는 시스템에 맞게.
user_role 테이블 같은 경우는 user와 권한에 대한 정보가 없다.
조인을 해서 가져와야 한다.
user_role은 ur로 약어 지정.
user는 u로 약어 지정.
inner join을 이용해서, user_role의 role_id가 role의 id와 동일.
JDBC를 이용한 사용자 정보와 권한 정보를 가져오는 쿼리를 작성.
home이 없으므로 삭제하고 재시작해봄.
레이아웃이 깨짐.
스타일시트가 적용이 안되고 있음.
starter-template.css 가 보안때문에 못가져옴.
접근 권한이 없는 것임.
추가를 해줌.
이제는 가져올 수 있음.
귀찮으니 css 폴더를 만들고 /css/** 를 할 것임.
폴더 안으로 옮겼음
경로도 수정해줘야 함.
경로가 board가 아니라 login으로 바뀜.
permitAll이 아니기 때문에 login으로 접속됨.
로그인 설정을 해줘야 함.
로그인 페이지를 부트스트랩에서 가져올 예정임.
이 페이지를 가져와볼 것임.
로그인 페이지로 지정을 했는데, 로그인 말고도 사용자 정보 수정 등, 어카운트 경로로 수정해줄 것임.
account/login.html 생성
index 파일을 붙여넣음.
title Login으로 수정
head도 직접 설정할 것임.
페이지 소스 보기
바디 안에 폼 테그 하나 있음.
form 태그 붙임.
가운데 정렬을 위해서 부스트랩에서 제공하는 text-center 클래스가 바디에 들어가 있음.
text-center를 붙임.
signin.css 눌러서 염.
해당 내용 복사
붙여넣기.
thymeleaf 문법을 이용해서 경로 지정.
참고 되도록 지정함.
경로 수정함.
경로가 설정됐는데 에러가 뜸.
AccountController 생성함.
login에 GetMapping.
로그인 페이지로 이동해라 라는 설정.
원래 상단에 이미지가 있었는데 뜨고 있지 않고 있다.
주소를 직접 붙여 넣어 볼 것임.
이제 이미지가 뜬다.
그러나 로그인을 이메일로 하지 않음.
username으로 변경
text로 변경
Remember me는 보이지 않도록 함.
화면이 잘 나옴.
로그인을 할 수 있는 사용자가 없음.
에러 처리부터 하도록 함.
에러가 나게 되면 메시지를 처리하는 부분이 있음.
form태그 안에서 로그인으로 보냄.
알아서 스프링에서 로그인이 일어나게 됨.
복사해서 테스트.
폼 태그 위에 에러 메시지 붙여 넣음
submit 버튼 눌렀을 때 어디로 전송이 되어야 하는지 설정을 하고 있음.
action은 반드시 th 네임 스페이스를 붙여야 함.
spring security를 이용해서 폼 요청으로 전송을 하게 되면 csrf정보가 같이 담겨서 전송이 됨.
이런 식으로 작성하면 로그인 요청은 되지만, csrf토큰이 같이 전송이 되지 않음.
action을 지정 해줘야 함.
/account/login으로 포스트요청을 보내면 됨.
name도 같이 지정을 해줘야 함.
password도 이름 지정
에러메시지 발생
파라미터로 에러가 와서 다시 한번 페이지가 보임.
th문법으로 에러인 경우는 메시지를 표시해주라고 함.
모양이 이쁘게 안나오고 있음.
부트스트랩을 통해서 수정을 해보도록 함.
alert기능으로 에러메시지를 출력할 수 있음.
danger를 role까지 복사.
logout은 심플로.
class를 추가해줌
메시지는 바꼈는데 위치가 바껴야 함.
알트 밑 버튼을 누르면 이동함.
Please sign in 아래에 표시.
이런 식으로 메시지가 뜸.
회원가입을 만들어보도록 함.
사용자 생성을 함.
그 후에 로그인할 수 있도록.
login파일을 복사해서 register로 이름 변경.
회원가입으로 바꾸고 에러 경고창을 지움.
post보낼 곳을 /account/register로 수정.
register를 포스트맵핑으로 만들기 전에 모델을 만들어야 함.
3개 테이블을 연결해줄 수 있는 모델 클래스를 생성할 것임.
사용자 테이블부터 만들어볼 것임.
어노테이션은 Board에서 복사
id까지 복사
username
password
enabled
user클래스 모델
Role 모델
Many to Many 매핑을 해볼 것이다.
JPA로 설정이 가능하다.
List를 이용하는게 편함.
권한과 맵핑을 할 것임.
멤버 변수를 하나 설정 했음.
User에 해당하는 권한이 알아서 조회가 되서 roles에 담기게 됨.
해당 페이지 참고.
예제는 Student와 Course로 되어있음.
course_like를 이용.
각각 PK와 FK를 지정.
user와 role 테이블을 생각하라.
@ManyToMany 설정을 해주면 됨.
https://www.baeldung.com/jpa-many-to-many
Set은 중복없도록 함.
@ManyToMany어노테이션 이외에도 연결테이블, user_role에 대한 설정을 같이 해주고 있음.
복사해서 붙여 넣을 것임.
@ManyToMany
@JoinTable(
name = "course_like",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id"))
join테이블은 user_role
조인될 상대 테이블은 inverseJoinColumns
Role에서도 role 레파지토리 이용을 해야 한다.
MappedBy는 user컬럼 이름.
해당 설정을 양방향 맵핑이라고 한다.
조회하면 상대방 조인된 테이블까지 같이 조회를 해올 수 있도록.
cascade라는 옵션이 있다.
user레파지토리에서 저장을 하게 되면, roles도 같이 반영이 되게 하는 방식을 지정할 수 있음.
사용자를 저장했을 때, 권한 까지는 바람직해보이지 않음. 주지는 않음.
샘플로 role을 주도록 하겠다.
Spring Security에서는 권한 설정할 때, ROLE_를 기본적으로 사용한다.
권한을 하나 생성함.
모델 클래스를 생성했으니 레파지토리도 생성.
User 클래스를 받아서 저장해주면 됨.
기존에는 파라미터를 받을 때, 모델 attribute 어노테이션 설정을 했다.
필수는 아니기 때문에 생략해서 설정함.
사용자 저장을 할 때, 암호화도 해야 하고, 사용자 권한도 추가 해줘야 함.
비즈니스 로직이 들어가기 때문에 서비스 클래스를 하나 만들어서 거기에서 처리를 해주도록 함.
@Service 어노테이션을 작성하면 비즈니스 로직을 작성할 수 있게 됨.
해당 클래스를 이용해서 유닛 테스트 작성하기도 용이함.
실제로 서비스를 하다보면 서비스를 인터페이스로 빼기도 하고, 2개로 작성하는 것을 보기도 함.
굳이 2개로 빼진 않을 것이다.
Service를 import 하게 되면, 스프링이 인스턴스를 넣어줌.
save를 하게 할 것임.
메소드 작성이 필요함.
생성해줬던 User Repository를 임포트.
@Autowired를 해줌.
스프링의 디펜던시 인젝션.
에러가 발생했는데 Board로 설정이 되어 있음.
인텔리제이에서 필드인젝션을 추천하지 않아 기존 방식으로 진행함. @RequiredArgsConstructor private UserRepository userRepository; |
User를 수정.
user를 그냥 저장 하면 안된다.
그냥 저장하면 사용자 이름과 패스워드만 저장된다.
비밀번호도 암호화해야한다.
enabled 값도 설정 해줘야 한다.
해당 권한도 user_role 테이블에 넣어줘야 함.
해당 처리를 user service에서 하도록 함.
스프링에서 제공해주는 PasswardEncoder bean을 작성해놨음.
인코더를 이용해서, Passward Encoder를 가져올 것임.
PasswordEncoder를 서비스에서 불러옴.
사용자가 전달한 비밀번호를 인코드를 이용하면 암호화가 됨.
user.setPassword를 통해 패스워드 설정.
기본적으로 회원 가입을 하면 Enabled를 true.
활성화된 상태로 변경.
user테이블의 roles를 셋팅.
기본적으로 ArrayList.
가끔 불필요하게 Null Exception이 자주 발생함.
그래서 arraylist 생성을 해서 채워주도록 함.
role에 어떤 권한을 줄건지 저장을 할 수 있음.
roles에 값을 넣어서 저장을 하게 되면, user_role 테이블에 알아서 저장이 됨.
Role을 하나 넣어야 하는데, ROLE_USER를 검색해서 가져오면 좋겠지만, 레파지토리를 또 만들어야 한다.
간편하게 하기 위해서 id를 하드코딩으로 지정한다.
user_role 테이블에 같이 저장이 될 것임.
Get Mapping 도 생성.
로그인이 완료가 됐으면 Home으로 이동하면 됨.
"redirect:/"
경로를 붙여주게 되면, 안에 있는 로직을 같이 실행이 된 후에, index로 감.
홈에서 필요한 모든 정보가 같이 세팅이 되서 홈으로 이동함.
모델을 지정해서 파라미터 지정해서 user클래스를 넘겨줄 수도 있겠지만,
User 자체에 username과 password가 있다.
파라미터에 맞게 form으로 전송을 해주게 됨.
자세한 벨리데이션은 굳이 지금은 추가하지 않는다.
form에 있는 파라미터 이용.
account/register로 이동했는데, 로그인 페이지로 이동함.
추가적으로 permitAll안에 회원가입 페이지도 추가.
가입 시도
회원 가입 완료.
id 자동 생성이 됐음.
username과 비밀번호.
password encoder를 통해 들어갔음.
해당 비밀번호는 복호화가 되지 않는다.
사용자가 입력한 비밀번호에 맞게 동일한 비밀번호인지만 확인 가능함.
단방향 암호화임.
enabled 1로 활성화된 사용자임을 확인함.
사용자 id와 role id.
매핑 정보도 자동으로 저장이 됨.
로그인 정보를 바탕으로 로그인을 해볼 것임.
로그인이 안됨.
email이 잘못됨.
email은 username으로 변경.
그리고 u.username으로.
왜냐하면 user테이블을 u로 지정.
r.name
정확하게 지정을 해줘야 함.
postgreSQL에서는 user가 예약어이므로 꼼수를 써야 한다. User.java @Table(name = "\"User\"") WebSecurityConfig.java @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.jdbcAuthentication() .dataSource(dataSource) .usersByUsernameQuery("select username, password, enabled " + "from \"user\" " + "where username = ?") .authoritiesByUsernameQuery("select u.username, r.name " + "from user_role ur " + "inner join \"user\" u on ur.user_id = u.id " + "inner join role r on ur.role_id = r.id " + "where u.username = ?"); } |
게시판이 보임.
home, /account/register, /css를 제외하고는 로그인 필요.
홈과 게시판을 자유롭게 왔다갔다 할 수 있음.
오른쪽 상단에 로그인/로그아웃/사용자 정보 등이 표현되면 좋을 것 같음.
해당 정보를 표시해주도록 함.
fragments/common.html 파일이 상단메뉴를 공통적으로 사용하는 파일.
해당 파일을 만들 때, Search 버튼이 있었다. 해당 form태그를 사용하도록 함.
반응형임.
해당 부분을 회원 정보나 로그아웃/로그인 버튼으로 만들어보도록 함.
input 창은 필요없음.
해당 방식으로 만들어볼 것임.
앞부분에는 로그인한 사용자의 정보를 표시해주도록 함.
로그인을 했으면, 현재 사용자에 대한 정보가 표시되도록 작성.
로그인은 앵커태그로 작성해볼 것임.
class가 버튼으로 들어갔기 때문에 앵커태그가 들어가도 버튼으로 표현.
링크 주소도 삽입.
글씨가 검정색이라 잘 보이지 않고, 여백도 없다.
bootstrap에서 text-white 클래스를 주게 되면, 글씨가 흰색으로 바뀜.
또 하나 제공해주는 클래스는 마진이다.
my-2
y- 위아래
x-좌우
글씨가 흰색으로 바꼈다.
로그인과 로그아웃 버튼이 같이 있어서 이상하다.
로그인에 따라서 로그인 버튼이 보일지,
사용자와 권한 그리고 Logout 버튼이 보일지 분기처리를 해줘야 함.
Thymeleaf + Spring Security basics 확인.
sec 예제가 있음.
authenticated는 로그인 된 사용자라는 뜻.
hasRole - 권한이 있는 사용자.
특정 권한이 있는 사용자한테는 특정 메시지 표시가 가능.
Spring Security integration module이 있다.
https://github.com/thymeleaf/thymeleaf-extras-springsecurity
추가를 해줘야 함.
<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity6 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
스프링부트를 사용하기 때문에 버전 생략이 가능.
Namespace를 선언하는 양식 예제가 나와 있다.
사용하지 않아도 상관없음.
그러나 에러로 인식할 수 있으므로, 선언해줌.
로그인 되지 않은 사용자만 볼 수 있게 isAuthenticated에 !첨가.
<div sec:authorize="isAuthenticated()">
This content is only shown to authenticated users.
</div>
<div sec:authorize="hasRole('ROLE_ADMIN')">
This content is only shown to administrators.
</div>
<div sec:authorize="hasRole('ROLE_USER')">
This content is only shown to users.
</div>
로그아웃쪽은 로그인된 사용자만 보이도록 추가.
사용자 이름과 권한을 그대로 보여줄 수 있음.
로그인 버튼만 보임.
로그인 하면 로그인버튼 사라짐.
이름과 권한이 나옴.
그리고 로그아웃 버튼.
그러나 지금 로그아웃을 지정해주지 않았다.
기본적으로 스프링 시큐리티에서 /logout 액션, 메소드는 post로 보내면 로그아웃이 됨.
로그아웃이 됐음.
자동으로 로그인 페이지로 옴.
로그아웃 파라미터도 같이 옴.
처음에 설정했었음.
로그아웃이 됐으므로, 로그인 버튼이 다시 나왔음.
회원가입도 추가해보겠다.
이름도 한글로 변경
please sign in에 home 앵커를 달아보겠다.
이미지에 달도록 하자.
register에도 동일하게 달도록 한다.
로그인과 회원 가입이 보임.
mr-2 오른쪽으로 여백
회원가입 버튼을 누르면 회원가입이 가능.
아이콘을 누르면 홈으로 이동.
로그인 정보가 나옴.
11. JPA로 조회방법(FetchType) 설정하기 (0) | 2023.12.16 |
---|---|
10. JPA를 이용하여 @OneToMany 관계 설정하기 (1) | 2023.12.15 |
Spring Boot으로 웹 출시까지 8. JPA를 이용한 페이지 처리 및 검색 (1) | 2023.12.08 |
Spring Boot으로 웹 출시까지 7. JPA이용한 RESTful API 작성 (0) | 2023.12.04 |
Spring Boot으로 웹 출시까지 #6. thymleaf에서 form 전송하기 (0) | 2023.11.06 |
댓글 영역