스프링

 

 

Thymeleaf + Spring Security integration basics

https://www.thymeleaf.org/doc/articles/springsecurity.html

 

 

 

 

spring.io

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

http://www.kyobobook.co.kr/product/detailViewKor.laf?ejkGb=KOR&mallGb=KOR&barcode=9788997924899&orderClick=LAG&Kc=

 

 

 

 

about author

PHRASE

Level 60  라이트

여자는 남자와의 관계가 파경에 이르면 여자는 이별 또한 철저하다. -프란체스코 알베로니

댓글 ( 4)

댓글 남기기

작성