Thymeleaf + Spring Security integration basics
https://www.thymeleaf.org/doc/articles/springsecurity.html
https://spring.io/guides/gs/securing-web/
스프링 부트 사용버전
1. 소스 : https://github.com/braverokmc79/Springboot-JPA-Blog (jsp, thymeleaf 둘다 사용)
2. 소스 : https://github.com/braverokmc79/spring-boot-jpa-member-join (thymeleaf )
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.2</version> </parent>
1 .pom.xml
<!-- ============================- --> <!-- 시큐리티 라이브러리 시작 --> <!-- spring-boot-starter-security 추가 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- 유효성 검증을 위해 spring-boot-starter-validation 추가 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <!-- 시큐리티 테스틀 위해 추가 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <!-- jsp 사용시 로그인 상태 유무 권한 설정할 수 있게 추가--> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> </dependency> <!-- thymeleaf 에서 로그인 상태 유무 권한 설정할 수 있게 추가 --> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> </dependency> <!-- 시큐리티 라이브러리 끝 --> <!-- ============================- -->
스프링 시큐리티는
위 라이브러리 등록후 아래와 같은 두개의 핵심적인 클래스만 생성하면 SecurityConfig, PrincipalDetailService
거의 완료 되었다고 볼수 있다.
1. 소스 : https://github.com/braverokmc79/Springboot-JPA-Blog
1. SecurityConfig.java
package com.cos.blog.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.method.configuration.EnableGlobalMethodSecurity;
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.web.util.matcher.AntPathRequestMatcher;
import com.cos.blog.config.auth.PrincipalDetailService;
@Configuration //빈등록 (IOC 관리)
@EnableWebSecurity //시큐리티 필터가 등록이 된다.
@EnableGlobalMethodSecurity(prePostEnabled = true) //특정 주소로 접근을 하면 권한 및 인증을 미리 체크하겠다는 뜻.
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private PrincipalDetailService principalDetailService;
@Bean //loC 가 된다.
public BCryptPasswordEncoder encodePWD() {
return new BCryptPasswordEncoder();
}
/**
*
* Spring Secureity 에서 인증은 AuthenticationManager 를 통해 이루어지며
* AuthenticationManagerBuilder 가 AuthenticationManager 를 생성합니다.
* userDetailsService 를 구현하고 있는 객체로 PrincipalDetailService를 지정해 주며,
* 비밀번호 암호화를 위해 passwordEncoder 를 지정해줍니다.
//시큐리티가 대신 로그인해주는데 password 를 가로채기를 하는데
//해당 password 가 뭘로 해쉬가 되어 회원가입이 되었는지 알아야
//같은 해쉬로 암호화해서 DB에 있는 해쉬랑 비교할 수 있음.
* */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(principalDetailService).passwordEncoder(encodePWD());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//.csrf().disable() //csrf 토큰 비활성화(테스트시 걸어두는 것이 좋다)
.authorizeRequests() //시큐리티 처리에 HttpServletRequest 를 이용한다는 것을 의미
.mvcMatchers("/", "/auth/**", "/js/**", "/css/**", "/images/**")
.permitAll()
.anyRequest().authenticated(); //위설정한 경로를 제외한 나머지 경로들은 모든 인증을 요구하도록 설정
http
.formLogin()
.loginPage("/auth/loginForm") //로그인 페이지 URL 을 설정
.loginProcessingUrl("/auth/loginProc") //스프링 시큐리티가 해당 주소로 요청오는 로그인을 가로채서 대신 로그인 해준다. 적지 않으면 .loginPage("/auth/loginForm") 값이 기본값 처리
.usernameParameter("username") //로그인시 사용할 파라미터 이름으로 username (또는 email) 여기서는 username 을 지정
.passwordParameter("password") //없을시 기본값 :password
.defaultSuccessUrl("/") // 로그인 성공시 이동할 URL 을 설정
.failureUrl("/auth/loginForm/error") // 로그인 실패시 이동할 URL 을 설정
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/auth/logout")) // 임의로그아웃 URL 을 설정하면당 스프링시큐리티가 해당 url 로 로그아웃 처리 . 기본 값은 logout
.logoutSuccessUrl("/"); //로그아웃 성공시 이동할 URL 을 설정
}
/**
*static 디렉터리의 하위 파일은 인증을 무시하도록 설정
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**");
}
}
2.PrincipalDetailService
package com.cos.blog.config.auth;
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;
import org.springframework.transaction.annotation.Transactional;
import com.cos.blog.model.User;
import com.cos.blog.repository.UserRepository;
@Service //Bean 등록
@Transactional
public class PrincipalDetailService implements UserDetailsService{
@Autowired
private UserRepository userRepository;
/**스프링이 로그인 요청을 가로챌 때, username, passwrod 변수 2개를 가로채는데
password 부분처리는 알아서 함.
username (email) 이 DB 에 있는지만 확인해주면 됨. return
*/
/**방법 1
컨트롤에서 다음과 같은 방법으로 시큐리티 세션정보를 가져온다.
Principal principal
@GetMapping("/user/updateForm")
public String updateUserForm(Principal principal, Model model) {
User userInfo =userService.getByUsername(principal.getName());
model.addAttribute("userInfo" ,userInfo);
return "user/updateUserForm";
}
*/
/**
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//이메일경우 예
// User principal=userRepository.findByEmail(email);
// if(principal==null){
// throw new UsernameNotFoundException(email);
// }
// UserDetails result= org.springframework.security.core.userdetails.User.builder()
// .username(principal.getEmail())
// .password(principal.getPassword())
// .roles(principal.getRole().toString())
// .build();
User principal=userRepository.findByUsername(username).orElseThrow(()->{
return new UsernameNotFoundException("해당 사용자를 찾을 수 없습니다. :" + username);
});
UserDetails result= org.springframework.security.core.userdetails.User.builder()
.username(principal.getUsername())
.password(principal.getPassword())
.roles(principal.getRole().toString())
.build();
return result;
}
*/
/**방법2 - 커스텀 로그인 방법 : UserDetails 상속받은 PrincipalDetails 생성후 로그인 처리 방법
컨트롤에서 다음과 같이 시큐리티 세션정보를 가져올수 있다.
@AuthenticationPrincipal PrincipalDetails principal
@GetMapping("/user/updateForm")
public String updateUserForm(@AuthenticationPrincipal PrincipalDetails principal, Model model) {
model.addAttribute("userInfo" ,principal.getUser());
return "user/updateUserForm";
}
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User principal=userRepository.findByUsername(username).orElseThrow(()->{
return new UsernameNotFoundException("해당 사용자를 찾을 수 없습니다. :" + username);
});
if(principal == null) {
return null;
}
return new PrincipalDetails(principal);
}
}
PrincipalDetails
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
import com.cos.blog.model.User;
import groovy.transform.ToString;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@Data
@ToString
public class PrincipalDetails implements UserDetails, OAuth2User{
private static final long serialVersionUID = 1L;
private User user;
private Map<String, Object> attributes;
//일반 로그인
public PrincipalDetails(User user) {
this.user=user;
}
// OAuth 로그인
public PrincipalDetails(User user, Map<String, Object> attributes) {
this.user = user;
this.attributes = attributes;
}
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
@Override
public String getName() {
return String.valueOf(user.getId());
}
// 권한 (원래 권한이 여러개 있을 수 있으므로 Collection 루프 돌려야 함)
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
// collect.add(new GrantedAuthority() {
// @Override
// public String getAuthority() {
// return memberDto.getMemberRole(); // "ROLE_" + @가 되어야 함
// }
// });
//스프링부트 2.5 이상 ROLE 슬러시를 붙이면 안된다. 자동으로 붙여준다.
authorities.add( () -> { return user.getRole().toString();});
return authorities;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
// 계정 만료(false) 여부
@Override
public boolean isAccountNonExpired() {
return true;
}
// 계정 정지(false) 여부
@Override
public boolean isAccountNonLocked() {
return true;
}
// 계정 신용(false) 여부
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 계정 활성(false) 여부 (예 : 최종 로그인 후 일정기간 경과 여부 -> 휴면계정)
@Override
public boolean isEnabled() {
return true;
}
}
다음 아래부터 추가적으로 내용 참조.
spring-boot-starter-security 추가하면 스프링 시큐리티에서 기본적으로 로그인 페이지로 이동함.
아이디 user , 비밀번호는 콘솔창에 출력
2. SecurityConfig 추가
다음 클래스를 추가하면 더이상 인증 요청하지 않으며
커스텀 로그인 처리
import com.macaronics.service.MemberService;
import groovy.lang.GrabConfig;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
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;
/**
*WebSecurityConfigurerAdapter 상속받는 클래스에 @EnableWebSecurity 어노테이션을
*선언하면 SpringSecurityFilterChain 이 자동으로 포함
*WebSecurityConfigurerAdapter를 상송받아서 메소드 오버라이딩을 통해 보안 설정을 커스터마이징할 수 있음
*
*http 요청에 대한 보안 으로 페이지 권한 설정, 로그인 페이지 설정, 로그아웃 메소드 등에 대한 설정.
*
*
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
MemberService memberService;
@Override
protected void configure(HttpSecurity http) throws Exception{
http.formLogin()
.loginPage("/members/login") //로그인 페이지 URL 을 설정
.defaultSuccessUrl("/") // 로그인 성공시 이동할 URL 을 설정
.usernameParameter("email") //로그인시 사용할 파라미터 이름으로 email 을 지정
.failureUrl("/members/login/error") // 로그인 실패시 이동할 URL 을 설정
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/members/logout")) //로그아웃 URL 을 설정
.logoutSuccessUrl("/"); //로그아웃 성공시 이동할 URL 을 설정
http.authorizeRequests() //시큐리티 처리에 HttpServletRequest 를 이용한다는 것을 의미.
.mvcMatchers("/", "/members/**","/board/**", "/images/**").permitAll() //permitAll() 을 통해 모든 사용자가 인증(로그인 없이 해당 경로 접근 )
.mvcMatchers("/admin/**").hasRole("ADMIN") // admin 으로 시작하는 경로는 해당 계정이 ADMIN Role 일 경우에만 접근이 가능
.anyRequest().authenticated(); //위설정한 경로를 제외한 나머지 경로들은 모든 인증을 요구하도록 설정
http.exceptionHandling()
.authenticationEntryPoint(new CustomAuthenticationEntryPoint()); //인증되지 않은 사용자가 리소스에 접근하였을 때 수행되는 핸들러를 등록
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
*
* Spring Secureity 에서 인증은 AuthenticationManager 를 통해 이루어지며
* AuthenticationManagerBuilder 가 AuthenticationManager 를 생성합니다.
* userDetailsService 를 구현하고 있는 객체로 memberService를 지정해 주며,
* 비밀번호 암호화를 위해 passwordEncoder 를 지정해줍니다.
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(memberService).passwordEncoder(passwordEncoder());
}
/**
*static 디렉터리의 하위 파일은 인증을 무시하도록 설정
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**");
}
}
3. Role
public enum Role {
//역할은 자동으로 삽입되므로 'ROLE_'로 시작하면 안 됩니다
GUEST , //손님
USER , //일반 사용자
MANAGER , //중간 관리자
ADMIN ; //촤고 관리자
}
4. DTO
MemberFormDto
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class MemberFormDto {
@NotBlank(message = "이름은 필수 입력 값입니다.")
private String name;
@NotEmpty(message = "이메일은 필수 입력 값입니다.")
@Email(message = "이메일 형식을 맞춰주세요.")
private String email;
@NotEmpty(message = "비밀번호는 필수 입력 값입니다.")
//@Length(min = 4, max = 16, message = "비밀번호는 4자 이상, 16자 이하로 입력해 주세요.")
@Pattern(regexp="(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}",
message = "비밀번호는 영문 대,소문자와 숫자, 특수기호가 적어도 1개 이상씩 포함된 8자 ~ 20자의 비밀번호여야 합니다.")
private String password;
@NotEmpty(message = "주소는 필수 입력 값입니다.")
private String address;
}
5. entity
Member.java
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.Comment;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.macaronics.constant.Role;
import com.macaronics.dto.MemberFormDto;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Entity
@Table(name="member")
@Getter
@Setter
@ToString
public class Member {
@Id
@Column(name="member_id")
@GeneratedValue(strategy = GenerationType.AUTO)
@Comment("회원아이디")
private Long id;
@Comment("회원이름")
private String name;
@Column(unique = true)
@Comment("이메일")
private String email;
@Comment("비밀번호")
private String password;
@Comment("주소")
private String address;
/**Enum 순서가 바뀔 경우 문제가 발생할 수 있으므로
* EnumType.STRING 옵션을 사용해서 String
*/
@Enumerated(EnumType.STRING)
private Role role;
public static Member createMember(MemberFormDto memberFormDto, PasswordEncoder passwordEncoder, Role state){
Member member=new Member();
member.setName(memberFormDto.getName());
member.setEmail(memberFormDto.getEmail());
member.setAddress(memberFormDto.getAddress());
String password=passwordEncoder.encode(memberFormDto.getPassword());
member.setPassword(password);
member.setRole(state);
return member;
}
}
6. Repository
MemberRepository
import org.springframework.data.jpa.repository.JpaRepository;
import com.macaronics.entity.Member;
public interface MemberRepository extends JpaRepository {
Member findByEmail(String email);
}
7. Service
MemberService
import com.macaronics.entity.Member;
import com.macaronics.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
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.stereotype.Service;
import javax.transaction.Transactional;
/**
* @Transactional
* :비즈니스 로직을 담당하는 서비스 계층 클래스에 @Transactional 어노테이션을 선언합니다.
* 로직을 처리하닥 에러가 발생하였다면, 변경된 데이터를 로직을 수행하기 이전 상태로 콜백 시켜줍니다.
*
*
* @RequiredArgsConstructor
* :빈을 주입하는 방법으로 @Autowired 어노테이션을 이용하거나, 필드 주입(Setter 주입), 생성자 주입을 이용
* 하는 방법이 있습니다. @RequiredArgsConstructor 어노테이션은 final 이나 @NonNull 이 붙은 필드에
* 생성자를 생성해줍니다. 빈에 생성자가 1개이고 생성자의 파라미터 타입이 빈으로 등록이 가능하다면 @Autowired
* 어노테이션 없이 의존성 주입이 가능합니다.
*
*
*
*
* 로그인
*
* UserDetailsService
* UserDetailsService 인터페이스는 데이터베이스에서 회원 정보를 가져오는 역할을 담당
*
* loadUserByUsername() 메소드가 존재하며, 회원정보를 조회하여 사용자의 정보와 권한을
* 갖는 UserDetails 인터페이스를 반환
*
*
*
*
*
*/
@Service
@Transactional
@RequiredArgsConstructor
public class MemberService implements UserDetailsService {
private final MemberRepository memberRepository;
public Member saveMember(Member member){
validateDuplicateMember(member);
return memberRepository.save(member);
}
private void validateDuplicateMember(Member member){
Member findMember=memberRepository.findByEmail(member.getEmail());
if(findMember!=null){
throw new IllegalStateException("이미 가입된 회원입니다.");
}
}
/**
*
UserDetailsService 인터페이스의 loadUserByUsername() 메소드를 오버라이딩합니다.
로그인할 유저의 email을 파라미터로 전달받습니다.
UserDetail을 구현하고 있는 User 객체를 반환해줍니다. User 객체를 생성하기 위해서 생성자로
회원의 이메일, 비밀번호, role 을 파라미터로 넘겨 줍니다.
*
*/
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Member member=memberRepository.findByEmail(email);
if(member==null){
throw new UsernameNotFoundException(email);
}
UserDetails result= User.builder()
.username(member.getEmail())
.password(member.getPassword())
.roles(member.getRole().toString())
.build();
System.out.println(" UserDetail 정보 :" + result.toString());
// UserDetail 정보 :org.springframework.security.core.userdetails.User
// [Username=test@gmail.com,
// Password=[PROTECTED], Enabled=true,
// AccountNonExpired=true, credentialsNonExpired=true,
// AccountNonLocked=true, Granted Authorities=[ROLE_USER]]
return result;
}
}
8.Controller
MemberController
import com.macaronics.constant.Role;
import com.macaronics.dto.MemberFormDto;
import com.macaronics.entity.Member;
import com.macaronics.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.validation.Valid;
@RequestMapping("/members")
@Controller
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
private final PasswordEncoder passwordEncoder;
@GetMapping(value = "/join")
public String memberForm(Model model){
model.addAttribute("memberFormDto", new MemberFormDto());
return "member/memberForm";
}
@PostMapping(value = "/join")
public String memberForm(@Valid MemberFormDto memberFormDto, BindingResult bindingResult, Model model){
if(bindingResult.hasErrors()){
return "member/memberForm";
}
try{
Member member=Member.createMember(memberFormDto, passwordEncoder, Role.USER);
memberService.saveMember(member);
}catch (IllegalStateException e){
model.addAttribute("errorMessage", e.getMessage());
return "member/memberForm";
}
return "redirect:/";
}
@GetMapping(value = "/login")
public String loginMember(){
return "/member/memberLoginForm";
}
@GetMapping(value = "/login/error")
public String loginError(Model model){
model.addAttribute("loginErrorMsg", "아이디 또는 비밀번호를 확인해 주세요.");
return "/member/memberLoginForm";
}
}
9. View
header.html
http://www.thymeleaf.org/extras/spring-security
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<div th:fragment="header">
<nav class="navbar bg-dark color-white">
<ul class="nav nav-dark">
<li class="nav-item">
<a class="nav-link" href="/">홈</a>
</li>
<li class="nav-item" sec:authorize="hasAnyAuthority('ROLE_ADMIN')">
<a class="nav-link" href="/admin/">관리자페이지</a>
</li>
<li class="nav-item" sec:authorize="isAuthenticated()">
<a class="nav-link" href="/board">게시판</a>
</li>
<li class="nav-item" sec:authorize="isAnonymous()">
<a class="nav-link" href="/members/login">로그인</a>
</li>
<li class="nav-item" sec:authorize="isAnonymous()">
<a class="nav-link" href="/members/join">회원가입</a>
</li>
<li class="nav-item" sec:authorize="isAuthenticated()">
<a class="nav-link" href="/members/logout">로그아웃</a>
</li>
</ul>
</nav>
</div>
</html>
<div sec:authorize="isAuthenticated()">
이 콘텐츠는 인증된 사용자에게만 표시됩니다.
</div>
<div sec:authorize="hasRole('ROLE_ADMIN')">
이 콘텐츠는 관리자에게만 표시됩니다.
</div>
<div sec:authorize="hasRole('ROLE_USER')">
이 콘텐츠는 사용자에게만 표시됩니다.
</div>
The sec:authentication 기록된 사용자 이름과 역할을 표시하는 데 사용됩:
Logged user: <span sec:authentication="name">Bob</span> Roles: <span sec:authentication="principal.authorities">[ROLE_USER, ROLE_ADMIN]</span>
memberForm.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout=http://www.ultraq.net.nz/thymeleaf/layout
layout:decorate="~{layouts/layout1}">
<!--사용자 css 추가-->
<th:block layout:fragment="css">
<style>
.fieldError{
color:#bd2130;
}
</style>
</th:block>
<th:block layout:fragment="script">
<script>
$(document).ready(function(){
var errorMessage='[[${errorMessage}]]';
if(errorMessage!=null){
console.log(errorMessage);
}
});
</script>
</th:block>
<div layout:fragment="content">
<form action="/members/join" role="form" method="post" th:object="${memberFormDto}">
<div class="form-group">
<p class="fieldError">[[${errorMessage}]]</p>
</div>
<div class="form-group">
<label th:for="name">이름</label>
<input type="text" th:field="*{name}" class="form-control" placeholder="이름을 입력해주세요">
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="fieldError">Incorrect data</p>
</div>
<div class="form-group">
<label th:for="email">이메일주소</label>
<input type="text" th:field="*{email}" class="form-control" placeholder="이메일을 입력해주세요">
<p th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="fieldError">Incorrect data</p>
</div>
<div class="form-group">
<label th:for="password">비밀번호</label>
<input type="password" th:field="*{password}" class="form-control" placeholder="비밀번호을 입력해주세요">
<p th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="fieldError">Incorrect data</p>
</div>
<div class="form-group">
<label th:for="address">주소</label>
<input type="text" th:field="*{address}" class="form-control" placeholder="주소를 입력해주세요">
<p th:if="${#fields.hasErrors('address')}" th:errors="*{address}" class="fieldError">Incorrect data</p>
</div>
<div style="text-align:center">
<button type="submit" class="btn btn-primary">전송</button>
</div>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
</form>
</div>
</html>
memberLoginForm.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout=http://www.ultraq.net.nz/thymeleaf/layout
layout:decorate="~{layouts/layout1}">
<!--사용자 css 추가-->
<th:block layout:fragment="css">
<style>
.error{
color:#bd2130;
}
</style>
</th:block>
<div layout:fragment="content">
<form role="form" method="post" action="/members/login">
<div class="form-group">
<label th:for="email">이메일주소</label>
<input type="email" name="email" class="form-control" placeholder="이메일을 입력해주세요.">
</div>
<div class="form-group">
<label th:for="password">비밀번호</label>
<input type="password" name="password" id="password" class="form-control" placeholder="비밀번호 입력">
</div>
<p th:if="${loginErrorMsg}" class="error" th:text="${loginErrorMsg}" ></p>
<button class="btn btn-primary">로그인</button>
<button type="button" class="btn btn-primary" onclick="location.href='/members/join'">회원가입</button>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
</form>
</div>
</html>
10.컨트롤에서 세션을 어떻게 찾는지?
@GetMapping(value={"", "/"})
public String index(Principal principal) { //컨트롤에서 세션을 어떻게 찾는지?
if(principal!=null) {
System.out.println("로그인 사용자 아이디 : " +principal.getName());
}
return "index";
}
소스 :
https://github.com/braverokmc79/spring-boot-jpa-member-join
참조:
Spring Security 회원가입 / 로그인 구현
https://blog.javabom.com/minhee/session/spring-security-1/spring-security
[Spring Boot] 간단한 로그인 기능 구현 - Spring Security
https://hooongs.tistory.com/233
백견불여일타 스프링 부트 쇼핑몰 프로젝트 with JPA

















댓글 ( 4)
댓글 남기기