Ex) SecurityConfiguration
- demo4 프로젝트 만든다. 종속성 Spring security 선택, pom 파일에서 복사해서
- fastlms에 붙여넣기
-애플리케이션 실행하면 시큐리티 패스워드가 뜬다.
- 로컬 접속 - 위의 패스워드 입력해야 로그인이 가능하다.
- 시큐리티 Config 클래스 추가, 주소에 대한 권한 설정
-> antMachers: '/' => 루트 페이지, '/**' => 루트 및 모든 페이지
-> 로그인 없이 접속 가능해진다.
<hide/>
package com.zerobase.fastlms;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
// 주소에 대한 권한 설정
http.authorizeRequests()
.antMatchers("/", "/**")
.permitAll();
super.configure(http);
}
}
===============================오류===================================
- 오류: 로그아웃 다음에 다시 로컬 접속하면 바로 메인 페이지가 떠야하는데 로그인 페이지가 뜬다.
- 원인: http.authorizeHttpRequests() 가 아니라 http.authorizeRequests()로 고친다.
- 실행 구성 설정
- 아래 처럼 실행 / 디버그 설정한다.
- 메인 페이지 - 회원 정보 페이지 만든다.
- 컨트롤러 내용 추가
<hide/>
@GetMapping("/member/info")
public String memberInfo(){
return "member/member_info";
}
- 멤버인포 html 파일 추가
<hide/>
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>회원 정보</title>
</head>
<body>
<h1>회원 정보</h1>
</body>
</html>
- SeucurityConfig 에 루트 페이지, 회원 가입 페이지, 계정 활성화 페이지 세 개는 로그인 없이 접근 되도록 만든다.
<hide/>
package com.zerobase.fastlms;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
// 주소에 대한 권한 설정
http.authorizeRequests()
.antMatchers("/",
"/member/register",
"/member/email-auth")
.permitAll();
super.configure(http);
}
}
- 로그인 페이지도 설정 가능하다.
-> failureHandler(null):
- SimpleUrlAuthenticationFailureHandler를 상속받는 클래스도 만든다.
<hide/>
package com.zerobase.fastlms.configuration;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class UserAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
// 에러 발생한 경우
setUseForward(true);
setDefaultFailureUrl("/member/login?error=true");
request.setAttribute("errorMessage", "로그인에 실패했습니다.");
super.onAuthenticationFailure(request, response, exception);
}
}
- 시큐리티 컨피그
-> 메서드 get 추가
-> 상속해야 userDetailsService()에 멤버서비스를 넣었을 때 오류가 안 난다.
<hide/>
package com.zerobase.fastlms.configuration;
import com.zerobase.fastlms.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;
@RequiredArgsConstructor
@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
private final MemberService memberService;
@Bean
UserAuthenticationFailureHandler getFailureHandler(){
return new UserAuthenticationFailureHandler();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 주소에 대한 권한 설정
http.authorizeHttpRequests()
.antMatchers("/",
"/member/register",
"/member/email-auth")
.permitAll();
http.formLogin()
.loginPage("/member/login")
.failureHandler(null)
.permitAll();
super.configure(http);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(memberService);
super.configure(auth);
}
}
- Impl 클래스
<hide/>
package com.zerobase.fastlms.member.service.impl;
import com.zerobase.fastlms.component.MailComponents;
import com.zerobase.fastlms.member.MemberRepository;
import com.zerobase.fastlms.member.entity.Member;
import com.zerobase.fastlms.member.model.MemberInput;
import com.zerobase.fastlms.member.service.MemberService;
import lombok.RequiredArgsConstructor;
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.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
private final MailComponents mailComponents;
@Override
public boolean register(MemberInput parameter) {
Optional<Member>optionalMember = memberRepository.findById(parameter.getUserId());
if(optionalMember.isPresent()){ // 이미 아이디가 존재하면?
return false;
}
// 회원가입입
String uuid = UUID.randomUUID().toString();
Member member = Member.builder()
.userId(parameter.getUserId())
.userName(parameter.getUserName())
.phone(parameter.getPhone())
.password(parameter.getPassword())
.regDt(LocalDateTime.now())
.emailAuthYn(false)
.emailAuthKey(uuid)
.build();
memberRepository.save(member);
// 메일 보내기
String email = parameter.getUserId();
String subject = "fastlms 사이트 가입을 축하드립니다.";
String text = "<p>fastlms 사이트 가입을 축하드립니다.</p><p>아래 링크를 클릭하셔서 가입을 완료하세요.</p>" +
"<div><a target='_blank' href='http://localhost:8080/member/email-auth?id=" + uuid + "'> 가입완료 </a></div>";
mailComponents.sendMail(email, subject, text);
return true;
}
@Override
public boolean emailAuth(String uuid) {
Optional<Member> optionalMember = memberRepository.findByEmailAuthKey(uuid); // 있으면 Optional<Member> 가 리턴된다.
if(!optionalMember.isPresent()){
return false;
}
Member member = optionalMember.get(); // 존재하는 경우 멤버
member.setEmailAuthYn(true);
member.setEmailAuthDt(LocalDateTime.now());
memberRepository.save(member);
return true;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 실질적으로 이메일
Optional<Member> optionalMember = memberRepository.findById(username);
if(!optionalMember.isPresent()){
throw new UsernameNotFoundException("회원 정보가 존재하지 않습니다.");
}
Member member = optionalMember.get();
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return new User(member.getUserId(), member.getPassword(), grantedAuthorities); // role
}
}
- 시큐리티 컨피그 클래스
-> 패스워드 인코더를 받아서 auth에다가 주입해준다.
-> 회원가입 할 때 패스워드를 지정해줘야한다.
<hide/>
package com.zerobase.fastlms.configuration;
import com.zerobase.fastlms.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;
@RequiredArgsConstructor
@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
private final MemberService memberService;
@Bean
PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
UserAuthenticationFailureHandler getFailureHandler(){
return new UserAuthenticationFailureHandler();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 주소에 대한 권한 설정
http.authorizeHttpRequests()
.antMatchers("/",
"/member/register",
"/member/email-auth")
.permitAll();
http.formLogin()
.loginPage("/member/login")
.failureHandler(getFailureHandler())
.permitAll();
super.configure(http);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(memberService)
.passwordEncoder(getPasswordEncoder());
super.configure(auth);
}
}
- 컨트롤러에서 로그인 페이지를 만든다. get 이랑 post 모두 받을 수 있도록 @RequestMapping
<hide/>
@RequestMapping("/member/login")
public String login(){
return "member/login";
}
Ex)
- 로그인 페이지
-> username은 변수이름? 어디에서??
<hide/>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>회원 로그인</title>
</head>
<body>
<h1>회원 로그인</h1>
<form method="post">
<div>
<input type="text" name="username" placeholder="아이디(이메일)을 입력해주세요"/>
</div>
<div>
<input type="password" name="password" placeholder="비밀번호 입력"/>
</div>
<div>
<button type="submit" >로그인</button>
</div>
</form>
</body>
</html>
=================================== 오류 ====================================
- 오류: 로컬 루트 페이지 접속하는데 로그인 페이지가 나온다.
- 추측한 원인: SecurityConfiguration에서 failureHandler(getFailureHandler()) <= 매개변수를 안 넣어줬다. => 이게 문제 가 아니었다.
-> SecurityConfiguration에 메서드를 잘못 넣었다. (위에 씀)
- SecurityConfig
-> csrf()를 넣으면 보안적 이슈 있으나 작동하지 않도록 잠시 넣는다.
<hide/>
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
// 주소에 대한 권한 설정
http.authorizeHttpRequests()
.antMatchers("/",
"/member/register",
"/member/email-auth")
.permitAll();
http.formLogin()
.loginPage("/member/login")
.failureHandler(null)
.permitAll();
super.configure(http);
}
<재부팅>
=========================오류========================
- 오류: 로그인 정보를 입력하면 강사님처럼 request method 'POST' not supported가 나와야되는데 안 나온다.
- 추측한 원인: Sec Config에서 failureHandler(getFailureHandler()) <= 매개변수를 안 넣어줬다.
- UserAuth ,.,,, 추가
<hide/>
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
// 에러 발생한 경우
setUseForward(true);
setDefaultFailureUrl("/member/login?error=true");
request.setAttribute("errorMessage", "로그인에 실패했습니다.");
System.out.println("로그인에 실패했습니다.");
super.onAuthenticationFailure(request, response, exception);
}
- 로그인 페이지
-> 여디서 "errorMessage"라는 이름의 변수는 로그인 페이지가 변수를 넘겨받는건가? 어디서?
<hide/>
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>회원 로그인</title>
</head>
<body>
<h1>회원 로그인</h1>
<div th:text="${errorMessage}">
</div>
<form method="post">
<div>
<input type="text" name="username" placeholder="아이디(이메일)을 입력해주세요"/>
</div>
<div>
<input type="password" name="password" placeholder="비밀번호 입력"/>
</div>
<div>
<button type="submit" >로그인</button>
</div>
<!-- </form>-->
</body>
</html>
- 시큐리티 컨피그에 로그아웃 내용 추가
-> 따로 html 파일을 넣지 않아도 세션을 초기화하고 루트 페이지로 돌아가게끔 만든다. ("/member/logout")
-> index 페이지에서 logout 이 걸려있기 때문이다. <a = href>
<hide/>
package com.zerobase.fastlms.configuration;
import com.zerobase.fastlms.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;
import static org.springframework.security.authorization.AuthenticatedAuthorizationManager.authenticated;
@RequiredArgsConstructor
@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
private final MemberService memberService;
@Bean
PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
UserAuthenticationFailureHandler getFailureHandler(){
return new UserAuthenticationFailureHandler();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); // csrf: 토큰
// 주소에 대한 권한 설정
http.authorizeRequests()
.antMatchers("/",
"/member/register",
"/member/email-auth")
.permitAll();
http.formLogin()
.loginPage("/member/login")
.failureHandler(getFailureHandler())
.permitAll();
http.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/member/logout"))
.logoutSuccessUrl("/") // 로그아웃 성공하면 메인페이지 이동
.invalidateHttpSession(true); // 로그아웃했으니 새션 초기화
super.configure(http);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(memberService)
.passwordEncoder(getPasswordEncoder());
super.configure(auth);
}
}
- 로그아웃 눌렀을 때 화면 - 확인하기
Ex) 이메일 인증을 완료한 사람만 로그인 가능하도록 하려면??
- 다음과 같이 인증받지 않은 계정도 있다.
- 예외도 새롭게 추가한다.
-> 런타임 을 상속받아야 문제 없다.
<hide/>
package com.zerobase.fastlms.member.exception;
public class MemberNotEmailAuthException extends RuntimeException {
public MemberNotEmailAuthException(String error) {
super(error);
}
}
- 그럼 failHandler 쪽으로 예외가 떨어진다.
- 회원 로그인 창에서 이메일 인증 받지 않은 정보를 입력하면?
- 디버깅 모드
-> InternalAuthenticationServiceException 발생한다.
- UserAuth
-> 예외 이름을 알았으니 넣어준다.
<hide/>
package com.zerobase.fastlms.configuration;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class UserAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
String msg = "로그인에 실패했습니다.";
if(exception instanceof InternalAuthenticationServiceException){
msg = exception.getMessage();
}
// 에러 발생한 경우
setUseForward(true);
setDefaultFailureUrl("/member/login?error=true");
request.setAttribute("errorMessage", msg); // html 파일에다가? 변수로?
System.out.println("로그인에 실패했습니다.");
super.onAuthenticationFailure(request, response, exception);
}
}
- 인증 받지 않은 이메일로 로그인을 시도하면?
- 이메일 로그인해서 이메일 인증을 해준다.
-활성화 되면 활성화 되었다는 알림창이 뜬다.
'Spring Projcect > 학습 관리 시스템 & 백오피스 구축' 카테고리의 다른 글
Chapter 09. 관리자 로그인 구현 (0) | 2022.08.20 |
---|---|
Chapter 08. 스프링 부트 프로젝트 - 비밀번호 찾기(초기화) (0) | 2022.08.18 |
Chapter 05. 스프링부트 기반 웹 프로젝트 구성 (0) | 2022.08.09 |
Chapter 04. MariaDB 설치 및 환경설정 (0) | 2022.08.09 |
Chapter 03. 데이터 구조 설계 (0) | 2022.08.09 |