스프링부트 스프링시큐리티 연동하기(2) UserDetailsService/AuthenticationProvider
DB에서 유저 정보를 직접 가져오는 인터페이스를 스프링 시큐리티에서 제공하는 UserDetailsService 인터페이스로 구현 하겠다.
1. UserDetailsService 상속 받은 SecurityService.java 인터페이스를 작성한다.
(SecurityService.java)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
package com.devmk.test.security.service;
import java.util.Collection;
import com.devmk.test.vo.Member;
public interface SecurityService extends UserDetailsService {
// 시큐리티 사용자 인증
UserDetails loadUserByUsername(String id);
// 중복아이디 체크
Member getSelectMeberInfo(String id) throws Exception;
//회원가입
int setInsertMember(Member member)throws Exception;
// 비밀번호 틀린 횟수 증가
int setUpdatePasswordLockCnt(String id) throws Exception;
// 비밀번호 틀린 횟수 초기화
int setUpdatePasswordLockCntReset(String id) throws Exception;
}
|
2. SecurityService 인터페이스 상속받은 UserServiceImpl 클래스를 작성한다.
(UserServiceImpl.java)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
package com.devmk.test.security.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.stereotype.Service;
import com.devmk.test.vo.Member;
@Service
public class UserServiceImpl implements SecurityService {
@Autowired
LoginMapper loingMapper;
@Override
public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException {
Member member = loingMapper.getSelectMeberInfo(id);
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
if(member != null) {
authorities.add(new SimpleGrantedAuthority(member.getUserRole()));
member.setAuthorities(authorities);
}
return member;
}
@Override
public int setInsertMember(Member member) throws Exception{
return loingMapper.setInsertMember(member);
}
@Override
public Member getSelectMeberInfo(String id) throws Exception{
return loingMapper.getSelectMeberInfo(id);
}
@Override
public int setUpdatePasswordLockCnt(String id) throws Exception{
return loingMapper.setUpdatePasswordLockCnt(id);
}
@Override
public int setUpdatePasswordLockCntReset(String id) throws Exception{
return loingMapper.setUpdatePasswordLockCntReset(id);
}
}
|
위의 소스에서 가장 중요한 메소드가 있다. loadUserByUsername 메소드를 반드시 구현해야 한다.
이 메소드는 UserDetailsService에 정의된 메소드로 실제 Spring security에서 User 정보를 읽을 때 사용된다.
User를 읽어왔으면 권한을 부여한다. 회원가입할때 권한을 스트링으로 저장해야하며, 권한은 MyBatis를 통해 String을 가져왔으므로
GrantedAuthority 인터페이스에 맞게 SimpleGrantedAuthority로 변환해서 Member.java vo에 있는 authorities에 setter를 한다.
그러면 로그인한 유저에게 권한이 부여된다.
3. 커스텀 로그인을 위한 AuthenticationProvider 구현
간단하게 스프링시큐리티를 구현할때 위에 작성한 서비스의 인증 결과로 로그인을 할 수 있지만
로그인 성공/실패 유무에 따라 수행하는 로직을 만들기 위해서 AuthenticationProvider 인터페이스는 구현하려고 한다.
보통 스프링 시큐리티로 커스텀 로그인을 작성할때 사용 하는 인터페이스다.
(AuthProvider.java)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.stereotype.Component;
import com.devmk.test.vo.Member;
import lombok.extern.slf4j.Slf4j;
@Component
public class AuthProvider implements AuthenticationProvider{
private static final Logger logger = LoggerFactory.getLogger(AuthSuccessHandler.class);
@Autowired
SecurityService securityService;
//로그인 버튼을 누를 경우
//첫번째 실행
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String id = authentication.getName();
String password = authentication.getCredentials().toString();
return authenticate(id, password);
}
//두번쨰 실행
private Authentication authenticate(String id, String password) throws AuthenticationException{
List<GrantedAuthority> grantedAuthorityList = new ArrayList<GrantedAuthority>();
Member member = new Member();
member = (Member)securityService.loadUserByUsername(id);
if ( member == null ){
logger.info("사용자 정보가 없습니다.");
throw new UsernameNotFoundException(id);
}else if(member != null && !member.getPassword().equals(password) ) {
logger.info("비밀번호가 틀렸습니다.");
throw new BadCredentialsException(id);
}
grantedAuthorityList.add(new SimpleGrantedAuthority(member.getUserRole()));
return new MyAuthentication(id, password, grantedAuthorityList, member);
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
|
추후 작성하겠지만 WebSecurityConfigurerAdapter 를 상속받은 스프링 시큐리를 설정하는 SecurityConfig.java 내용안에
.jsp에서 아이디,비밀번호의 input name을 설정 해 줄 수 있다. 그것이 authenticate 생성자가 받는
id, password 변수명이다.
로그인이 실패하면 AuthenticationFailureHandler 상속받아 로그인 실패 로직을 구현 한다.
(AuthFailureHandler.java)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
import java.io.IOException;
import javax.servlet.ServletException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.stereotype.Component;
import com.devmk.test.vo.LoginLog;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* 로그인 실패 핸들러
*/
@Component
public class AuthFailureHandler implements AuthenticationFailureHandler {
@Autowired
SecurityService securityService;
private static final Logger logger = LoggerFactory.getLogger(AuthFailureHandler.class);
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
String ip = CommonUtil.getClientIp(request);
logger.info(" :::::::::::::::::::::::::::: 로그인실패 :::::::::::::::::::::::: ");
LoginLog loginLog = new LoginLog();
String id = "";
String msg = "";
try {
id = exception.getMessage();
if(securityService.getSelectMeberInfo(id) != null) {
securityService.setUpdatePasswordLockCnt(id);
loginLog.setLoginIp(ip);
loginLog.setStatus("FAILD");
securityService.setInsertLoginLog(loginLog);
msg="비밀번호가 틀렸습니다.";
}else {
msg="아이디가 없습니다.";
}
} catch (Exception e) {
e.printStackTrace();
}
response.sendRedirect("/login?msg="+msg);
}
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5; text-decoration:none">Colored by Color Scripter
|
보통 로그, 비밀번호가 틀렸거나 아이디가 없을때의 로직을 작성할 수 있다.
(AuthSuccessHandler.java)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
import java.io.IOException;
import javax.servlet.ServletException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import com.devmk.test.vo.LoginLog;
/*
* 스프링 시큐리티를 사용하며, 로그인 성공시 부가 작업을 하려면,
*별도로 authenticationSuccessHandler를 지정하지 않으면 기본적으로
*/
@Component
public class AuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler{
private static final Logger logger = LoggerFactory.getLogger(AuthSuccessHandler.class);
@Autowired
SecurityService securityService;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException{
String ip = CommonUtil.getClientIp(request);
logger.info("::::::::::::::::::::::::::::: 로그인 성공 ::::::::::::::::::::::::::::: ");
LoginLog loginLog = new LoginLog();
String id = "";
try {
id = authentication.getName().toString();
securityService.setUpdatePasswordLockCntReset(id);
loginLog.setLoginIp(ip);
loginLog.setStatus("SUCCESS");
securityService.setInsertLoginLog(loginLog);
} catch (Exception e) {
e.printStackTrace();
}
super.setDefaultTargetUrl("/home");
super.onAuthenticationSuccess(request, response, authentication);
}
}
|
로그인이 성공하면 SimpleUrlAuthenticationSuccessHandler을 상속받아 성공 로직을 구현 한다.
보통 로그인 로그를 남기는 로직을 작성한다.
loadUserByUsername 에서 리턴받은 member vo가 null이면 UsernameNotFoundException타고
AuthenticationFailureHandler의 구현체가 실행되고
사용자 정보가 있으면 인증 후 객체를 리턴한다.
인증 후 객체를 생성하는 MyAuthentication클래스는
스프링 시큐리티에서 제공하는 UsernamePasswordAuthenticationToken 인터페이스를
상속받은 클래스이다. 인증이 완료된 인증 후 객체를 리턴 후
SimpleUrlAuthenticationSuccessHandler의 구현체가 실행한다.
(MyAuthentication.java)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
import java.util.List;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import com.devmk.test.vo.Member;
import lombok.Data;
//현재 로그인한 사용자 객체 저장 DTO
@Data
public class MyAuthentication extends UsernamePasswordAuthenticationToken{
private static final long serialVersionUID = 1L;
Member member;
public MyAuthentication(String id, String password, List<GrantedAuthority> grantedAuthorityList, Member member) {
super(id, password, grantedAuthorityList);
this.member = member;
}
}
|
다음 포스팅에는 WebSecurityConfigurerAdapter 를 상속받은 스프링 시큐리를 설정하는 SecurityConfig.java을 작성 과
로그인 컨틀롤러 LoginController를 작성하겠다.