[spring] security-context 스프링 시큐리티 설정 (접근권한, 로그인)
by mini_min[spring] security-context 스프링 시큐리티 설정 (접근권한, 로그인)
✔️ 스프링 시큐리티 설정
: web.xml 에서 환경 설정할 수 있다.
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/root-context.xml
/WEB-INF/spring/security-context.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 스프링 시큐리티 -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- 시큐리티 설정은 웹 사이트 경로에 대한 권한을 설정하는 것이다. auto-config="true" 를 통해 일반적인 웹어플리케이션에 필요한 기본 보안 서비스를 자동으로 설정한다.
<context:component-scan base-package="com.sp.app"/>
<intercept-url pattern="...." access="...." />
위에 패턴은 url pattern 의 권한에 따른 보안을 설정한다.
리소스와 uploads/photo 의 경우, 메인이나 다른 부분에서는 모두가 접근해야하기 때문에 따로 보안을 설정한다.
⭐ hasRole : 하나의 권한을 설정
⭐ hasAnyRole : 하나 이상의 권한을 설정
⭐ permitAll : 모두 통과시킨다.
⭐ denyAll : 모두 거절한다.
⭐ isAuthenticated() : 인증한 사용자인지 확인한다. (isAnonymous() : 익명 사용자인지 확인한다.)
<!-- 권한이 없는 페이지를 접근할 경우 접근 불가 메시지 출력 -->
<http auto-config="true">
<!-- spring 4.x때 추가된 옵션으로 ssl을 사용하지 않을 경우 csrf는 disalbed=true로 설정. -->
<csrf disabled="true"/>
<!-- 모든 접근자 접근 허용-->
<intercept-url pattern="/" access="permitAll"/>
<intercept-url pattern="/index.jsp" access="permitAll"/>
<intercept-url pattern="/member/login" access="permitAll"/>
<intercept-url pattern="/member/member" access="permitAll"/>
<intercept-url pattern="/member/userIdCheck" access="permitAll"/>
<intercept-url pattern="/member/complete" access="permitAll"/>
<intercept-url pattern="/member/pwdFind" access="permitAll"/>
<intercept-url pattern="/member/expired" access="permitAll"/>
<intercept-url pattern="/resources/**" access="permitAll"/>
<intercept-url pattern="/uploads/photo/**" access="permitAll"/>
<!-- 관리자 페이지 -->
<intercept-url pattern="/admin" access="hasAnyRole('ROLE_ADMIN', 'ROLE_EMP')"/>
<!-- 모든 페이지 -->
<intercept-url pattern="/**" access="hasAnyRole('ROLE_ADMIN', 'ROLE_EMP', 'ROLE_USER')"/>
✔️ 로그인 페이지 설정하기
: <form-login .... />
로그인 페이지를 설정한다.
<form-login login-page="/member/login"
login-processing-url="/member/login"
username-parameter="userId"
password-parameter="userPwd"
authentication-failure-url="/member/login?login_error"
default-target-url="/"
always-use-default-target="false"
authentication-success-handler-ref="loginSuccessHandler"
authentication-failure-handler-ref="loginFailureHandler"/>
<beans:bean id="loginSuccessHandler" class="com.sp.app.security.LoginSuccessHandler">
<beans:property name="defaultUrl" value="/"/>
</beans:bean>
<beans:bean id="loginFailureHandler" class="com.sp.app.security.LoginFailureHandler">
<beans:property name="defaultFailureUrl" value="/member/login?login_error"/>
</beans:bean>
⭐ login-page : 로그인 페이지. 세션 인증이 없는 경우 호출될 URL 이다.
⭐ login-processing-url : 로그인 처리를 호출할 때 사용할 URL 이다.
⭐ username-parameter : 로그인 페이지에서 사용될 사용자 계정 파라미터명이다.
⭐ password-parameter : 로그인 페이지에서 사용될 사용자 암호 파라미텨명이다.
- authentication-failure-url : 로그인 실패시 호출할 URL
- default-target-url : 로그인 후 호출될 URL
- always-use-default-target : 로그인 후 디폴트 타겟 url 을 호출할지 결정하는 여부!
- authentication-success-handler-ref : 로그인 후 호출될 핸들러이다. 직접 후처리 작업할 경우 사용
- authentication-failure-handler-ref : 로그인 실패 후 호출될 핸들러이다. 직접 후처리 작업할 경우 사용
✔️ 로그아웃 설정하기
: <logout .... />
로그아웃을 설정한다.
<logout logout-url="/member/logout"
invalidate-session="true"
logout-success-url="/"/>
⭐ logout-url : 로그아웃 URL
⭐ invalidate-session : 세션 삭제 여부
⭐ logout-success-url : 로그아웃 후 호출될 URL 이다.
✔️ 접근 권한 없는 경우 & 접속 제한
<!-- 접근 권한 없는 경우 -->
<access-denied-handler error-page="/member/noAuthorized"/>
<!-- 동일 아이디로 한명만 로그인 했을 경우. 기존 세션 만료하기 -->
<session-management>
<concurrency-control max-sessions="1" expired-url="/member/expired"/>
</session-management>
✔️ 시큐리티 데이터베이스 인증 설정
: 사용자 정보를 데이터베이스에서 조회하는 인증제공자 설정이다!
<jdbc-user-service > 엘리먼트를 사용한다. (userId 에 따라 사용자 정보를 DB에서 조회한다.)
⭐ users-by-username-query : 로그인 페이지에서 사용자가 제공한 계정을 "?" 변수에 받아 사용자의 username, password, enabled 를 검색한다. 해당 쿼리에서는 반드시 username, password, enabled 컬럼을 리턴 받아야한다. 컬럼 이름이 다르면! 별칭을 사용한다.
⭐ authorities-by-username-query : 사용자가 제공한 계정을 "?" 변수에 받아 username, authority 를 검색한다. 마찬가지로 컬럼 이름이 다르면! 별칭을 사용한다.
<jdbc-user-service data-source-ref="dataSource"
id="userService"
users-by-username-query="SELECT userId AS username, userPwd AS password, enabled FROM member1 WHERE userId = ?"
authorities-by-username-query="SELECT userId AS username, authority FROM memberAuthority WHERE userId= ?"/>
✔️ 암호화
: SHA 알고리즘은 미국 NSA 에서 고안한 암호화 알고리즘이다. 스프링은 기본적으로 SHA256 알고리즘을 사용한다.
📓 스프링 시큐리티 암호화 클래스 종류
: bcryptEncoder
= 스프링 시큐리티에서 기본적으로 사용하는 암호화 방식으로 암호화가 될 때 마다 새로운 값을 생성한다. 임의적 값을 추가해서 암호화 하지 않아도 된다.
⭐환경설정
<authentication-manager>
<authentication-provider user-service-ref="userService">
<password-encoder ref="bcryptEncoder"/>
</authentication-provider>
</authentication-manager>
⭐ 암호화 설정 - 패스워드를 인코딩하는 빈을 설정한다.
<!-- bcrypt : 패스워드 암호화에 특화된 password hashing function -->
<beans:bean id="bcryptEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></beans:bean>
나중에 @Autowired PasswordEncoder passwordEncoder ;
를 오토와이드 하여 암호화 시킨다. (ServiceImpl 에서)
@Autowired
private BCryptPasswordEncoder bcrypt;
//패스워드 암호화
String encPassword = bcrypt.encode(dto.getUserPwd());
dto.setUserPwd(encPassword);
//패스워드 비교
return bcrypt.matches(userPwd, dto.getUserPwd());
✔️ LoginSuccessHandler
: SavedRequestAwareAuthenticationSuccessHandler 클래스를 상속받는다.
빈객체에 있는 value 값을 가져와서 디폴트url set 에서 받는다!
(빈 프로퍼티 값 가져옴 -> 로그인석세스핸들러 set에 저장)
1) 로그인시, 로그인 날짜를 변경한다. 최근 로그인 날짜는 SYSDATE, failure_cnt (비밀번호 틀린횟수) = 0
2) 세션에 로그인 유저 정보를 저장
3) 로그인 Id 로 readMember 한다.
4) 그래서 얻은 로그인 정보를 세션에 저장한다.
5) if 패스워드 변경날짜가 90일 이상인 경우 updatePwd 페이지로 연결시킨다.
6) 해당되지 않으면 리다이렉트 시킨다.
: resultRedirectStrategy 는 리다이렉트 설정이다.
1) 만약, 로그인이 되지 않은 상태에서 권한이 필요한 페이지에 접근한 경우 리다이렉트 한다.
public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Autowired
private MemberService service;
private String defaultUrl;
private RequestCache requestCache = new HttpSessionRequestCache();
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException {
// 로그인 날짜 변경
try {
//authentication.getName() : 로그인 아이디
service.updateLastLogin(authentication.getName());
} catch (Exception e) {
}
//세션에 로그인 유저 정보 저장
HttpSession session = request.getSession();
Member member = service.readMember(authentication.getName());
// 로그인 정보를 세션에 저장
SessionInfo info = new SessionInfo();
info.setUserName(member.getUserName());
info.setUserId(member.getUserId());
info.setMemberIdx(member.getMemberIdx());
info.setMembership(member.getMembership());
session.setAttribute("member", info);
//패스워드 변경이 90일이 지난 경우 패스워드 변경 창으로 이동
try {
Date endDate = new Date();
long gap;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date modifyDate = sdf.parse(member.getModify_date());
gap = (endDate.getTime() - modifyDate.getTime()) / (24*60*60*1000);
if(gap >= 90) {
String targetUrl = "/member/updatePwd";
redirectStrategy.sendRedirect(request, response, targetUrl);
return;
}
} catch (Exception e) {
}
//redirect 설정
resultRedirectStrategy(request, response, authentication);
}
public void resultRedirectStrategy(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws ServletException, IOException {
SavedRequest savedRequest = requestCache.getRequest(request, response);
if(savedRequest != null) {
//로그인이 되지 않은 상태에서 권한이 필요한 페이지에 접근한 경우
String targetUrl = savedRequest.getRedirectUrl();
redirectStrategy.sendRedirect(request, response, targetUrl);
} else {
//직접 로그인 주소를 클릭한 경우
redirectStrategy.sendRedirect(request, response, defaultUrl);
}
}
public String getDefaultUrl() {
return defaultUrl;
}
public void setDefaultUrl(String defaultUrl) {
this.defaultUrl = defaultUrl;
}
}
✔️ LoginFailureHandler
: AuthenticationFailureHandler 구현
1) InternalAuthenticationServiceException : 존재하지 않는 아이디
2) DisabledException : 인증이 거부되었을 때 예외처리
public class LoginFailureHandler implements AuthenticationFailureHandler {
@Autowired
private MemberService service;
private String defaultFailureUrl;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
String userId = request.getParameter("userId");
String s = "아이디 또는 패스워드가 일치하지 않습니다.";
try {
if(exception instanceof BadCredentialsException) {
// 패스워드가 일치하지 않는 경우
//실패횟수 누적
service.updateFailureCount(userId);
int count = service.checkFailureCount(userId);
if(count >= 5) {
//계정 비활성화
Map<String, Object> map = new HashMap<String, Object>();
map.put("enabled", 0);
map.put("userId", userId);
service.updateMemberEnabled(map);
//계정 비활성화 상태 저장하기
Member dto = new Member();
dto.setUserId(userId);
dto.setRegisterId(userId);
dto.setStateCode(1); //패스워드 틀린사람 (계정 잠금) = 1
dto.setMemo("패스워드 5회 이상 오류!!");
service.insertMemberState(dto);
}
} else if(exception instanceof InternalAuthenticationServiceException) {
//존재하지 않는 아이디
} else if(exception instanceof DisabledException ) {
//인증 거부 : enabled=0
s = "계정이 비활성화 되어 있습니다. 관리자에게 문의하세요.";
}
} catch (Exception e) {
// TODO: handle exception
}
request.setAttribute("message", s);
request.getRequestDispatcher(defaultFailureUrl).forward(request, response);
}
public void setDefaultFailureUrl(String defaultFailureUrl) {
this.defaultFailureUrl = defaultFailureUrl;
}
}
'Spring' 카테고리의 다른 글
[Spring] 스프링 PDF, 엑셀 다운로드 기능 구현 (1) | 2022.12.18 |
---|---|
[스프링부트] 스프링 부트 주의사항 (1) (0) | 2022.12.09 |
[spring] 쪽지보내기 , 메일 보내기 Controller,ServiceImpl (0) | 2022.12.05 |
[spring] 주간 매출!!!!! 차트 출력하기 (echart) (0) | 2022.12.01 |
[spring] XML 파싱, JSON 파싱 ⭐⭐ (1) | 2022.11.30 |
블로그의 정보
개발자 미니민의 개발로그
mini_min