DB연동은 되어있는 상태에서 시작합니다.
- 시큐리티 로그인기능
- 자동로그인 기능
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>${security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>${security.version}</version>
</dependency>
pom.xml에 추가해줍니다.
<properties>
<spring.maven.artifact.version>4.3.25.RELEASE</spring.maven.artifact.version>
<egovframework.rte.version>3.10.0</egovframework.rte.version>
<security.version>4.2.3.RELEASE</security.version>
</properties>
pom.xml 상단에 <properties>태그안에 <security.version>4.2.3.RELEASE</security.version>를 추가해줍니다.
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:egovframework/spring/context-*.xml</param-value>
</context-param>
spring폴더 안에 context- 로 시작하는 xml을 읽을수 있게 수정해줍니다.
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan base-package="egovframework.example.security"/>
<http auto-config="true" use-expressions="true">
<!-- 모든 url 패턴에 ROLE_USER의 권한을 가지고 있을때만 접근가능 -->
<!-- <intercept-url pattern="/**" access="ROLE_USER" /> -->
<intercept-url pattern="/userPage/**" access="hasRole('ROLE_USER')"/>
<intercept-url pattern="/**" access="permitAll"/>
<access-denied-handler ref="userDeniedHandler" />
<form-login
username-parameter="loginId"
password-parameter="loginPwd"
login-processing-url="/login"
login-page="/loginPage.do"
default-target-url="/"
/>
<!-- 로그아웃할 url 및 로그아웃성공시 이동할 url -->
<logout
logout-url="/logout"
logout-success-url="/"
invalidate-session="true"
delete-cookies="true" />
<remember-me data-source-ref="dataSource"
remember-me-parameter="remember-me"
token-validity-seconds="604800"/>
<!-- <remember-me services-ref="" /> -->
</http>
<beans:bean id="userDeniedHandler" class="egovframework.example.security.UserDeniedHandler"></beans:bean>
<authentication-manager>
<authentication-provider ref="userAuthProvider"></authentication-provider>
<authentication-provider user-service-ref="userService"></authentication-provider>
</authentication-manager>
<beans:bean id="userService" class="egovframework.example.sample.service.CustomUserDetailsService"/>
<beans:bean id="userAuthProvider" class="egovframework.example.security.CustomAuthenticationProvider"/>
</beans:beans>
web.xml에서 context- 관련 xml을 읽을수 있게 수정한 폴더에 context-security.xml 를 생성후 코드를 붙여줍니다.
<remember-me data-source-ref="dataSource"
remember-me-parameter="remember-me"
token-validity-seconds="604800"/>
이 코드에서 dataSource는 bean으로 등록된 dataSource id입니다. egov기준 context-datasource.xml
맨위 <context:component-scan base-package="egovframework.example.security"/> 스캔 가능하도록 설정한것 처럼
egovframework.example경로에 security 폴더를 생성합니다.
security 폴더안에 UserDeniedHandler.java와 CustomAuthenticationProvider.java 파일을 생성합니다.
egovframework.example.sample.service 경로에 CustomUserDetailsService.java를 생성합니다.(로그인 관련 db 조회)
package egovframework.example.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;
import egovframework.example.sample.service.UserVO;
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// TODO Auto-generated method stub
System.out.println("authentication : " + authentication);
String loginUserName = String.valueOf(authentication.getPrincipal());
String loginPassword = String.valueOf(authentication.getCredentials());
System.out.println("loginUserName : " + loginUserName);
System.out.println("loginPassword : " + loginPassword);
UserVO user = (UserVO) userDetailsService.loadUserByUsername(loginUserName);
if(!matchPassword(loginPassword, user.getPassword())) {
System.out.println();
throw new BadCredentialsException(loginUserName);
}
if(!user.isEnabled()) {
throw new BadCredentialsException(loginUserName);
}
return new UsernamePasswordAuthenticationToken(loginUserName, loginPassword, user.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
// TODO Auto-generated method stub
return true;
}
private boolean matchPassword(String loginPassword, String password) {
return loginPassword.equals(password);
}
}
package egovframework.example.security;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.access.AccessDeniedHandler;
public class UserDeniedHandler implements AccessDeniedHandler {
private String errorPage = "/WEB-INF/jsp/egovframework/example/sample/errorPage.jsp";
@Override
public void handle(HttpServletRequest req, HttpServletResponse res,
AccessDeniedException ade) throws IOException, ServletException {
// TODO Auto-generated method stub
/*
* req.setAttribute("errMsg",ade.getMessage()); req.getRequestDispatcher(
* "/WEB-INF/jsp/egovframework/example/sample/errorPage.jsp").forward(req, res);
*/
String ajaxHeader = req.getHeader("X-Ajax-call");
String result = "";
res.setStatus(HttpServletResponse.SC_FORBIDDEN);
res.setCharacterEncoding("UTF-8");
// null로 받은 경우는 X-Ajax-call 헤더 변수가 없다는 의미이기 때문에
// ajax가 아닌 일반적인 방법으로 접근했음을 의미한다.
if(ajaxHeader == null) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Object principal = auth.getPrincipal();
req.setAttribute("username", principal);
req.setAttribute(("errormsg"), ade);
req.getRequestDispatcher(errorPage).forward(req, res);
result = "{\"result\" : \"fail\", \"message\" : \"" + ade.getMessage() + "\"}";
System.out.println("result :::: " + result);
}else {
if("true".equals(ajaxHeader)) {
// true로 받았다는 것은 ajax로 접근
result = "{\"result\" : \"fail\", \"message\" : \"" + ade.getMessage() + "\"}";
}else {
// 헤더 변수는 있으나 값이 틀린 경우이므로 헤더값이 틀렸다는 의미로 돌려준다
result = "{\"result\" : \"fail\", \"message\" : \"Access Denied(Header Value Mismatch)\"}";
}
res.getWriter().print(result);
res.getWriter().flush();
}
}
public void setErrorPage(String errorPage) {
if ((errorPage != null) && !errorPage.startsWith("/")) {
throw new IllegalArgumentException("errorPage must begin with '/'");
}
this.errorPage = errorPage;
}
}
package egovframework.example.sample.service;
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 egovframework.example.sample.service.impl.UserMapper;
public class CustomUserDetailsService implements UserDetailsService{
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
// TODO Auto-generated method stub
UserVO user = userMapper.getUserById(userName);
if(user == null) {
throw new UsernameNotFoundException(userName);
}
return user;
}
}
package egovframework.example.sample.service;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
public class UserVO implements UserDetails {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private String userId;
private String userPassword;
private String userName;
private String userAuthority;
//private ArrayList<GrantedAuthority> authorities; 여러 권한?
private boolean userEnabled;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
ArrayList<GrantedAuthority> auth = new ArrayList<GrantedAuthority>();
auth.add(new SimpleGrantedAuthority(userAuthority));
return auth;
}
@Override
public String getPassword() {
// TODO Auto-generated method stub
return userPassword;
}
@Override
public String getUsername() {
// TODO Auto-generated method stub
return userId;
}
@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return userEnabled;
}
public String getName() {
// TODO Auto-generated method stub
return userName;
}
public void setNAME(String name) {
userName = name;
}
}
package egovframework.example.sample.service.impl;
import egovframework.example.sample.service.UserVO;
import egovframework.rte.psl.dataaccess.mapper.Mapper;
@Mapper("userMapper")
public interface UserMapper {
public UserVO getUserById(String userName);
}
CREATE TABLE TB_USER (
USER_ID VARCHAR(100) NOT NULL,
USER_PASSWORD VARCHAR(300) NOT NULL,
USER_NAME VARCHAR(45) NOT NULL,
USER_AUTHORITY VARCHAR(50)DEFAULT 'ROLE_USER' NOT NULL,
USER_ENABLED VARCHAR(1) DEFAULT '1' NOT NULL,
PRIMARY KEY (USER_ID)
);
INSERT INTO TB_USER (USER_ID, USER_PASSWORD, USER_NAME, USER_AUTHORITY, USER_ENABLED)
VALUES('stage011', '1234', 'kyj', 'ROLE_USER', '1');
INSERT INTO TB_USER (USER_ID, USER_PASSWORD, USER_NAME, USER_AUTHORITY, USER_ENABLED)
VALUES('stage018', '1234', 'kyh', 'ROLE_GUEST', '1');
CREATE TABLE persistent_logins (
username varchar(64) not null,
series varchar(64) not null,
token varchar(64) not null,
last_used timestamp not null,
PRIMARY KEY (series)
);
COMMIT;
<?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="egovframework.example.sample.service.impl.UserMapper">
<resultMap id="resultUserVO"
type="egovframework.example.sample.service.UserVO">
<result property="userId" column="USER_ID" />
<result property="userPassword" column="USER_PASSWORD" />
<result property="userName" column="USER_NAME" />
<result property="userAuthority" column="USER_AUTHORITY" />
<result property="userEnabled" column="USER_ENABLED" />
</resultMap>
<select id="getUserById" parameterType="String" resultMap="resultUserVO">
SELECT
USER_ID
, USER_PASSWORD
, USER_NAME
, USER_AUTHORITY
, USER_ENABLED
FROM TB_USER
WHERE USER_ID = #{userId}
</select>
</mapper>
package egovframework.example.sample.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class LoginController {
@RequestMapping(value="/loginPage.do")
public String loginPage() throws Exception{
return "sample/loginPage";
}
@RequestMapping(value="/userPage/userTest.do")
public String userPage() throws Exception{
return "sample/userPage";
}
}
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<jsp:forward page="/main.do"/>
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="ui" uri="http://egovframework.gov/ctl/ui"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<p>현재시간 ${serverTime}</p>
<!-- 로그인중이 아닐 때에만 Login 버튼이 보임 -> taglib ( security/tags ) 때문에 가능 -->
<sec:authorize access="isAnonymous()">
<a href='${pageContext.request.contextPath}/loginPage.do'>Login</a>
</sec:authorize>
<!-- 로그인 중일 경우에만 Logout 버튼이보임 -->
<sec:authorize access="isAuthenticated()">
<form action="${pageContext.request.contextPath}/logout" method="POST">
<input id="logoutBtn" type="submit" value="Logout" />
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}">
</form>
</sec:authorize>
<form action="${pageContext.request.contextPath}/userPage/userTest.do" method="POST">
<input id="logoutBtn" type="submit" value="userPage" />
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}">
</form>
</body>
</html>
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="ui" uri="http://egovframework.gov/ctl/ui"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/login" method="post">
<input type="text" name="loginId" placeholder="ID">
<input type="password" name="loginPwd" placeholder="Password">
<input name="${_csrf.parameterName}" type="hidden" value="${_csrf.token}"/>
<input name="remember-me" type="checkbox" />자동 로그인
<button type="submit">로그인</button>
</form>
</body>
</html>
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="ui" uri="http://egovframework.gov/ctl/ui"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
권한이없다!
${username}
${errormsg}
</body>
</html>
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="ui" uri="http://egovframework.gov/ctl/ui"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
userPage
</body>
</html>
http://localhost:7070/security/loginPage.do로 접속
사용자를 생성한 아이디로 자동 로그인 체크를 하고 로그인을 해본다.
자동로그인을 체크한 후 로그인하면 해당 테이블에 자동으로 INSERT 되며 로그아웃하면 테이블에서 데이터가 삭제된다.
TB_USER 테이블에 권한 컬럼(USER_AUTHORITY) 가 ROLE_USER이면 해당 URL에 접근이 가능하고 아니면
사진과 같이 권한이없다는 내용의 errorPage.jsp 로 이동된다.
참고
https://codevang.tistory.com/274
https://codevang.tistory.com/268
https://velog.io/@hellas4/Security-관련-설정