728x90

 

1. package org.scoula.security.account.domain;

AuthVO

@Data
public class AuthVO implements GrantedAuthority {
    private String username;
    private String auth;

    @Override
    public String getAuthority() {
        return auth;
    }
}

 

  • GrantedAuthority 인터페이스 구현
    • getAuthority() 로 권한 정보 추출

 

CustomUser

@Getter @Setter
public class CustomUser extends User {
    //실질적인 사용자 데이터
    private MemberVO member;

    public CustomUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
    }

    public CustomUser(MemberVO vo) {
        super(vo.getUsername(), vo.getPassword(), vo.getAuthList());
        this.member = vo;
    }
}
  • CustomUser(MemberVO vo)
    • MemberVO 를 User로 변환

 


 

 

2.  package org.scoula.security.account.dto;

 

LoginDTO

public class LoginDTO {
    public String username;
    public String password;

    public static LoginDTO of(HttpServletRequest request) {
        ObjectMapper om = new ObjectMapper();
        try {
            return om.readValue(request.getInputStream(), LoginDTO.class);
        } catch (Exception e) {
            e.printStackTrace();
            throw new BadCredentialsException("username or password is incorrect");
        }
    }
}
  • request body에서 username, password 역직렬화(JSON 문자열 → 자바 객체로 변환하는 것)
  • username이나 password가 틀리면 여기서 Exception 발생
  • JwtUsernamePasswordAuthenticationFilter의 attemptAuthentication에서 사용

 


 

 

3.  package org.scoula.security.account.mapper;

 

UserDetailsMapper

public interface UserDetailsMapper {
    public MemberVO get(String username);
}

 

  • tbl_member 테이블과 tbl_member_auth 테이블을 Join 처리
  • get()은 CustomUserDetailsService에서 username으로 MemberVo를 조회하는데 사용
MemberVO vo = mapper.get(username);

 

 


 

 

4. package org.scoula.security.filter;

 

 

 

JwtUsernamePasswordAuthenticationFilter

public class JwtUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    public JwtUsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager, LoginSuccessHandler loginSuccessHandler, LoginFailureHandler loginFailureHandler) {
        super(authenticationManager);

        setFilterProcessesUrl("/api/auth/login");
        setAuthenticationSuccessHandler(loginSuccessHandler);
        setAuthenticationFailureHandler(loginFailureHandler);
    }

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

        LoginDTO login = LoginDTO.of(request);

        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(login.getUsername(), login.getPassword());

        return getAuthenticationManager().authenticate(authenticationToken);
    }
}

 

  • 로그인 요청 경로 지정 : /api/auth/login
  • 로그인 성공 처리기, 로그인 실패 처리기 등록

 

  • attemptAuthentication() 메서드
    • 등록한 인증 url로 요청이 오면 호출
    • 요청에서 username, password 부분을 추출
    • UsernamePasswordAuthenticationToken 구성
    • AuthenticationManager에게 인증 요청
    • 인증경과 Authentication을 리턴

필터 구조도


 

 

5. package org.scoula.security.handler, service, util;

 

 


🎁 전체 코드 => 깃허브 업로드 예정

 

 

 

🎁 패키지 전체 요약

security 패키지 전체 주요 설명

 


로그인 처리 전체 흐름

 

🐻‍❄️ 프론트엔드

stores/auth.js 에서 백엔드 서버 '/api/auth/login’  경로로 POST 요청 전송
const login = async (member) => {
    const { data } = await axios.post('/api/auth/login', member);
    state.value = { ...data };
    localStorage.setItem('auth', JSON.stringify(state.value));
  };

 

 

🐻 백엔드

1️⃣ AuthenticationErrorFilter

필터 체인 초입에 위치 / 토큰이 만료되었거나 유효하지 않은 경우 예외 발생, 예외 응답 반환

 

2️⃣ JwtAuthenticationFilter

요청 헤더에 토큰이 존재하는 지 검사 / 토큰에서 username 추출

 

 

3️⃣ JwtUsernamePasswordAuthenticationFilter

약속된 login url 요청 절차 수행

  • 요청이 JwtUsernamePasswordAuthenticationFilter.attemptAuthentication()로 전달됨.
  • LoginDTO.of(request)를 통해 요청의 username, password를 꺼냄.
  • 이 정보로 UsernamePasswordAuthenticationToken을 생성하여 AuthenticationManager에게 전달 → 로그인 인증 시도.
  • 인증 성공 → LoginSuccessHandler 동작
  • 인증 실패 → LoginFailureHandler 동작
@Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {

        // 요청 BODY의 JSON에서 username, password  LoginDTO
        LoginDTO login = LoginDTO.of(request);

        // 인증 토큰(UsernamePasswordAuthenticationToken) 구성
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(login.getUsername(), login.getPassword());

        // AuthenticationManager에게 인증 요청
        return getAuthenticationManager().authenticate(authenticationToken);
    }

 

 

4️⃣-1) LoginSuccessHandler

public class LoginSuccessHandler implements AuthenticationSuccessHandler {
    private final JwtProcessor jwtProcessor;

    private AuthResultDTO makeAuthResult(CustomUser user) {
        String username = user.getUsername();
        // 토큰 생성
        String token = jwtProcessor.generateToken(username);
        // 토큰 + 사용자 기본 정보 (사용자명, ...)를 묶어서 AuthResultDTO 구성
        return new AuthResultDTO(token, UserInfoDTO.of(user.getMember()));
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        // 인증 결과 Principal
        CustomUser user = (CustomUser) authentication.getPrincipal();

        // 인증 성공 결과를 JSON으로 직접 응답
        AuthResultDTO result = makeAuthResult(user);
        JsonResponse.send(response, result);
    }

}

 

public static <T> void send(HttpServletResponse response, T result) throws IOException {
        ObjectMapper om = new ObjectMapper();
        response.setContentType("application/json;charset=UTF-8");
        Writer out = response.getWriter();
        out.write(om.writeValueAsString(result));
        out.flush();
    }

 

 

4️⃣-2) LoginFailureHandler

@Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException {

        JsonResponse.sendError(response, HttpStatus.UNAUTHORIZED, "사용자 ID 또는 비밀번호가 일치하지 않습니다.");
    }

 

public static void sendError(HttpServletResponse response, HttpStatus status, String message) throws IOException {
        response.setStatus(status.value());
        response.setContentType("application/json;charset=UTF-8");
        Writer out = response.getWriter();
        out.write(message);
        out.flush();
    }

 

728x90

+ Recent posts