Spring Security 사용하기 위해 다음의 의존성 추가가 필요하다. (gradle 기준)
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-test'
이후 WebSecurityConfigurerAdapter을 상속받은 WebSecurityConfig클래스를 만들어준다.
*@Configuration 을 선언한 클래스에선 @Bean을 관리할 수 있다.
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//application.properties의 dataSource가져옴
@Autowired
private DataSource dataSource;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/account/register", "/css/**").permitAll() //누구나 접근할수 있는 url
.anyRequest().authenticated() //어떤 요청이라도 authenticated 돼야한다.
.and()
.formLogin() //authenticated안된 상태에서 다른 페이지 가려하면
.loginPage("/account/login") //login페이지로 redirect된다.
.permitAll() //login 페이지는 누구라도 접근가능
.and()
.logout()
.permitAll();
}
}
테이블 ERD는 다음과 같다.
user 테이블에는 id, username, password, enabled(활성화여부)
role 테이블은 id, name(권한명)
user와 role은 many-to-many 관계이므로
user_role 테이블을 만들어서 관계형 테이블을 만든다.
user_id 와 role_id는 각각 pk이다.
외래키 설정을 통해 무결성을 보장하도록 한다.
JDBC Authentication 설정을 위해
https://www.baeldung.com/spring-security-jdbc-authentication 을 참고했다.
-PasswordEncoder: 추후 회원가입시 암호화시켜주는 bean
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//application.properties의 dataSource가져옴
@Autowired
private DataSource dataSource;
...
//테이블에 쿼리 날림
//스프링 datasource 가져와서 알아서 내부에서 인증처리
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.passwordEncoder(passwordEncoder()) //알아서 비번 암호화
.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 = ?");
}
@Bean
public static PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
- authoritiesByUsernameQuery: 권한정보 가져오는 쿼리, user_role에는 user와 role에 대한 정보가 없다. 따라서 user테이블과 role 테이블을 조인해서 정보를 가져오도록 한다.
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 = ?
==로그인 및 회원가입 컨트롤러 설정==
@Controller
@RequestMapping("/account")
public class AccountController {
@Autowired
private UserService userService;
@GetMapping("/login")
public String login(){
return "account/login";
}
@GetMapping("/register")
public String register(){
return "account/register";
}
@PostMapping("/register")
public String register(User user){ //modelattribute 생략해도 괜춘
userService.save(user);
return "redirect:/";
}
}
==로그인시 에러처리==
login.html
<form class="form-signin" th:action="@{/account/login}" method="post">
<h1 class="h3 mb-3 font-weight-normal"><a th:href="@{/}">Please sign in</a></h1>
<div th:if="${param.error}" class="alert alert-danger" role="alert">
Invalid username and password.
</div>
<div th:if="${param.logout}" class="alert alert-primary" role="alert">
You have been logged out.
</div>
<label for="username" class="sr-only">Email address</label>
<input type="text" id="username" name="username" class="form-control" placeholder="Username" required autofocus>
<label for="inputPassword" class="sr-only" name="password">Password</label>
<input type="password" id="inputPassword" name="password" class="form-control" placeholder="Password" required>
...
</form>
- form 태그 안에서 login으로 post 요청 보내는데, 만약 에러가 발생하면(파라미터에 error발생하면) 해당 에러 메시지가 나온다(로그아웃 시에도 동일)
==User, Role 모델을 만들어 many-to-many 관계설정-
참조: https://www.baeldung.com/jpa-many-to-many
@Entity //db연동을 위한 모델클래스임을 명시
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) //Autoincrement 설정, IDENTITY가 많이 사용
private Long id;
private String username;
private String password;
private Boolean enabled;
//user에 해당하는 권한이 알아서 조회돼서, roles에 담긴다.
@ManyToMany
@JoinTable(
name = "user_role",
joinColumns = @JoinColumn(name="user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private List<Role> roles = new ArrayList<>();
}
@Entity //db연동을 위한 모델클래스임을 명시
@Data
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) //Autoincrement 설정, IDENTITY가 많이 사용
private Long id;
private String name; //권한명
@ManyToMany(mappedBy = "roles") //mappedBy: User클래스에 있는 컬럼명이 된다.
private List<User> users;
}
==UserRepository 생성==
public interface UserRepository extends JpaRepository<User, Long> {
}
==UserService 생성==
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
//유저 저장
public User save(User user){
// 1.비밀번호 인코딩
String encodedPassword = passwordEncoder.encode(user.getPassword());
user.setPassword(encodedPassword);
// 2.회원 활성화 여부 - 기본적으로 enabled로 설정
user.setEnabled(true);
// 3. role정보 추가
Role role = new Role();
role.setId(1L);
user.getRoles().add(role);
return userRepository.save(user);
}
}
- DI를 통해 UserRepository 객체를 받아온다.
- WebSecurityConfig에 Bean으로 있는 PasswordEncoder를 DI로 가져와서 사용자가 전달한 비밀번호 인코딩한다.
== 로그인 여부에 따른 메뉴 구성 ==
https://www.thymeleaf.org/doc/articles/springsecurity.html참고
common.html
<a class="btn btn-secondary my-2 mr-2 my-sm-0" th:href="@{/account/login}"
sec:authorize="!isAuthenticated()">로그인</a>
<a class="btn btn-secondary my-2 my-sm-0" th:href="@{/account/register}"
sec:authorize="!isAuthenticated()">회원가입</a>
<form class="form-inline my-2 my-lg-0" th:action="@{/logout}" method="post" sec:authorize="isAuthenticated()">
<span class="text-white" sec:authentication="name">사용자</span>
<span class="text-white mx-2" sec:authentication="principal.authorities">권한</span>
<button class="btn btn-secondary my-2 my-sm-0" type="submit">로그아웃</button>
</form>
sec:authorize="!isAuthenticated() : isAuthenticated()은 로그인 되었을때니까 ! 붙여준다.
sec:authentication="name" : 로그인 한 사용자의 name 조회
sec:authentication="principal.authorities" :로그인 한 사용자의 권한 조회
이 기능을 추가하기 위해선 다음 의존성을 먼저 추가해주고,
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.0.4.RELEASE'
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
네임스페이스도 추가해준다.
'Study > SpringBoot' 카테고리의 다른 글
[Springboot] 회원가입 구현 (0) | 2022.05.16 |
---|---|
[SpringBoot] 사용자- 게시글 one-to-many, many-to-one 관계설정 (0) | 2022.05.12 |
[SpringBoot] 타임리프 if문 (0) | 2021.11.29 |
[SpringBoot] 문제해결: template might not exist or might not be accessible by any of the configured Template Resolvers (1) | 2021.11.18 |
[SpringBoot]@Mapper (0) | 2021.10.10 |