Spring Security는 스프링 기반 애플리케이션의 인증과 권한 부여를 담당하는 프레임워크이다.
보안과 관련된 여러 기능을 제공해주기에 많은 유저가 쓰고있다.
실제 회원가입을 진행할떄 가장 먼저 찾게되는 분야가 스프링 시큐리티 분야이다.
우선은 기본적인 흐름을 확인해보자
- request가 들어온다
- 실제 로그인을 진행할때 우리가 button을 눌러 from을 제출하는 등의 동작을 의미한다.
- MVC 패턴에선 mapping등으로 front와 back을 연결해주었으나 해당 설정으로 로그인 페이지가 어디인지 명시적으로 설정, 여기에 사용할 파라미터 또환 설정해주었다.
-
//로그인에 사용할 페이지, id 파라미터, password를 명시적으로 작성하였다 <security:form-login login-page="/loginPage" username-parameter="member_name" password-parameter="member_password" //사용할값 authentication-failure-url="/loginPage?message=error" //로그인 실패시 띄울 화면 authentication-success-handler-ref="customLoginSuccess" //로그인 성공시 담당할 핸들러 default-target-url="/notice" always-use-default-target="true" /> //기본적으로 접근하는 URL <security:logout logout-url="/logout" />
- AuthenticationFilter (토큰생성)
- UsernamePasswordAuthenticationToken은 해당 요청을 처리할 수 있는 Provider을 찾는데 사용
- AuthenticationFilter가 요청을 받아서 UsernamePasswordAuthenticationToken토큰(인증용 객체)을 생성
- AuthenticationFilter로 부터 인증용 객체를 전달 받는다.
- Authentication Manager에게 처리 위임
- Authentication Manager는 List형태로 Provider들을 갖고 있다.
-
//인증 매니저 구성화면 <security:authentication-manager alias="authenticationManager"> <security:authentication-provider user-service-ref="customUserDetailsService"> //UserDetailsService를 구현한 customUserDetailsService <security:password-encoder ref="bcryptPasswordEncoder" /> //비밀번호 암호화 목적 </security:authentication-provider> </security:authentication-manager>
- Token을 처리할 수 있는 Authentication Provider 선택
- 실제 인증을 할 AuthenticationProvider에게 인증용 객체를 다시 전달한다.
- 인증 절차
- 인증 절차가 시작되면 AuthenticationProvider 인터페이스가 실행되고 DB에 있는 사용자의 정보와 화면에서 입력한 로그인 정보를 비교
- UserDetailsService의 loadUserByUsername메소드 수행
- AuthenticationProvider 인터페이스에서는 authenticate() 메소드를 오버라이딩 하게 되는데 이 메소드의 파라미터인 인증용 객체로 화면에서 입력한 로그인 정보를 가져올 수 있다.
- AuthenticationProvider 인터페이스에서 DB에 있는 사용자의 정보를 가져오려면, UserDetailsService 인터페이스를 사용한다.
- UserDetailsService 인터페이스는 화면에서 입력한 사용자의 username으로 loadUserByUsername() 메소드를 호출하여 DB에 있는 사용자의 정보를 UserDetails 형으로 가져온다.
- 만약 사용자가 존재하지 않으면 예외를 던진다. 이렇게 DB에서 가져온 이용자의 정보와 화면에서 입력한 로그인 정보를 비교하게 되고, 일치하면 Authentication 참조를 리턴하고, 일치 하지 않으면 예외를 던진다.
-
@Service public class CustomUserDetailsService implements UserDetailsService{ @Autowired MemberDAO memberDAO; @Override public UserDetails loadUserByUsername(String member_name) throws UsernameNotFoundException { MemberVO member = memberDAO.memberLogin(member_name); if (member == null) { throw new UsernameNotFoundException("User not found"); } // 사용자 정보를 UserDetails 객체로 매핑 UserDetails userDetails = User.withUsername(member.getMember_name()) .password(member.getMember_password()) // 여기를 수정 UserDetails에 저장된 이미 인코딩된 비밀번호와 비교하여 사용자를 인증합니다 .roles(member.getMember_role()) // 사용자의 권한 정보 설정 .build(); return userDetails; }
- 인증이 완료되면 사용자 정보를 가진 Authentication 객체를 SecurityContextHolder에 담은 이후 AuthenticationSuccessHandle를 실행한다.(실패시 AuthenticationFailureHandler를 실행한다.)
(UsernamePassword)AuthenticationFilter
아이디와 비밀번호를 사용하는 form 기반 인증
설정된 로그인 URL로 오는 요청을 감시하며, 유저 인증 처리인 AuthenticationManager를 통한 인증 실행
인증이 성공한다면 인증용 객체를 SecurityContext에 저장 후 AuthenticationSuccessHandler 실행
실패한다면 AuthenticationFailureHandler 실행
//AuthenticationSuccessHandler을 상속받아 개인적으로 변경
//로그인이 완료되었을 경우 사용자별 시작 페이지를 다르게 설정
public class CustomLoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth)
throws IOException, ServletException {
System.out.println("Login Success");
List<String> roleNames = new ArrayList<>();
auth.getAuthorities().forEach(authority -> {
roleNames.add(authority.getAuthority());
});
if (roleNames.contains("ROLE_ADMIN")) {
response.sendRedirect("mypage");
return;
}
if (roleNames.contains("ROLE_USER")) {
response.sendRedirect("surveylist");
return;
}
if (roleNames.contains("ROLE_MASTER")) {
response.sendRedirect("notice");
return;
}
response.sendRedirect("notice");
}
}
AuthenticationProvider
화면에서 입력한 로그인 정보와 DB정보를 비교
Spring Security의 AuthenticationProvider을 구현한 클래스로 security-context에 provider로 등록 후 인증절차를 구현한다.
login view에서 login-processing-url로의 form action 진행 시 해당 클래스의 supports() > authenticate() 순으로 인증 절차 진행
UserDetailsService
UserDetailsService 인터페이스는 DB에서 유저 정보를 가져오는 역할
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
해당 코드가 기본적으로 제공되는 메서드이며 이것을 @Override해서 내가 필요로 하는 형식으로 변형해서 사용하면된다.
//내가 구현한 코드1
public UserDetails loadUserByUserEmail(String email){
System.out.println("Loading user by email");
MemberVO member = memberDAO.memberLoginByEmail(email); // 이메일을 기준으로 사용자 정보 조회
if (member == null) {
}
// 사용자 정보를 UserDetails 객체로 매핑
UserDetails userDetails = User.builder()
.username(member.getMember_name()) // username 대신 email 사용
.password(member.getMember_password())
.roles(member.getMember_role())
.build();
return userDetails;
}
//내가 구현한 코드2 (@Override)
@Override
public UserDetails loadUserByUsername(String member_name) throws UsernameNotFoundException {
MemberVO member = memberDAO.memberLogin(member_name);
if (member == null) {
throw new UsernameNotFoundException("User not found");
}
// 사용자 정보를 UserDetails 객체로 매핑
UserDetails userDetails = User.withUsername(member.getMember_name())
.password(member.getMember_password()) // 여기를 수정 UserDetails에 저장된 이미 인코딩된 비밀번호와 비교하여 사용자를 인증합니다
.roles(member.getMember_role()) // 사용자의 권한 정보 설정
.build();
return userDetails;
}
UserDetails
사용자의 정보를 담는 인터페이스이다.
스프링 시큐리티에서 로그인한 사람의 정보를 다루기 위해 User클래스를 상속받아 이것을 builder()메서드를 활용하여 객체를 생성한다.
오버라이딩 되는 메소드들만 Spring Security에서 알아서 사용하기 때문에 별도로 클래스를 만들지 않고 멤버변수를 추가해서 같이 사용해도 무방하다.
//User는 UserDetails를 구현한 클래스
//내부적으로 get,set이 구현되어있다.
public class User implements UserDetails, CredentialsContainer {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
// ~ Instance fields
// ================================================================================================
private String password;
private final String username;
private final Set<GrantedAuthority> authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;
//UserDatails 인터페이스에 구현되있는 추상 메서드들
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
'Spring' 카테고리의 다른 글
배포흐름 (0) | 2024.05.26 |
---|---|
소켓 / 웹소켓 (0) | 2024.04.07 |
웹에서 핸드폰으로 메세지 보내기 - 2 (0) | 2024.02.05 |
웹에서 핸드폰으로 메세지 보내기 - 1 (0) | 2024.02.05 |
카카오 api 로그인 (0) | 2024.02.04 |