-
초보자를 위한 스프링 시큐리티 커스텀 구현하기(UserDetails, UserDetailsService, AuthenticationProvider customizing 하기)Study/Spring 2022. 12. 21. 16:01반응형
앞서 스프링시큐리티 인증절차에 대해서 정리해 봤다.
혹시 못보신분들은 아래 링크로 가시면 될듯
https://cbn1218.tistory.com/10
로그인form에서 "/login"으로 요청을 보내어 스프링시큐리티에 이미 구현한 기능을 사용하여 인증하면 좋겠지만 실제프로젝트에서는 인증부분을 직접 customizing 하여 인증절차를 진행 하는게 많기 때문에
연습삼아 아주간단한 형태로 customizing 하여 인증절차를 진행해 보겠다.
*환경
IDE: 전자정부프레임워크기반
DB: mariaDB, DB연결은 mybatis를 사용
1.설명
구현하기전 간단하게 UserDetails, UserDetailsService, AuthenticationProvider에 대해 알아 보자
-UserDetails: Spring Security에서 사용자의 정보를 담는 인터페이스이다.
-UserDetailsService: Spring Security에서 유저의 정보를 가져오는 인터페이스이다.
-AuthenticationProvider: DB에서 가져온 정보와 input 된 정보가 비교되서 체크되는 로직이 포함되어있는 인터페이스이다.
로그인form에서 /login 로 post로 보내면 시큐리티 인증 절차를 거쳐서 로그인이 되는데,
인증절차를 관여하는 UserDetails, UserDetailsService, AuthenticationProvider 를 상속받아 customizing 할 예정이다.특이한점은 나는 마이바티스를 이용하여 mapper를 DAO를 대신해서 쓸 예정이다.
2.구현
(1) DB 구현
CREATE TABLE CUSTOMMEMBER( ID VARCHAR(30) NOT NULL PRIMARY KEY, PASSWORD VARCHAR(30) NOT NULL, NAME VARCHAR(30) NOT NULL, AUTHORITY VARCHAR(30) DEFAULT 'ROLE_MEMBER', ENABLED INT DEFAULT '1' )DEFAULT CHARSET=UTF8
(2) CustomUserDetails 구현
보통 VO가 DB에서 가져온 정보를 담는 그릇같은역할을 하는데, 여기서 VO를 대신하여 UserDetails을 상속 받은 CustomUserDetails 클래스를 만들어 오버라이딩 메서드에 담아야될 정보들을 매칭 시킨다.
package ddi.main.security; import java.util.ArrayList; import java.util.Collection; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; public class CustomUserDetails implements UserDetails{ private String ID; private String PASSWORD; private String NAME; private String AUTHORITY; private boolean ENABLED; @Override public Collection<? extends GrantedAuthority> getAuthorities() { ArrayList<GrantedAuthority> auth = new ArrayList<GrantedAuthority>(); auth.add(new SimpleGrantedAuthority(AUTHORITY)); return auth; } @Override public String getPassword() { return PASSWORD; } @Override public String getUsername() { return ID; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return ENABLED; } public String getNAME() { return NAME; } public void setNAME(String name) { NAME = name; } }
(3)mapper.xml 구현
여기서 주의해야 할점은 resultType을 CustomUserDetails로 설정 해야한다.
그래야 DB에서 받은 정보를 CustomUserDetails클래스가 받을수 있기 때문이다.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="ddi.main.mapper.memberMapper"> <select id="loginID" resultType="ddi.main.security.CustomUserDetails"> select * from CUSTOMMEMBER where ID=#{username} </select> </mapper>
(4)memberMapper 구현
mapper.xml 을 작성 했다면 쿼리를 불러오는 Mapper인터페이스를 작성하고 주의할점은 mapper.xml 의 select id와
메서드명이 일치해야 하며, return타입을 CustomUserDetails로 해줘야 한다.
package ddi.main.mapper; import ddi.main.security.CustomUserDetails; import egovframework.rte.psl.dataaccess.mapper.Mapper; @Mapper("memberMapper") public interface memberMapper { public CustomUserDetails loginID(String ID); }
(5)CustomUserDetailsService 구현
UserDetailsService를 상속받아 구현 한것인데
게시판 CRUD를 할때 mapper->service->serviceImpl->contllor 이런 절차로 진행하는데
저기서 serviceImpl부분을 대체해서 구현한 부분이 CustomUserDetailsService라고 생각하는게 쉬울거 같다.
아래코드 보면 mapper를 선언해주고 DB에서 사용자정보를 불러오는 메서드 부분을 선언해주는데 serviceImpl과 역할이 비슷한거 같다.
package ddi.main.security; 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 ddi.main.mapper.memberMapper; public class CustomUserDetailsService implements UserDetailsService{ @Autowired private memberMapper mapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { CustomUserDetails user = mapper.loginID(username); if(username==null) { throw new UsernameNotFoundException(username); } System.out.println("CustomUserDetailsService 들어왔다!!!!!!!!!!!!!!!!"); return user; } }
(6)CustomAuthenticationProvider 구현
CustomAuthenticationProvider는 로그인요청한 유져와 DB의 유져정보를 비교하는 실질적으로 인증을 담당하는 부분이다.
유져정보 비교하고 맞으면 Authentication 객체로 반환하는데 하단에 보면
new UsernamePasswordAuthenticationToken를 생성하여 유져정보를 담는데 UsernamePasswordAuthenticationToken은 Authentication 인터페이스의 구현체이다.
package ddi.main.security; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetailsService; public class CustomAuthenticationProvider implements AuthenticationProvider{ @Autowired private UserDetailsService userDetailsService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username=(String) authentication.getPrincipal(); String password=(String) authentication.getCredentials(); CustomUserDetails user = (CustomUserDetails)userDetailsService.loadUserByUsername(username); if(!matchPassword(password, user.getPassword())) { throw new BadCredentialsException("비밀번호 안맞음!!!!!"); } if(!user.isEnabled()) { throw new BadCredentialsException("계정활성화 안되있음!!!!!"); } return new UsernamePasswordAuthenticationToken(username,password,user.getAuthorities()); } @Override public boolean supports(Class<?> authentication) { return true; } private boolean matchPassword(String loginPwd, String password) { return loginPwd.equals(password); } }
(7)context-security 설정
나는 비밀번호 암호화는 따로 설정하지 않았기 때문에 customNoPasswordEncoder 를 설정했고,
설정시 빼도 상관없는 부분이다.
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd"> <beans:bean id="loginSuccess" class="ddi.main.security.CustomLoginSuccessHandler" /> <beans:bean id="customNoPasswordEncoder" class="ddi.main.security.CustomNoPasswordEncoder"/> <beans:bean id="customUserDetailsService" class="ddi.main.security.CustomUserDetailsService"/> <beans:bean id="customAuthenticationProvider" class="ddi.main.security.CustomAuthenticationProvider"/> <http> <intercept-url pattern="/login.do" access="permitAll" /> <intercept-url pattern="/admin.do" access="hasRole('ROLE_ADMIN')" /> <intercept-url pattern="/main.do" access="hasRole('ROLE_MEMBER')" /> <form-login login-page='/login.do' authentication-success-handler-ref="loginSuccess" /> </http> <authentication-manager> <authentication-provider ref="customAuthenticationProvider" /> <authentication-provider user-service-ref="customUserDetailsService"> <password-encoder ref="customNoPasswordEncoder"/> </authentication-provider> </authentication-manager> </beans:beans>
반응형'Study > Spring' 카테고리의 다른 글
초보자를 위한 Ajax통신으로 메뉴불러오기_(1) (0) 2023.03.07 초보자를 위한 AJAX통신으로 회원가입기능 간단히 만들기 (0) 2023.01.03 AJAX 너는 뭐니? 도대체!!! (0) 2022.12.22 스프링 시큐리티 인증 절차 (0) 2022.12.21 전자정부프레임워크 Maven 설정파일(Spring Maven,pom.xml,web.xml, dispatcher-servlet.xml .... ) (0) 2022.12.06