1. 로그인 로그아웃 처리를 해주기위해 우선 Configuration을 수정해준다.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
CustomUserDetailService customUserDetailService;
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/webjars/**");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/main","/members/loginerror","/members/joinform","/members/join","/members/welcome").permitAll()
.antMatchers("/securepage","/members/**").hasRole("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/members/loginform")
.usernameParameter("userId")
.passwordParameter("password")
.loginProcessingUrl("/authenticate")
.failureForwardUrl("/members/loginerror?login_error=1")
.defaultSuccessUrl("/",true)
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/");
}
//패스워드 인코더를 빈으로 등록.
//암호를 인코딩하거나 인코딩된 암호와 사용자가 입력한 암호가 같은지 확인할때 사용
@Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
}
SecurityConfig.java
- 메소드 설명
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailService);
}
WebSecurityConfigurerAdapter가 가지고 있는 메소드 configure(AuthenticationManagerBuilder auth)를 오버라이딩 하고 있다. 해당 메소드를 오버라이딩 한 후 UserDetailsService인터페이스를 구현하고 있는 객체(customUserDetailService)를 auth.userDetailsService()메소드의 인자로 전달하고 있다.
스프링 시큐리티 필터 중 AuthenticationFilter가 아이디/암호를 입력해서 로그인 할 때 처리해주는 필터이고 아이디에 해당하는 정보를 데이터베이스에서 읽어 들일 때 UserDetailsService를 구현하고 있는 객체를 이용한다.
UserDetailsService는 인터페이스이고 해당 인터페이스를 구현하고 있는 빈(customUserDetailService)을 사용한다.
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/main","/members/loginerror","/members/joinform","/members/join","/members/welcome").permitAll()
.antMatchers("/securepage","/members/**").hasRole("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/members/loginform")
.usernameParameter("userId")
.passwordParameter("password")
.loginProcessingUrl("/authenticate")
.failureForwardUrl("/members/loginerror?login_error=1")
.defaultSuccessUrl("/",true)
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/");
}
- http.csrf().disable()는 crsf()라는 기능을 끄는 설정이다.
- csrf는 보안 설정 중 post방식으로 값을 전송할 때 token을 사용해야하는 보안 설정입니다. csrf은 기본으로 설정되어 있는데 csrf를 사용하게 되면 보안성은 높아지지만 개발초기에는 불편함이 있다는 단점이 있습니다. 그래서 csrf 기능을 끄도록 한 것입니다.
- disable()메소드는 http(여기에선 HttpSecurity)를 리턴합니다.
이말은 disable().authorizeRequests()는 http.authoriazeRequests()와 같은 의미를 가집니다.
- antMatchers("/","/main","/members/loginerror","/members/joinform","/members/join","/members/welcome").permitAll()
- 로그인 없이 누구나 접근할 수 있는 경로를 추가했다.
- antMatchers("/securepage","/members/**").hasRole("USER")
- securepage와 members이하 경로엔 로그인이 필요하고 "USER"라는 권한도 가지고 있어야 접근이 가능하다.
- formlogin : 로그인 폼을 설정한다.
- logout : 로그아웃 처리 (스프링 시큐리티가 자동으로 처리)
- logoutUrl("/logout") : /logout요청이 들어오면 세션에서 로그인 정보를 삭제한다
- logoutSuccessUrl("/") : 로그아웃 성공시 "/"로 리다이렉트
2. 로그인 처리를 위한 클래스 생성하기
package securityexam.service.security;
public class UserEntity {
private String loginUserId;
private String password;
public UserEntity(String loginUserId, String password) {
this.loginUserId = loginUserId;
this.password = password;
}
public String getLoginUserId() {
return loginUserId;
}
public void setLoginUserId(String loginUserId) {
this.loginUserId = loginUserId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
UserEntity.java
로그인 아이디와 권한(Role)정보를 가지는 UserRoleEntity클래스를 생성
package securityexam.service.security;
public class UserRoleEntity {
private String userLoginId;
private String roleName;
public UserRoleEntity(String userLoginId, String roleName) {
this.userLoginId = userLoginId;
this.roleName = roleName;
}
public String getUserLoginId() {
return userLoginId;
}
public void setUserLoginId(String userLoginId) {
this.userLoginId = userLoginId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
}
UserRoleEntity.java
UserDbService인터페이스를 생성. 로그인한 사용자 id를 파라미터로 받아들여서
UserEntity와 List를 리턴하는 메소드를 가지고 있다.
package securityexam.service.security;
import java.util.List;
//스프링 시큐리티에서 필요로 하는 정보를 가지고 오는 인터페이스
public interface UserDbService {
public UserEntity getUser(String loginUserId);
public List<UserRoleEntity> getUserRoles(String loginUserId);
}
UserDbService.java
데이터베이스에서 읽어 들인 로그인 정보는 UserDetails인터페이스를 구현하고 있는 객체에 저장되어야 한다. UserDetails를 구현하고 있는 CustomUserDetails클래스를 생성합니다.
package securityexam.service.security;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
public class CustomUserDetails implements UserDetails {
private String username;
private String password;
private boolean isEnabled;
private boolean isAccountNonExpired;
private boolean isAccountNonLocked;
private boolean isCredentialsNonExpired;
private Collection<? extends GrantedAuthority>authorities;
@Override
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean isEnabled() {
return isEnabled;
}
public void setEnabled(boolean isEnabled) {
this.isEnabled = isEnabled;
}
@Override
public boolean isAccountNonExpired() {
return isAccountNonExpired;
}
public void setAccountNonExpired(boolean isAccountNonExpired) {
this.isAccountNonExpired = isAccountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return isAccountNonLocked;
}
public void setAccountNonLocked(boolean isAccountNonLocked) {
this.isAccountNonLocked = isAccountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return isCredentialsNonExpired;
}
public void setCredentialsNonExpired(boolean isCredentialsNonExpired) {
this.isCredentialsNonExpired = isCredentialsNonExpired;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
this.authorities = authorities;
}
}
CustomUserDetails.java
UserDetailsService인터페이스를 구현하는 CustomUserDetailsService를 생성한다.
UserDetailsService인터페이스는 1개의 메소드만 선언하고 있는데
loadUserByUsername(String loginId) throws UsernameNotFoundException 메소드이다.
사용자가 로그인을 할 때 아이디를 입력하면 해당 아이디를 loadUserByUsername()메소드의 인자로 전달하고
해당 아이디에 해당하는 정보가 없으면 UsernameNotFoundException이 발생한다.
정보가 있을 경우엔 UserDetails인터페이스를 구현한 객체를 리턴 하게 된다.
package securityexam.service.security;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class CustomUserDetailService implements UserDetailsService {
//데이터베이스에서 로그인 아이디에 해당하는 정보를 읽어 들이기 위해서
//UserDbService를 구현한 객체를 주입받고 있다.
@Autowired
UserDbService userdbService;
@Override
public UserDetails loadUserByUsername(String loginId) throws UsernameNotFoundException {
// loginId에 해당하는 정보를 데이터베이스에서 읽어 CustomUser객체에 저장한다.
// 해당 정보를 CustomUserDetails객체에 저장한다
UserEntity customUser = userdbService.getUser(loginId);
if(customUser==null)
throw new UsernameNotFoundException("사용자가 입력한 아이디에 해당하는 사용자를 찾을 수 없습니다.");
CustomUserDetails customUserDetails = new CustomUserDetails();
customUserDetails.setUsername(customUser.getLoginUserId());
customUserDetails.setPassword(customUser.getPassword());
List<UserRoleEntity> customRoles = userdbService.getUserRoles(loginId);
// 로그인 한 사용자의 권한 정보를 GrantedAuthority를 구현하고 있는 SimpleGrantedAuthority객체에 담아
// 리스트에 추가한다. MemberRole 이름은 "ROLE_"로 시작되야 한다.
List<GrantedAuthority> authorities = new ArrayList<>();
if(customRoles != null) {
for(UserRoleEntity customRole : customRoles) {
authorities.add(new SimpleGrantedAuthority(customRole.getRoleName()));
}
}
// CustomUserDetails객체에 권한 목록 (authorities)를 설정한다.
customUserDetails.setAuthorities(authorities);
customUserDetails.setEnabled(true);
customUserDetails.setAccountNonExpired(true);
customUserDetails.setAccountNonLocked(true);
customUserDetails.setCredentialsNonExpired(true);
return customUserDetails;
}
}
CustomUserDetailService.java
회원 관련 처리를 하는 Service 생성 (회원가입 등)
//회원관련 정보처리하는 서비스
public interface MemberService extends UserDbService {
}
MemberService.java
@Service
public class MemberServiceImpl implements MemberService {
// 생성자에 의해 주입되는 객체이고, 해당 객체를 초기화할 필요가 이후에 없기 때문에 final로 선언하였다.
// final로 선언하고 초기화를 안한 필드는 생성자에서 초기화를 해준다.
private final MemberDao memberDao;
private final MemberRoleDao memberRoleDao;
// @Service가 붙은 객체는 스프링이 자동으로 Bean으로 생성하는데
// 기본생성자가 없고 아래와 같이 인자를 받는 생성자만 있을 경우 자동으로 관련된 타입이 Bean으로 있을 경우 주입해서 사용하게 된다.
public MemberServiceImpl(MemberDao memberDao, MemberRoleDao memberRoleDao) {
this.memberDao = memberDao;
this.memberRoleDao = memberRoleDao;
}
@Override
@Transactional
public UserEntity getUser(String loginUserId) {
Member member = memberDao.getMemberByEmail(loginUserId);
return new UserEntity(member.getEmail(),member.getPassword());
}
@Override
@Transactional
public List<UserRoleEntity> getUserRoles(String loginUserId) {
List<MemberRole> memberRoles = memberRoleDao.getRolesByEmail(loginUserId);
List<UserRoleEntity> list = new ArrayList<UserRoleEntity>();
for(MemberRole memberRole : memberRoles) {
list.add(new UserRoleEntity(loginUserId, memberRole.getRoleName()));
}
return list;
}
}
MemberServiceImpl.java
3. 로그인 처리를 위한 컨트롤러와 뷰 생성
@Controller
@RequestMapping(path = "/members")
public class MemberController {
// 스프링 컨테이너가 생성자를 통해 자동으로 주입한다.
private final MemberService memberService;
public MemberController(MemberService memberService){
this.memberService = memberService;
}
@GetMapping("/loginform")
public String loginform(){
return "members/loginform";
}
@RequestMapping("/loginerror")
public String loginerror(@RequestParam("login_error")String loginError){
return "members/loginerror";
}
}
MemberController.java
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인</title>
</head>
<body>
<div>
<div>
<form method="post" action="/securityexam/authenticate">
<div>
<label>ID</label>
<input type="text" name="userId">
</div>
<div>
<label>PASSWORD</label>
<input type="password" name="password">
</div>
<div>
<label></label>
<input type="submit" value="로그인">
</div>
</form>
</div>
</div>
</body>
</html>
members/loginform.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 오류</title>
</head>
<body>
<h1>로그인 오류가 발생했습니다. id나 암호를 다시 입력해주세요.</h1>
<a href="/securityexam/members/loginform">login</a>
</body>
</html>
members/loginerror.jsp
url과 데이터베이스정보(암호화된 비밀번호:1234)를 입력하고 로그인하면 main page로 이동하고
securepage도 잘 접근이 되는 것을 확인할 수 있다.
http://localhost:8080/securityexam/members/logout 을 입력하면 로그아웃이 되는데,
로그아웃을 처리하는 기능을 하나도 구현하지 않았지만 로그아웃이 되는 이유는
이미 로그아웃을 처리하는 필터가 동작하고 있기 때문이다.
출처: https://ivory-room.tistory.com/26?category=875739 [개발로 자기개발]
'study > java' 카테고리의 다른 글
전자정부프레임워크기반 게시판 만들기 (1) 시작 (0) | 2021.12.30 |
---|---|
[Spring Security] 회원가입하기 (0) | 2021.12.30 |
[Spring Security] DB정보로 로그인/로그아웃하기 (1) (0) | 2021.12.30 |
[Spring Security] 스프링 시큐리티 설정 (0) | 2021.12.30 |
[Spring Security] 스프링 시큐리티 개념 (0) | 2021.12.30 |