Spring Security를 이용한 로그인 처리

뚜우웅이·2023년 2월 14일
0

SpringBoot웹

목록 보기
6/23

Security 의존성 추가

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-test</artifactId>
	<scope>test</scope>
</dependency>

pom.xml에 의존성을 추가해줍니다.

.authorizeHttpRequests

.requestMatchers("/", "/home").permitAll()
()안에 URL은 로그인 없이 누구나 접근이 가능합니다.

.anyRequest().authenticated()
나머지 요청은 로그인을 해야 확인이 가능합니다.

.formLogin((form) -> form
				.loginPage("/login")
				.permitAll()

permitAll 페이지 외 다른 페이지로 가게 되면 로그인 페이지로 이동하게 되고 로그인을 했을 경우 기존에 이동하려 했던 페이지로 이동하게 됩니다.
로그인되지 않은 사용자도 로그인 페이지에 접근할 수 있게 permitAll()을 해줍니다.

MariaDB와 연동해서 사용자 인증 처리

config 패키지를 만들어 준 뒤 WebSecurityConfig class를 생성해줍니다.

package com.project.myhome.config;

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.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();
    }
}

HeidiSQl을 열어서 사용자 테이블을 생성해줍니다.

https://www.baeldung.com/spring-security-jdbc-authentication
위의 사이트에 있는 Customizing the Search Queries 부분의 코드를 가지고 옵니다.

@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 = ?");
}

AuthenticationManagerBuilder 인스턴스를 가지고 Spring 내부에서 처리를 하게 됩니다.
위에 코드에서는 email,password,enabled 순서로 확인을 합니다.

BCryptPasswordEncoder
->Spring Security에서 제공하는 프레임워크로 비밀번호를 암호화 하는데 사용할 수 있는 메소드를 가진 클래스입니다.

위 코드를 WebSecurityConfig 파일에 넣어줍니다.

Authentication 로그인
Authroization 권한

권한 처리 테이블을 생성해줍니다.

id와 권한 이름 속성을 생성해줍니다.

권한 처리 테이블과 user 테이블을 JOIN 해줘야 하는데 연관관계 매핑에는 4가지 종류가 있습니다.
@OneToOne
ex) user - user_detail

@OneToMany
ex) user - Board

@ManyToOne
ex) Board - user

@ManyToMany
ex) user - role
하나의 사용자는 여러개의 권한을 가질 수 있고, 하나의 권한은 여러개의 사용자에의해 설정 될 수 있습니다.

user_role 테이블을 생성해줍니다.

WebSecurityConfig를 수정해줍니다.

package com.project.myhome.config;

import org.springframework.beans.factory.annotation.Autowired;
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.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

import javax.sql.DataSource;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests((requests) -> requests
                        .requestMatchers("/", "/css/**").permitAll()
                        .anyRequest().authenticated()
                )
                .formLogin((form) -> form
                        .loginPage("/account/login")
                        .permitAll()
                )
                .logout((logout) -> logout.permitAll());

        return http.build();
    }
    @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 PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

현재 이 코드로 실행을 하게 되면 순환참조 오류가 나게 됩니다. 이러한 경우에는 application.properties에 spring.main.allow-circular-references=true를 넣어주면 됩니다.

현재 위의 코드는 css서식을 가져오지 못하기 때문에 static 폴더 밑에 css 폴더를 생성하고 starter-template.css 파일을 폴더에 넣어준 뒤 WebSecurityConfig 클래스에서 .requestMatchers("/").permitAll() 이 부분을 .requestMatchers("/", "/css/**").permitAll()로 수정해줍니다.

css파일의 위치가 바뀌었기 때문에 다른 곳에서도 수정을 해줘야 합니다 common.html의 코드도 수정을 해줍니다.

th:href="@{/starter-template.css}"

위의 코드에서 아래 코드처럼 수정을 해줍니다.

th:href="@{/css/starter-template.css}"

현재 index.html이 아닌 board로 들어가려고 하면 url이 login으로 바뀌면서 오류 표시가 나옵니다.

board 페이지에 대해서는 permitAll()을 해주지 않았기 때문입니다.

로그인 페이지 생성

templates 폴더 아래에 account 폴더를 생성한 뒤 아래에 login.html 파일을 생성해줍니다.

bootstrap에 있는 Signin Template을 가져와서 사용해줍니다.

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
          integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
    <link th:href="@{/css/signin.css}" rel="stylesheet">
    <title>Login</title>
</head>
<body class="text-center">
<main class="form-signin w-100 m-auto">
    <form>
        <img class="mb-4" src="/docs/5.2/assets/brand/bootstrap-logo.svg" alt="" width="72" height="57">
        <h1 class="h3 mb-3 fw-normal">Please sign in</h1>

        <div class="form-floating">
            <input type="email" class="form-control" id="floatingInput" placeholder="name@example.com">
            <label for="floatingInput">Email address</label>
        </div>
        <div class="form-floating">
            <input type="password" class="form-control" id="floatingPassword" placeholder="Password">
            <label for="floatingPassword">Password</label>
        </div>

        <div class="checkbox mb-3">
            <label>
                <input type="checkbox" value="remember-me"> Remember me
            </label>
        </div>
        <button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button>
        <p class="mt-5 mb-3 text-muted">&copy; 2017–2022</p>
    </form>
</main>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"
        integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
        crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct"
        crossorigin="anonymous"></script>

</body>
</html>

css 폴더 밑에 signin.css 파일을 생성해줍니다.

html,
body {
  height: 100%;
}

body {
  display: flex;
  align-items: center;
  padding-top: 40px;
  padding-bottom: 40px;
  background-color: #f5f5f5;
}

.form-signin {
  max-width: 330px;
  padding: 15px;
}

.form-signin .form-floating:focus-within {
  z-index: 2;
}

.form-signin input[type="email"] {
  margin-bottom: -1px;
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 0;
}

.form-signin input[type="password"] {
  margin-bottom: 10px;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

WebSecurityConfig 클래스에서 loginPage 경로도 아래와 같이 수정해줍니다.

.loginPage("/account/login")

AccountController를 생성해줍니다.

package com.project.myhome.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.crypto.interfaces.PBEKey;

@Controller
@RequestMapping("/account")
public class AccountController {
    @GetMapping("/login")
    public String login(){
        return "account/login";
    }
}

현재 로그인 페이지에서는 이미지가 제대로 표시 되지 않습니다.

bootstrap에 있는 이미지를 우클릭 한 뒤 이미지 주소를 복사해서 login.html파일에 붙여넣어 줍니다. 그 후에 Remember me는 잠깐 주석처리를 하고 이메일이 아닌 username으로 변경을 해줍니다.

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
          integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
    <link th:href="@{/css/signin.css}" rel="stylesheet">
    <title>Login</title>
</head>
<body class="text-center">
<main class="form-signin w-100 m-auto">
    <form>
        <img class="mb-4" src="https://getbootstrap.com/docs/5.2/assets/brand/bootstrap-logo.svg" alt="" width="72" height="57">
        <h1 class="h3 mb-3 fw-normal">Please sign in</h1>

        <div class="form-floating">
            <input type="text" class="form-control" id="username" placeholder="Username">
            <label for="username">Username</label>
        </div>
        <div class="form-floating">
            <input type="password" class="form-control" id="floatingPassword" placeholder="Password">
            <label for="floatingPassword">Password</label>
        </div>

<!--        <div class="checkbox mb-3">-->
<!--            <label>-->
<!--                <input type="checkbox" value="remember-me"> Remember me-->
<!--            </label>-->
<!--        </div>-->
        <button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button>
        <p class="mt-5 mb-3 text-muted">&copy; 2017–2022</p>
    </form>
</main>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"
        integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
        crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct"
        crossorigin="anonymous"></script>

</body>
</html>

이제는 이미지까지 제대로 나오는 것을 확인할 수 있습니다.

에러 처리

spring.io에서 가져온 코드를 활용해줍니다.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
    <head>
        <title>Spring Security Example </title>
    </head>
    <body>
        <div th:if="${param.error}">
            Invalid username and password.
        </div>
        <div th:if="${param.logout}">
            You have been logged out.
        </div>
        <form th:action="@{/login}" method="post">
            <div><label> User Name : <input type="text" name="username"/> </label></div>
            <div><label> Password: <input type="password" name="password"/> </label></div>
            <div><input type="submit" value="Sign In"/></div>
        </form>
    </body>
</html>

에러가 발생하면 메시지를 띄워주는 것입니다.

login.html을 수정해줍니다.

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
          integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
    <link th:href="@{/css/signin.css}" rel="stylesheet">
    <title>Login</title>
</head>
<body class="text-center">
<div th:if="${param.error}">
    Invalid username and password.
</div>
<div th:if="${param.logout}">
    You have been logged out.
</div>
<main class="form-signin w-100 m-auto">

    <form th:action="@{/account/login}" method="post">
        <img class="mb-4" src="https://getbootstrap.com/docs/5.2/assets/brand/bootstrap-logo.svg" alt="" width="72" height="57">
        <h1 class="h3 mb-3 fw-normal">Please sign in</h1>

        <div class="form-floating">
            <input type="text" class="form-control" id="username" name="username" placeholder="Username">
            <label for="username">Username</label>
        </div>
        <div class="form-floating">
            <input type="password" class="form-control" id="floatingPassword" name="password" placeholder="Password">
            <label for="floatingPassword">Password</label>
        </div>

<!--        <div class="checkbox mb-3">-->
<!--            <label>-->
<!--                <input type="checkbox" value="remember-me"> Remember me-->
<!--            </label>-->
<!--        </div>-->
        <button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button>
        <p class="mt-5 mb-3 text-muted">&copy; 2017–2022</p>
    </form>
</main>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"
        integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
        crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct"
        crossorigin="anonymous"></script>

</body>
</html>

현재 이 상태에서 로그인을 하게 되면 에러처리가 되어 에러 메시지기 나오게 됩니다.

url도 http://localhost:8080/account/login?error 로 바뀌게 됩니다.
에러 메시지의 형태를 수정하기 위해 bootstrap에서 alert를 검색하여 코드를 가져와줍니다.

login.html 페이지에서 에러 부분의 서식을 수정해줍니다.

<form th:action="@{/account/login}" method="post">
        <img class="mb-4" src="https://getbootstrap.com/docs/5.2/assets/brand/bootstrap-logo.svg" alt="" width="72" height="57">
        <h1 class="h3 mb-3 fw-normal">Please sign in</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>
        <div class="form-floating">
            <input type="text" class="form-control" id="Username" name="Username" placeholder="Username">
            <label for="Username">Username</label>
        </div>
        <div class="form-floating">
            <input type="password" class="form-control" id="floatingPassword" name="password" placeholder="Password">
            <label for="floatingPassword">Password</label>
        </div>

<!--        <div class="checkbox mb-3">-->
<!--            <label>-->
<!--                <input type="checkbox" value="remember-me"> Remember me-->
<!--            </label>-->
<!--        </div>-->
        <button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button>
        <p class="mt-5 mb-3 text-muted">&copy; 2017–2022</p>
    </form>

회원가입 페이지 생성

account 폴더 밑에 login.html 페이지를 Ctrl C, Ctrl V를 이용하여 register.html의 기본 틀을 만들어주시고 수정을 해줍니다.

post 요청을 보낼 곳을 /account/register로 지정해줍니다.

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
          integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
    <link th:href="@{/css/signin.css}" rel="stylesheet">
    <title>Login</title>
</head>
<body class="text-center">

<main class="form-signin w-100 m-auto">

    <form th:action="@{/account/register}" method="post">
        <img class="mb-4" src="https://getbootstrap.com/docs/5.2/assets/brand/bootstrap-logo.svg" alt="" width="72" height="57">
        <h1 class="h3 mb-3 fw-normal">회원가입</h1>
        <div class="form-floating">
            <input type="text" class="form-control" id="username" name="username" placeholder="Username">
            <label for="username">Username</label>
        </div>
        <div class="form-floating">
            <input type="password" class="form-control" id="floatingPassword" name="password" placeholder="Password">
            <label for="floatingPassword">Password</label>
        </div>
        <button class="w-100 btn btn-lg btn-primary" type="submit">회원가입</button>
        <p class="mt-5 mb-3 text-muted">&copy; 2017–2022</p>
    </form>
</main>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"
        integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
        crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct"
        crossorigin="anonymous"></script>

</body>
</html>

model 폴더 아래에 User, Role class를 생성해줍니다.

User.java
List를 이용하여 role과 매핑을 시켜줍니다.
UserRepository를 이용해서 조회를 하게 되면 user에 해당하는 권한이 자동으로 조회돼서 roles에 담기게 됩니다.

https://www.baeldung.com/hibernate-many-to-many
위를 링크를 참고하여 @ManyToMany를 사용해줍니다.

package com.project.myhome.model;

import jakarta.persistence.*;
import lombok.Data;

import java.util.List;

@Entity
@Data
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String username;
    private String password;
    private Boolean enabled;

    @ManyToMany
    @JoinTable(
            name = "user_role",
            joinColumns = { @JoinColumn(name = "user_id") },
            inverseJoinColumns = { @JoinColumn(name = "role_id") }
    )
    private List<Role> roles = new ArrayList<>();;
}

cascade옵션은 엔터티의 상태 변화를 전파시키는 옵션입니다. 지금은 user를 저장했을 때 권한까지 저장하는 것이 바람직하지 않기 때문에 cascade 옵션을 주지 않았습니다.

Role.java

package com.project.myhome.model;

import jakarta.persistence.*;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;

@Entity
@Data
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String name;

    @ManyToMany(mappedBy = "roles")
    private List<User> users = new ArrayList<>();;
}

role 테이블에 샘플로 데이터를 입력해줍니다.

UserRepository를 생성해줍니다.

package com.project.myhome.repository;

import com.project.myhome.model.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long>{
}

AccountController에 register에 대한 PostMapping을 추가해줍니다.

사용자 저장을 위해 password 암호화와 권한도 추가해줘야 하기 때문에 service라는 패키지를 생성한 후 패키지 아래에 UserService라는 class를 생성해줍니다.

UserService.java
password를 encode를 통해 암호화 처리를 해주고 기본적으로 회원가입을 하면 기본적으로 enabled를 활성화 해줍니다.

user.getRoles().add(role);
->Roles에 어떤 권한을 줄지 추가할 수 있습니다.

package com.project.myhome.service;

import com.project.myhome.model.Role;
import com.project.myhome.model.User;
import com.project.myhome.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    public User save(User user){
        String encodedPassword = passwordEncoder.encode(user.getPassword());
        user.setPassword(encodedPassword);
        Role role = new Role();
        role.setId(1);
        user.setEnabled(String.valueOf(true));
        user.getRoles().add(role);
        return userRepository.save(user);
    }
}

하드 코딩이긴 하지만 role 테이블에 id값이 1번인 ROLE_USER 권한을 주는 것입니다.

AccountController.java
회원가입을 하면 정보를 가지고 index.html 페이지로 이동을 시켜줍니다.

@GetMapping("/register")
    public String register(){
        return "account/register";
    }

    @PostMapping("/register")
    public String register(User user){
        userService.save(user);
        return "redirect:/";
    }

WebSecurityConfig.java에서 회원가입 페이지도 permitAll로 바꿔줍니다.

.requestMatchers("/", "/account/register" , "/css/**").permitAll()


회원가입 페이지가 제대로 뜨는 것을 확인할 수 있습니다.

Username에 kkk를 주고 password로 1234를 입력 후 회원가입 버튼을 누르면 index.html 페이지로 이동하게 됩니다.

user 테이블을 확인하면 값이 제대로 들어간 것을 확인할 수 있습니다.

password는 passwordEncoder에 의해 인코딩 되어 값이 들어갑니다.
단방향 암호화이기 때문에 비밀번호를 복호화를 할 수는 없고 비밀번호가 맞는지만 확인 가능합니다.
enabled가 1이므로 활성화된 사용자라는 것도 알 수 있습니다.

user_role 테이블을 확인해보면 user 테이블와 id와 role 테이블의 id가 매핑된 것을 확인할 수 있습니다.


로그인을 하게 되면 게시판에 정상적으로 들어와지는 것을 알 수 있습니다.

로그인한 사용자, 로그인, 로그아웃 버튼 추가

상단 메뉴를 공통적으로 사용할 수 있게하는 파일인 common.html을 수정하여
로그인한 사용자 내용, 로그인 버튼, 로그아웃 버튼을 추가해줍니다.

bootstrap 공백 주는 법
m : margin
p : padding
t : top
b : bottom
l : left
r : right
x : x축
y : y축

thymeleaf 문법을 이용해 로그인에 따라서 처리를 해줍니다.

<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>

isAuthenticated() -> 로그인 된 사용자
hasRole('ROLE_ADMIN') -> ROLE_ADMIN 권한이 있는 사용자

특정 권한이 있는 사용자에게 특정 메시지를 보여주는 문법입니다.

위의 코드를 이용해 login 버튼은 로그인 되지 않은 사용자만 보이게 해주고 로그아웃 기능과 사용자 정보, 권한 정보가 있는 form 태그는 로그인 된 사용자만 보여주게 해줍니다.

이 문법을 사용하기 위해서는 의존성을 추가해줘야 합니다.

<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-springsecurity6</artifactId>
		</dependency>
<html xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

id에서 error로 인식할 수 있기 때문에 위의 코드를 선언해줍니다.

Logged user: <span sec:authentication="name">Bob</span>
Roles: <span sec:authentication="principal.authorities">[ROLE_USER, ROLE_ADMIN]</span>

->사용자 이름과 권한 리스트들을 확인할 수 있는 코드입니다.

th:action="@{/logout}" method="post"
SpringSecrity에서 지원하는 logout을 post 요청으로 보내서 로그아웃을 시켜주는 코드 입니다.

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head th:fragment="head(title)">
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
          integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
    <link href="starter-template.css" th:href="@{/css/starter-template.css}" rel="stylesheet">
    <title th:text="${title}">Hello, world!</title>
</head>
    <body>
    <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top" th:fragment="menu(menu)">
        <a class="navbar-brand" href="#">Spring Boot Tutorial</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault"
                aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>

        <div class="collapse navbar-collapse" id="navbarsExampleDefault">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item" th:classappend="${menu} == 'home' ? 'active'">
                    <a class="nav-link" href="#" th:href="@{/}">Home <span class="sr-only" th:if="${menu} == 'home'">(current)</span></a>
                </li>
                <li class="nav-item" th:classappend="${menu} == 'board' ? 'active'">
                    <a class="nav-link" href="#" th:href="@{/board/list}">게시판 <span class="sr-only" th:if="${menu} == 'board'">(current)</span></a>
                </li>
                <!--            <li class="nav-item">-->
                <!--                <a class="nav-link disabled">Disabled</a>-->
                <!--            </li>-->
                <!--            <li class="nav-item dropdown">-->
                <!--                <a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown" aria-expanded="false">Dropdown</a>-->
                <!--                <div class="dropdown-menu">-->
                <!--                    <a class="dropdown-item" href="#">Action</a>-->
                <!--                    <a class="dropdown-item" href="#">Another action</a>-->
                <!--                    <a class="dropdown-item" href="#">Something else here</a>-->
                <!--                </div>-->
                <!--            </li>-->
            </ul>
            <a class="btn btn-secondary my-2 my-sm-0"  th:href="@{/acount/login}"
               sec:authorize="!isAuthenticated()">Login</a>
            <form class="form-inline my-2 my-lg-0" th:action="@{/logout}" method="post"  sec:authorize="isAuthenticated()">
                <!--                    <input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search">-->
                <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">Logout</button>
            </form>
        </div>
    </nav>
    </body>
</html>


로그인이 되지 않은 상태에서는 로그인 버튼이 뜨게 되고


로그인을 한 경우에는 사용자 이름과 권한, 로그아웃 버튼이 나오게 됩니다.


로그아웃도 정상적으로 잘 작동하는 것을 알 수 있습니다.

login.html에 있는 코드인

<div th:if="${param.logout}" class="alert alert-primary" role="alert">
            You have been logged out.
        </div>

logout 파라미터가 같이 와서 로그아웃 됐다는 메시지가 표시 되는 것입니다.

회원가입 버튼 생성

common.html에 아래의 코드를 추가해줍니다.

<a class="btn btn-secondary my-2 my-sm-0"  th:href="@{/acount/register}"
               sec:authorize="!isAuthenticated()">회원가입</a>

로그인 버튼에는 margin right 값을 줍니다. (mr)

<a class="btn btn-secondary my-2 my-sm-0 mr-2"  th:href="@{/acount/login}"
               sec:authorize="!isAuthenticated()">로그인</a>

login.html과 register.html 페이지에서 홈 화면으로 가는 a태그를 추가해줍니다.

login.html

<a th:href="@{/}"><img class="mb-4" src="https://getbootstrap.com/docs/5.2/assets/brand/bootstrap-logo.svg" alt="" width="72" height="57"></a>

register.html

 <a th:href="@{/}"><img class="mb-4" src="https://getbootstrap.com/docs/5.2/assets/brand/bootstrap-logo.svg" alt="" width="72" height="57"></a>

이제 회원가입 페이지나 로그인 페이지에서 이미지를 클릭하게 되면 홈 화면으로 돌아가게 됩니다.

profile
공부하는 초보 개발자

0개의 댓글