DOing
[Spring Security] 로그인 로그아웃 - 1 본문
1. pom.xml의 설정
<!-- spring 시큐리티 3개 파일은 동일한버전으로 맞춰야 한다 -->
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<!-- JSP에서 스프링 시큐리티 태그라이브러리를 사용할 수 있도록 -->
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-taglibs -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
2. security-context.xml 생성
스프링 시큐리티는 단독으로 설정할 수 있기때문에 별도로 security-context.xml을 따로 작성하는 것이 좋다.
네임스페이스에서 시큐리티 항목을 체크한다. XML을 이용해서 스프링 시큐리티를 설정할때는 5.0네임스페이스에서 문제가 발생해서 다음과 같이 바꿔준다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
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">
3. web.xml의 설정
스프링 시큐리티가 스프링 MVC에서 사용되기 위해서는 필터를 이용해서 스프링 동작에 관여하도록 설정한다.
<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<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>
스프링 시큐리티xml파일을 찾을 수 있게해준다.
스프링의 서브프로젝트에 스프링 MVC와 스프링 시큐리티가 있는것이라는 것을 명심해야한다.
<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>
4. 접근제한
security-context.xml에서 <security:intercept-url>을 사용한다.
<security:intercept-url>는 특정 URI에 접근할때 인터셉터를 이용해서 접근을 제한시킨다.
<security:http>
<security:intercept-url pattern="/sample/all" access="permitAll"/>
<security:intercept-url pattern="/sample/member" access="hasRole('ROLE_MEMBER')"/>
<security:intercept-url pattern="/sample/admin" access="hasRole('ROLE_ADMIN')"/>
<security:form-login/>
</security:http>
이 방법이 아닌 어노테이션을 사용할 수도 있다.
4-1. 어노테이션을 통한 접근제한
servlet-context.xml에서 어노테이션을 허용시켜줘야한다. 네임스페이스에 시큐리티를 추가하고 다음 코드를 추가한다.
<security:global-method-security pre-post-annotations="enabled" secured-annotations="enabled"/>
그후엔 @PreAuthorize와 @Secured를 사용해서 쓸 수 있다.
5. 로그인 페이지
/sample/member로 접근을 하게되면 다음과 같은 페이지가 나온다.
다음은 spring security에서 기본적으로 제공하는 로그인 페이지이다.
만약 직접 로그인 페이지를 커스텀하고 싶으면 다음과 같이 바꿔주면 된다.
<security:http>
<security:intercept-url pattern="/sample/member" access="hasRole('ROLE_MEMBER')" />
<security:intercept-url pattern="/sample/admin" access="hasRole('ROLE_ADMIN')" />
<security:form-login login-page="/customLogin"/>
</security:http>
6. 특정 계정에 대한 로그인 처리
인증과 권한에 대한 처리는 AuthenticationManager->AuthenticationProvider->UserDetailsService의 과정을 거치게 된다. 때문에 특정 유저를 추가해서 로그인 시키기위해서 다음과 같이 설정할 수 있다.
<security:http>
<security:intercept-url pattern="/sample/member" access="hasRole('ROLE_MEMBER')" />
<security:intercept-url pattern="/sample/admin" access="hasRole('ROLE_ADMIN')" />
<security:form-login login-page="/customLogin"/>
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:user name="member" password="{noop}member" authorities="ROLE_MEMBER"/>
<security:user name="admin" password="{noop}admin" authorities="ROLE_ADMIN"/>
</security:authentication-provider>
</security:authentication-manager>
스프링 시큐리티 5버전 부터는 반드시 PasswordEncoder를 사용해야한다. {noop}를 패스워드 앞에 추가하게되면 패스워드의 인코딩 처리없이 사용할 수 있다. 지금은 임시방편으로 {noop}을 사용해서 패스워드를 지정한다.
7. 로그아웃 처리
로그인과 마찬가지로 로그아웃도 특정한 URI를 지정할 수 있다. 로그아웃시에 세션을 무효화시키는 추가로 설정하였다.
<security:http>
<security:intercept-url pattern="/sample/member" access="hasRole('ROLE_MEMBER')" />
<security:intercept-url pattern="/sample/admin" access="hasRole('ROLE_ADMIN')" />
<security:form-login login-page="/customLogin"/>
<security:form-login login-page="/customLogout" invalidate-session="true"/>
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:user name="member" password="{noop}member" authorities="ROLE_MEMBER"/>
<security:user name="admin" password="{noop}admin" authorities="ROLE_ADMIN"/>
</security:authentication-provider>
</security:authentication-manager>
Controller단에 로그아웃 페이지에 대한 메서드를 처리한다.
@Controller
@Log4j
public class LoginController {
@GetMapping("/customLogout")
public void logoutGET() {
log.info("custom logout");
}
@PostMapping("/customLogout")
public void logoutPost() {
log.info("post custom logout");
}
}
화면단에서의 처리는 다음과 같다.
메인페이지에서는 로그아웃 페이지를 호출시킨다.
<a href="/customLogout">로그아웃</a>
로그아웃 페이지에서는 post방식으로 실제 로그아웃을 처리한다.
<h1> Logout Page </h1>
<form action = "/customLogout" method = "post">
<input type="hidden" name = "{_csrf.parameterName}" value="${_csrf.token}"/>
<button>로그아웃</button>
</form>
이어지는 포스팅 :
2021.04.04 - [spring] - [Spring Security] 로그인 성공, 실패 - 2
======================================
7. UserDetailsService와 UserDetails 구현
스프링 시큐리티에서 사용자의 정보로 username(회원의 아이디) 만을 이용한다.
때문에 username이외에 이메일이나 이름과 같은 정보를 추가하기 위해서는 UserDetailsService를 구현해야한다.
UserDetailsService는 loadUserByUsername()이라는 단하나의 메서드만 가지고 있다.
이 메소드의 반환 타입은 UserDetails로 사용자의 정보와 권한정보를 담는다.
UserDetails는 getAuthorities(), getPassword, getUserName()등 여러 추상메소드를 가지고 있다. 그래서 이것을 직접 구현할것인지 UserDetails 인터페이스를 구현해둔 스프링시큐리티의 하위클래스를 이용할 것인지 판단해야한다. 가장 많이 사용하는 방법은 여러 하위 클래스들 중에서 org.springframework.securtiy.core.userdtails.User 클래스를 상속하는 형태이다.
MemberMapper 타입의 인스턴스를 주입받고 UserDetailService를 구현하는 CustomUserDetailsService를 만들자.
UserDetailsService의 loadUserByUsername()는 UserDetails으로 리턴해야한다고 하였다. 즉, MemberVO의 인스턴스를 UserDetails타입으로 변환해야한다. (memberVO는 내가 따로 만든 회원정보를 담는 자바 클래스이다.)
MemberVO클래스를 직접수정해서 UserDetails인터페이스를 구현하는 방법도 좋지만 가능하면 기존 클래스를 수정하지않고 확장하는 방식이 더 낫다고 생각되서 doamin패키지를 추가해서 CustomUser클래스를 생성하였다.
@Data
public class MemberVO {
private String userid;
private String userpw;
private String userName;
private boolean enabled;
private Date regDate;
private Date updateDate;
private List<AuthVO> authList;
}
public class CustomUser extends User{
private static final long serialVersionUID = 1L;
private MemberVO member;
public CustomUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
// TODO Auto-generated constructor stub
}
public CustomUser(MemberVO vo) {
super(vo.getUserid(), vo.getUserpw(),vo.getAuthList()
.stream().map(auth->new SimpleGrantedAuthority(auth.getAuth()))
.collect(Collectors.toList()));
this.member = vo;
}
}
public class CustomUserDetailsService implements UserDetailsService {
@Setter(onMethod_= {@Autowired})
private MemberMapper memberMapper;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException{
log.warn("Load User By UserName : "+userName);
// userName means userid
MemberVO vo = memberMapper.read(userName);
log.warn("queried by member Mapper : "+vo);
return vo == null? null:new CustomUser(vo);
}
}
8. security-xml에 UserDetailsService를 구현한 클래스를 추가
<bean id="customUserDetailsService" class="org.zerock.security.CustomUserDetailsService"></bean>
<!-- 패스워드 인코딩 빈 등록 -->
<bean id ="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<security:authentication-manager>
<security:authentication-provider user-service-ref="customUserDetailsService">
<security:password-encoder ref="bcryptPasswordEncoder" />
</security:authentication-provider>
</security:authentication-manager>
9. 스프링 시큐리티 회원정보 이용하기
principal은 UserDetailsService에서 반환된 객체이다.
스프링 시큐리티에서 사용하는 표현식은 다음과 같다.
표현식 | |
hasRole([role]) hasAuthority |
해당 권한이 있으면 true |
hasAnyRole([role,role2]) hasAnyAuthority |
여러 권한 중에서 하나라도 해당하는 권한이 있으면 true |
principal | 현재 사용자 정보를 의미 |
permitAll | 모든 사용자 |
isAnomymous() | 익명의 사용자(로그인을 하지 않은 경우) |
denyAll | 모든 사용자 거부 |
isAuthenticated() | 인증된 사용자라면 true |
isFullyAuthenticated() | remember-me로 인증된 것이 아닌 인증된 사용자의 경우 |
이는 JSP에서 스프링 시큐리티를 사용할때 다음과 같이 이용할 수 있다.
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec" %>
<sec:authorize access="isAnonymous()">
<a href="/mypage/customlogin">Login</a>
</sec:authorize>
<sec:authorize access="isAuthenticated()">
<a href="/mypage/logout">Logout</a>
</sec:authorize>
또한 컨트롤러에서 @PreAuthorize와 함께 다음과 같이 사용할 수도 있다.
'Spring' 카테고리의 다른 글
[Spring Security] 소셜 로그인 - 구글 (0) | 2021.04.10 |
---|---|
[Spring Security] 로그인 성공, 실패 - 2 (0) | 2021.04.04 |
[Spring] Spring Security (0) | 2021.04.03 |
[Spring] @RestController (0) | 2021.04.02 |
[Spring] Controller에서 데이터 전달하기 (1) | 2021.04.02 |