[イニシアチブ-SpringBoot]掲示板-3を作成します.Spring Securityの適用
99315 ワード
Spring Securityとは?
Spring securityは、認証と認可を重点的に提供するカスタマイズ可能な認証とアクセス制御フレームワークです.
Springベースのアプリケーションを保護するための事実上の基準です.
Spring安全特性
包括的で拡張性の高い
2.依存性の追加
file : build.gradleplugins {
id 'org.springframework.boot' version '2.5.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.rptp'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
//==================add==================
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
implementation 'org.springframework.security:spring-security-test'
// ==================add==================
annotationProcessor "org.projectlombok:lombok"
compileOnly "org.projectlombok:lombok"
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testCompileOnly 'org.projectlombok:lombok:1.18.20'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.20'
runtimeOnly 'mysql:mysql-connector-java'
}
test {
useJUnitPlatform()
}
3.セキュリティ構成の作成 package com.rptp.rptpSpringBoot.common.security;
import com.rptp.rptpSpringBoot.core.member.service.MemberService;
import lombok.RequiredArgsConstructor;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final MemberService memberService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/**").permitAll()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/login-success")
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/logout-success")
.invalidateHttpSession(true)
.and()
.exceptionHandling().accessDeniedPage("/denied");
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(memberService).passwordEncoder(passwordEncoder());
}
}
コード詳細
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
復号クラスに注入されます.
CryptPasswordEncoderは、Cryptハッシュ関数を使用してパスワードをエンコードする方法と、ユーザーがコミットしたパスワードがリポジトリに格納されているパスワードと一致するかどうかを決定する方法を提供します.
ハッシュの強度は、コンストラクション関数のパラメータ値(Version、Stress、SecureRandom instance)によって調整できます. @Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**");
}
Webセキュリティは、FilterChainProxyを生成するフィルタです.
antMatchersで指定したアドレスのファイルは認証を無視します.@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// (1)
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/**").permitAll()
// (2)
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/login-success")
.permitAll()
// (3)
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/logout-success")
.invalidateHttpSession(true)
// (4)
.and()
.exceptionHandling().accessDeniedPage("/denied");
}
HttpSecurityにより、HTTPリクエストに対してWebベースのセキュリティを構成することができる.
(1). antMatchers()メソッドを使用して特定のパスを指定し、permitAll()メソッドとhasRole()メソッドを使用してロールに基づいてアクセス設定を設定します.
(2). フォームベースの認証はformLogin()で行います.ログイン情報は基本的にHttpSessionを使用します.
(3). ログアウトの方法をサポートします.WebSecurityコンフィギュレータアダプタを使用すると自動的に有効になります.
デフォルトでは、/logoutにアクセスするとHTTPセッションが削除されます.
(4). 処理403異常処理のハンドル.
4.メンバーエンティティロールの追加
4-1. Role enumの作成
file : Role.javapackage com.rptp.rptpSpringBoot.core.member.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum Role {
ADMIN("ROLE_ADMIN"),
MEMBER("ROLE_MEMBER"),
GUEST("ROLE_GUEST");
private String value;
}
4-2. メンバーの変更
file : Member.javapackage com.rptp.rptpSpringBoot.core.member.domain;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberId;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String password;
private String profilePhoto;
@Column(nullable = false)
private String nickName;
//==================add==================
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private Role role = Role.GUEST;
//==================add==================
@Builder
public Member(String name, String password, String profilePhoto, String nickName) {
this.name = name;
this.password = password;
this.profilePhoto = profilePhoto;
this.nickName = nickName;
}
}
5.MemberRepository findByNameメソッドの追加
file : MemberRepository.javapackage com.rptp.rptpSpringBoot.core.member.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface MemberRepository extends JpaRepository<Member, Long> {
//==================add==================
Optional<Member> findByName(String name);
//==================add==================
}
6.メンバーサービスでのUserDetailServiceインタフェースの実装
file : MemberService.javapackage com.rptp.rptpSpringBoot.core.member.service;
import com.rptp.rptpSpringBoot.core.member.domain.Member;
import com.rptp.rptpSpringBoot.core.member.domain.MemberRepository;
import com.rptp.rptpSpringBoot.core.member.domain.Role;
import com.rptp.rptpSpringBoot.core.member.dto.SignUpRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Service
@RequiredArgsConstructor
//==================edit==================
public class MemberService implements UserDetailsService {
//==================edit==================
private final MemberRepository memberRepository;
private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Transactional
public Long signUp(SignUpRequest req) {
return memberRepository.save(buildMember(req)).getMemberId();
}
private Member buildMember(SignUpRequest req) {
return Member.builder()
.name(req.getName())
//==================edit==================
.password(passwordEncoder.encode(req.getPassword()))
//==================edit==================
.profilePhoto(req.getProfilePhoto())
.nickName(req.getNickName())
.build();
}
//==================add==================
@Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
Member member = memberRepository.findByName(name)
.orElseThrow(() -> new UsernameNotFoundException(name + "은 존재하지 않습니다"));
List<GrantedAuthority> authorities = new ArrayList<>();
if (member.getRole() == Role.ADMIN) {
authorities.add(new SimpleGrantedAuthority(Role.ADMIN.getValue()));
}
authorities.add(new SimpleGrantedAuthority(Role.MEMBER.getValue()));
return new User(member.getName(), member.getPassword(), authorities);
}
//==================add==================
}
7.ビューの追加&ハンドルの変更と追加
admin.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>어드민</title>
</head>
<body>
<h1>어드민 페이지입니다.</h1>
<hr>
</body>
</html>
demied.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>접근 거부</title>
</head>
<body>
<h1>접근 불가 페이지입니다.</h1>
<hr>
</body>
</html>
index.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
<h1>메인 페이지</h1>
<hr>
<a sec:authorize="isAnonymous()" th:href="@{/login}">로그인</a>
<a sec:authorize="isAuthenticated()" th:href="@{/logout}">로그아웃</a>
<a sec:authorize="isAnonymous()" th:href="@{/sign-up}">회원가입</a>
<a sec:authorize="hasRole('ROLE_MEMBER')" th:href="@{/user}">내정보</a>
<a sec:authorize="hasRole('ROLE_ADMIN')" th:href="@{/admin}">어드민</a>
</body>
</html>
sec:authorizerでログインユーザーの状態に応じてラベルを表示するかどうかを決定します.
login.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
유효하지 않은 아이디 또는 비밀번호입니다
</div>
<div th:if="${param.logout}">
로그아웃
</div>
<form th:action="@{/login}" method="post">
<!--input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /-->
<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="로그인"/></div>
</form>
</body>
</html>
Spring Securityが適用される場合、POST方式で送信されるすべてのデータはcsrfトークン値を必要とする.
したがって本来はフォーム上でcsrfトークンを一緒に送信すべきであるが、time lifeのth:actionを使用すると自動的にcsrfが送信される
login-success.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
로그인 완료!
<a th:href="@{/}">메인으로</a>
</body>
</html>
logout.html<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>로그아웃</title>
</head>
<body>
<h1>로그아웃 처리되었습니다.</h1>
<hr>
<a th:href="@{'/'}">메인으로 이동</a>
</body>
</html>
logout-success.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
로그아웃 완료!
<a th:href="@{/}">메인으로</a>
</body>
</html>
sign-up.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
<form th:action="@{/api/member}" method="post">
<label>아이디<input type="text" name="name"></label>
<label>password<input type="password" name="password"></label>
<label>프로필사진<input type="text" name="profilePhoto"></label>
<label>별명<input type="text" name="nickName"></label>
<input type="submit" value="가입하기">
</form>
</body>
</html>
sign-up-success.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
회원가입 완료!
<a th:href="@{/}">메인으로</a>
</body>
</html>
MainController.javapackage com.rptp.rptpSpringBoot.api;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/")
@RequiredArgsConstructor
public class MainController {
@GetMapping("")
public String index(){
return "/index";
}
@GetMapping("sign-up")
public String signUp() {
return "/sign-up";
}
@GetMapping("sign-up-success")
public String signUpSuccess() {
return "/sign-up-success";
}
@GetMapping("login")
public String login() {
return "/login";
}
@GetMapping("login-success")
public String loginSuccess() {
return "/login-success";
}
@GetMapping("logout-success")
public String logoutSuccess() {
return "/logout-success";
}
@GetMapping("denied")
public String denied() {
return "/denied";
}
@GetMapping("/admin")
public String admin() {
return "/admin";
}
}
実行結果
初回接続時のホームページ
会員収入
会員加入完了
ログイン
ログイン完了
一般ユーザーのホームページ
ログアウトページ
オペレータホーム-ROLEをdbからADMINに直接変更
リファレンス
https://mangkyu.tistory.com/76
https://victorydntmd.tistory.com/328
https://kimvampa.tistory.com/129
Reference
この問題について([イニシアチブ-SpringBoot]掲示板-3を作成します.Spring Securityの適用), 我々は、より多くの情報をここで見つけました
https://velog.io/@phjppo0918/初心-Spring-Boot-3.-Spring-Security-적용
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
plugins {
id 'org.springframework.boot' version '2.5.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.rptp'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
//==================add==================
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
implementation 'org.springframework.security:spring-security-test'
// ==================add==================
annotationProcessor "org.projectlombok:lombok"
compileOnly "org.projectlombok:lombok"
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testCompileOnly 'org.projectlombok:lombok:1.18.20'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.20'
runtimeOnly 'mysql:mysql-connector-java'
}
test {
useJUnitPlatform()
}
package com.rptp.rptpSpringBoot.common.security;
import com.rptp.rptpSpringBoot.core.member.service.MemberService;
import lombok.RequiredArgsConstructor;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final MemberService memberService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/**").permitAll()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/login-success")
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/logout-success")
.invalidateHttpSession(true)
.and()
.exceptionHandling().accessDeniedPage("/denied");
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(memberService).passwordEncoder(passwordEncoder());
}
}
コード詳細
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
復号クラスに注入されます.CryptPasswordEncoderは、Cryptハッシュ関数を使用してパスワードをエンコードする方法と、ユーザーがコミットしたパスワードがリポジトリに格納されているパスワードと一致するかどうかを決定する方法を提供します.
ハッシュの強度は、コンストラクション関数のパラメータ値(Version、Stress、SecureRandom instance)によって調整できます.
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**");
}
Webセキュリティは、FilterChainProxyを生成するフィルタです.antMatchersで指定したアドレスのファイルは認証を無視します.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// (1)
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/**").permitAll()
// (2)
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/login-success")
.permitAll()
// (3)
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/logout-success")
.invalidateHttpSession(true)
// (4)
.and()
.exceptionHandling().accessDeniedPage("/denied");
}
HttpSecurityにより、HTTPリクエストに対してWebベースのセキュリティを構成することができる.(1). antMatchers()メソッドを使用して特定のパスを指定し、permitAll()メソッドとhasRole()メソッドを使用してロールに基づいてアクセス設定を設定します.
(2). フォームベースの認証はformLogin()で行います.ログイン情報は基本的にHttpSessionを使用します.
(3). ログアウトの方法をサポートします.WebSecurityコンフィギュレータアダプタを使用すると自動的に有効になります.
デフォルトでは、/logoutにアクセスするとHTTPセッションが削除されます.
(4). 処理403異常処理のハンドル.
4.メンバーエンティティロールの追加
4-1. Role enumの作成
file : Role.javapackage com.rptp.rptpSpringBoot.core.member.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum Role {
ADMIN("ROLE_ADMIN"),
MEMBER("ROLE_MEMBER"),
GUEST("ROLE_GUEST");
private String value;
}
4-2. メンバーの変更
file : Member.javapackage com.rptp.rptpSpringBoot.core.member.domain;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberId;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String password;
private String profilePhoto;
@Column(nullable = false)
private String nickName;
//==================add==================
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private Role role = Role.GUEST;
//==================add==================
@Builder
public Member(String name, String password, String profilePhoto, String nickName) {
this.name = name;
this.password = password;
this.profilePhoto = profilePhoto;
this.nickName = nickName;
}
}
5.MemberRepository findByNameメソッドの追加
file : MemberRepository.javapackage com.rptp.rptpSpringBoot.core.member.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface MemberRepository extends JpaRepository<Member, Long> {
//==================add==================
Optional<Member> findByName(String name);
//==================add==================
}
6.メンバーサービスでのUserDetailServiceインタフェースの実装
file : MemberService.javapackage com.rptp.rptpSpringBoot.core.member.service;
import com.rptp.rptpSpringBoot.core.member.domain.Member;
import com.rptp.rptpSpringBoot.core.member.domain.MemberRepository;
import com.rptp.rptpSpringBoot.core.member.domain.Role;
import com.rptp.rptpSpringBoot.core.member.dto.SignUpRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Service
@RequiredArgsConstructor
//==================edit==================
public class MemberService implements UserDetailsService {
//==================edit==================
private final MemberRepository memberRepository;
private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Transactional
public Long signUp(SignUpRequest req) {
return memberRepository.save(buildMember(req)).getMemberId();
}
private Member buildMember(SignUpRequest req) {
return Member.builder()
.name(req.getName())
//==================edit==================
.password(passwordEncoder.encode(req.getPassword()))
//==================edit==================
.profilePhoto(req.getProfilePhoto())
.nickName(req.getNickName())
.build();
}
//==================add==================
@Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
Member member = memberRepository.findByName(name)
.orElseThrow(() -> new UsernameNotFoundException(name + "은 존재하지 않습니다"));
List<GrantedAuthority> authorities = new ArrayList<>();
if (member.getRole() == Role.ADMIN) {
authorities.add(new SimpleGrantedAuthority(Role.ADMIN.getValue()));
}
authorities.add(new SimpleGrantedAuthority(Role.MEMBER.getValue()));
return new User(member.getName(), member.getPassword(), authorities);
}
//==================add==================
}
7.ビューの追加&ハンドルの変更と追加
admin.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>어드민</title>
</head>
<body>
<h1>어드민 페이지입니다.</h1>
<hr>
</body>
</html>
demied.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>접근 거부</title>
</head>
<body>
<h1>접근 불가 페이지입니다.</h1>
<hr>
</body>
</html>
index.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
<h1>메인 페이지</h1>
<hr>
<a sec:authorize="isAnonymous()" th:href="@{/login}">로그인</a>
<a sec:authorize="isAuthenticated()" th:href="@{/logout}">로그아웃</a>
<a sec:authorize="isAnonymous()" th:href="@{/sign-up}">회원가입</a>
<a sec:authorize="hasRole('ROLE_MEMBER')" th:href="@{/user}">내정보</a>
<a sec:authorize="hasRole('ROLE_ADMIN')" th:href="@{/admin}">어드민</a>
</body>
</html>
sec:authorizerでログインユーザーの状態に応じてラベルを表示するかどうかを決定します.
login.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
유효하지 않은 아이디 또는 비밀번호입니다
</div>
<div th:if="${param.logout}">
로그아웃
</div>
<form th:action="@{/login}" method="post">
<!--input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /-->
<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="로그인"/></div>
</form>
</body>
</html>
Spring Securityが適用される場合、POST方式で送信されるすべてのデータはcsrfトークン値を必要とする.
したがって本来はフォーム上でcsrfトークンを一緒に送信すべきであるが、time lifeのth:actionを使用すると自動的にcsrfが送信される
login-success.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
로그인 완료!
<a th:href="@{/}">메인으로</a>
</body>
</html>
logout.html<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>로그아웃</title>
</head>
<body>
<h1>로그아웃 처리되었습니다.</h1>
<hr>
<a th:href="@{'/'}">메인으로 이동</a>
</body>
</html>
logout-success.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
로그아웃 완료!
<a th:href="@{/}">메인으로</a>
</body>
</html>
sign-up.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
<form th:action="@{/api/member}" method="post">
<label>아이디<input type="text" name="name"></label>
<label>password<input type="password" name="password"></label>
<label>프로필사진<input type="text" name="profilePhoto"></label>
<label>별명<input type="text" name="nickName"></label>
<input type="submit" value="가입하기">
</form>
</body>
</html>
sign-up-success.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
회원가입 완료!
<a th:href="@{/}">메인으로</a>
</body>
</html>
MainController.javapackage com.rptp.rptpSpringBoot.api;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/")
@RequiredArgsConstructor
public class MainController {
@GetMapping("")
public String index(){
return "/index";
}
@GetMapping("sign-up")
public String signUp() {
return "/sign-up";
}
@GetMapping("sign-up-success")
public String signUpSuccess() {
return "/sign-up-success";
}
@GetMapping("login")
public String login() {
return "/login";
}
@GetMapping("login-success")
public String loginSuccess() {
return "/login-success";
}
@GetMapping("logout-success")
public String logoutSuccess() {
return "/logout-success";
}
@GetMapping("denied")
public String denied() {
return "/denied";
}
@GetMapping("/admin")
public String admin() {
return "/admin";
}
}
実行結果
初回接続時のホームページ
会員収入
会員加入完了
ログイン
ログイン完了
一般ユーザーのホームページ
ログアウトページ
オペレータホーム-ROLEをdbからADMINに直接変更
リファレンス
https://mangkyu.tistory.com/76
https://victorydntmd.tistory.com/328
https://kimvampa.tistory.com/129
Reference
この問題について([イニシアチブ-SpringBoot]掲示板-3を作成します.Spring Securityの適用), 我々は、より多くの情報をここで見つけました
https://velog.io/@phjppo0918/初心-Spring-Boot-3.-Spring-Security-적용
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
package com.rptp.rptpSpringBoot.core.member.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum Role {
ADMIN("ROLE_ADMIN"),
MEMBER("ROLE_MEMBER"),
GUEST("ROLE_GUEST");
private String value;
}
package com.rptp.rptpSpringBoot.core.member.domain;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberId;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String password;
private String profilePhoto;
@Column(nullable = false)
private String nickName;
//==================add==================
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private Role role = Role.GUEST;
//==================add==================
@Builder
public Member(String name, String password, String profilePhoto, String nickName) {
this.name = name;
this.password = password;
this.profilePhoto = profilePhoto;
this.nickName = nickName;
}
}
file : MemberRepository.java
package com.rptp.rptpSpringBoot.core.member.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface MemberRepository extends JpaRepository<Member, Long> {
//==================add==================
Optional<Member> findByName(String name);
//==================add==================
}
6.メンバーサービスでのUserDetailServiceインタフェースの実装
file : MemberService.javapackage com.rptp.rptpSpringBoot.core.member.service;
import com.rptp.rptpSpringBoot.core.member.domain.Member;
import com.rptp.rptpSpringBoot.core.member.domain.MemberRepository;
import com.rptp.rptpSpringBoot.core.member.domain.Role;
import com.rptp.rptpSpringBoot.core.member.dto.SignUpRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Service
@RequiredArgsConstructor
//==================edit==================
public class MemberService implements UserDetailsService {
//==================edit==================
private final MemberRepository memberRepository;
private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Transactional
public Long signUp(SignUpRequest req) {
return memberRepository.save(buildMember(req)).getMemberId();
}
private Member buildMember(SignUpRequest req) {
return Member.builder()
.name(req.getName())
//==================edit==================
.password(passwordEncoder.encode(req.getPassword()))
//==================edit==================
.profilePhoto(req.getProfilePhoto())
.nickName(req.getNickName())
.build();
}
//==================add==================
@Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
Member member = memberRepository.findByName(name)
.orElseThrow(() -> new UsernameNotFoundException(name + "은 존재하지 않습니다"));
List<GrantedAuthority> authorities = new ArrayList<>();
if (member.getRole() == Role.ADMIN) {
authorities.add(new SimpleGrantedAuthority(Role.ADMIN.getValue()));
}
authorities.add(new SimpleGrantedAuthority(Role.MEMBER.getValue()));
return new User(member.getName(), member.getPassword(), authorities);
}
//==================add==================
}
7.ビューの追加&ハンドルの変更と追加
admin.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>어드민</title>
</head>
<body>
<h1>어드민 페이지입니다.</h1>
<hr>
</body>
</html>
demied.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>접근 거부</title>
</head>
<body>
<h1>접근 불가 페이지입니다.</h1>
<hr>
</body>
</html>
index.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
<h1>메인 페이지</h1>
<hr>
<a sec:authorize="isAnonymous()" th:href="@{/login}">로그인</a>
<a sec:authorize="isAuthenticated()" th:href="@{/logout}">로그아웃</a>
<a sec:authorize="isAnonymous()" th:href="@{/sign-up}">회원가입</a>
<a sec:authorize="hasRole('ROLE_MEMBER')" th:href="@{/user}">내정보</a>
<a sec:authorize="hasRole('ROLE_ADMIN')" th:href="@{/admin}">어드민</a>
</body>
</html>
sec:authorizerでログインユーザーの状態に応じてラベルを表示するかどうかを決定します.
login.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
유효하지 않은 아이디 또는 비밀번호입니다
</div>
<div th:if="${param.logout}">
로그아웃
</div>
<form th:action="@{/login}" method="post">
<!--input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /-->
<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="로그인"/></div>
</form>
</body>
</html>
Spring Securityが適用される場合、POST方式で送信されるすべてのデータはcsrfトークン値を必要とする.
したがって本来はフォーム上でcsrfトークンを一緒に送信すべきであるが、time lifeのth:actionを使用すると自動的にcsrfが送信される
login-success.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
로그인 완료!
<a th:href="@{/}">메인으로</a>
</body>
</html>
logout.html<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>로그아웃</title>
</head>
<body>
<h1>로그아웃 처리되었습니다.</h1>
<hr>
<a th:href="@{'/'}">메인으로 이동</a>
</body>
</html>
logout-success.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
로그아웃 완료!
<a th:href="@{/}">메인으로</a>
</body>
</html>
sign-up.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
<form th:action="@{/api/member}" method="post">
<label>아이디<input type="text" name="name"></label>
<label>password<input type="password" name="password"></label>
<label>프로필사진<input type="text" name="profilePhoto"></label>
<label>별명<input type="text" name="nickName"></label>
<input type="submit" value="가입하기">
</form>
</body>
</html>
sign-up-success.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
회원가입 완료!
<a th:href="@{/}">메인으로</a>
</body>
</html>
MainController.javapackage com.rptp.rptpSpringBoot.api;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/")
@RequiredArgsConstructor
public class MainController {
@GetMapping("")
public String index(){
return "/index";
}
@GetMapping("sign-up")
public String signUp() {
return "/sign-up";
}
@GetMapping("sign-up-success")
public String signUpSuccess() {
return "/sign-up-success";
}
@GetMapping("login")
public String login() {
return "/login";
}
@GetMapping("login-success")
public String loginSuccess() {
return "/login-success";
}
@GetMapping("logout-success")
public String logoutSuccess() {
return "/logout-success";
}
@GetMapping("denied")
public String denied() {
return "/denied";
}
@GetMapping("/admin")
public String admin() {
return "/admin";
}
}
実行結果
初回接続時のホームページ
会員収入
会員加入完了
ログイン
ログイン完了
一般ユーザーのホームページ
ログアウトページ
オペレータホーム-ROLEをdbからADMINに直接変更
リファレンス
https://mangkyu.tistory.com/76
https://victorydntmd.tistory.com/328
https://kimvampa.tistory.com/129
Reference
この問題について([イニシアチブ-SpringBoot]掲示板-3を作成します.Spring Securityの適用), 我々は、より多くの情報をここで見つけました
https://velog.io/@phjppo0918/初心-Spring-Boot-3.-Spring-Security-적용
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
package com.rptp.rptpSpringBoot.core.member.service;
import com.rptp.rptpSpringBoot.core.member.domain.Member;
import com.rptp.rptpSpringBoot.core.member.domain.MemberRepository;
import com.rptp.rptpSpringBoot.core.member.domain.Role;
import com.rptp.rptpSpringBoot.core.member.dto.SignUpRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Service
@RequiredArgsConstructor
//==================edit==================
public class MemberService implements UserDetailsService {
//==================edit==================
private final MemberRepository memberRepository;
private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Transactional
public Long signUp(SignUpRequest req) {
return memberRepository.save(buildMember(req)).getMemberId();
}
private Member buildMember(SignUpRequest req) {
return Member.builder()
.name(req.getName())
//==================edit==================
.password(passwordEncoder.encode(req.getPassword()))
//==================edit==================
.profilePhoto(req.getProfilePhoto())
.nickName(req.getNickName())
.build();
}
//==================add==================
@Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
Member member = memberRepository.findByName(name)
.orElseThrow(() -> new UsernameNotFoundException(name + "은 존재하지 않습니다"));
List<GrantedAuthority> authorities = new ArrayList<>();
if (member.getRole() == Role.ADMIN) {
authorities.add(new SimpleGrantedAuthority(Role.ADMIN.getValue()));
}
authorities.add(new SimpleGrantedAuthority(Role.MEMBER.getValue()));
return new User(member.getName(), member.getPassword(), authorities);
}
//==================add==================
}
admin.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>어드민</title>
</head>
<body>
<h1>어드민 페이지입니다.</h1>
<hr>
</body>
</html>
demied.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>접근 거부</title>
</head>
<body>
<h1>접근 불가 페이지입니다.</h1>
<hr>
</body>
</html>
index.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
<h1>메인 페이지</h1>
<hr>
<a sec:authorize="isAnonymous()" th:href="@{/login}">로그인</a>
<a sec:authorize="isAuthenticated()" th:href="@{/logout}">로그아웃</a>
<a sec:authorize="isAnonymous()" th:href="@{/sign-up}">회원가입</a>
<a sec:authorize="hasRole('ROLE_MEMBER')" th:href="@{/user}">내정보</a>
<a sec:authorize="hasRole('ROLE_ADMIN')" th:href="@{/admin}">어드민</a>
</body>
</html>
sec:authorizerでログインユーザーの状態に応じてラベルを表示するかどうかを決定します.login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
유효하지 않은 아이디 또는 비밀번호입니다
</div>
<div th:if="${param.logout}">
로그아웃
</div>
<form th:action="@{/login}" method="post">
<!--input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /-->
<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="로그인"/></div>
</form>
</body>
</html>
Spring Securityが適用される場合、POST方式で送信されるすべてのデータはcsrfトークン値を必要とする.したがって本来はフォーム上でcsrfトークンを一緒に送信すべきであるが、time lifeのth:actionを使用すると自動的にcsrfが送信される
login-success.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
로그인 완료!
<a th:href="@{/}">메인으로</a>
</body>
</html>
logout.html<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>로그아웃</title>
</head>
<body>
<h1>로그아웃 처리되었습니다.</h1>
<hr>
<a th:href="@{'/'}">메인으로 이동</a>
</body>
</html>
logout-success.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
로그아웃 완료!
<a th:href="@{/}">메인으로</a>
</body>
</html>
sign-up.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
<form th:action="@{/api/member}" method="post">
<label>아이디<input type="text" name="name"></label>
<label>password<input type="password" name="password"></label>
<label>프로필사진<input type="text" name="profilePhoto"></label>
<label>별명<input type="text" name="nickName"></label>
<input type="submit" value="가입하기">
</form>
</body>
</html>
sign-up-success.html<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>RPTP</title>
</head>
<body>
회원가입 완료!
<a th:href="@{/}">메인으로</a>
</body>
</html>
MainController.javapackage com.rptp.rptpSpringBoot.api;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/")
@RequiredArgsConstructor
public class MainController {
@GetMapping("")
public String index(){
return "/index";
}
@GetMapping("sign-up")
public String signUp() {
return "/sign-up";
}
@GetMapping("sign-up-success")
public String signUpSuccess() {
return "/sign-up-success";
}
@GetMapping("login")
public String login() {
return "/login";
}
@GetMapping("login-success")
public String loginSuccess() {
return "/login-success";
}
@GetMapping("logout-success")
public String logoutSuccess() {
return "/logout-success";
}
@GetMapping("denied")
public String denied() {
return "/denied";
}
@GetMapping("/admin")
public String admin() {
return "/admin";
}
}
実行結果
初回接続時のホームページ
会員収入
会員加入完了
ログイン
ログイン完了
一般ユーザーのホームページ
ログアウトページ
オペレータホーム-ROLEをdbからADMINに直接変更
リファレンス
https://mangkyu.tistory.com/76
https://victorydntmd.tistory.com/328
https://kimvampa.tistory.com/129
Reference
この問題について([イニシアチブ-SpringBoot]掲示板-3を作成します.Spring Securityの適用), 我々は、より多くの情報をここで見つけました
https://velog.io/@phjppo0918/初心-Spring-Boot-3.-Spring-Security-적용
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
https://mangkyu.tistory.com/76
https://victorydntmd.tistory.com/328
https://kimvampa.tistory.com/129
Reference
この問題について([イニシアチブ-SpringBoot]掲示板-3を作成します.Spring Securityの適用), 我々は、より多くの情報をここで見つけました https://velog.io/@phjppo0918/初心-Spring-Boot-3.-Spring-Security-적용テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol