스프링부트+jsp로 배달사이트 만들기-08 스프링 시큐리티로 로그인

2021. 12. 6. 14:19스프링부트

loginProcessingUrl("/login")에서 설정한 주소로 요청을 하게되면

스프링 시큐리티가 form태그의 input태그의 name을 가로채서 로그인을 처리하게되는데

input name을 기본적으론 아이디는 username 비밀번호는 password로 정해줘야합니다

 

스프링시큐리티는 로그인 성공시 UserDetails 타입의 세션을 생성합니다

db에서 유저정보를 받을 User 클래스와

유저정보를 같이 세션에 올리기 위해 UserDetails를 상속받은 LoginService클래스를 생성합니다

 

User

@Getter
@Setter
@ToString
public class User {
	private long id;
	private String username;
	private String password;
	private String email;
	private String nickname;
	private int point;
	private String phone;
	private String rating;
	private String role;
	
	public User(String username, String password, String email, String nickname, String phone) {
		this.username = username;
		this.password = password;
		this.email = email;
		this.nickname = nickname;
		this.phone = phone;
	}
}

 

LoginService

@Getter
@Setter
@ToString
public class LoginService implements UserDetails {
	private User user;
	
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		List<GrantedAuthority> roles = new ArrayList<>();
		roles.add(new GrantedAuthority() {
			@Override
			public String getAuthority() {
				return user.getRole();
			}
		});
		return roles;
	}

	@Override
	public String getPassword() {
		return user.getPassword();
	}

	@Override
	public String getUsername() {
		return user.getUsername();
	}

	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	@Override
	public boolean isEnabled() {
		return true;
	}

}

 

다음으로 db에서 유저정보를 불러올 UserDetailsService을 구현한 클래스를 만듭니다

LoginDetailService

@Service
public class LoginDetailService implements UserDetailsService {

	@Autowired
	private SqlSession sql;

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		User user = sql.selectOne("user.login", username);

		if (user != null) {
			LoginService loginDetail = new LoginService();

			loginDetail.setUser(user);

			return loginDetail;
		} else {
			throw new UsernameNotFoundException("유저없음");
		}
	}

}

db에서 유저정보를 확인하고, 있다면 loginDetail에 user을 담아 반환하면 시큐리티 세션에 저장이됩니다

 

 

UserMapper에 로그인 쿼리문을 추가합니다

<select id="login" resultType="User">
    SELECT 
        U.ID
        ,U.USERNAME
        ,U.PASSWORD
        ,U.EMAIL
        ,U.NICKNAME
        ,U.PHONE
        ,U.RATING
        ,U.ROLE
        ,P.POINT
    FROM 
        BM_USER U 
    LEFT JOIN 
        (SELECT USER_ID, SUM(POINT) POINT FROM BM_POINT GROUP BY USER_ID) P
    ON 
        U.ID = P.USER_ID
    WHERE U.USERNAME = #{username }    
</select>

 

 


시큐리티 설정파일에

successHandler(), failureHandler()을 추가하면 로그인 성공과 실패 후에 파라미터로 전달한 코드를 실행할  수 있습니다

파라미터로는 각각 

AuthenticationSuccessHandler을 구현한 LoginSuccess

AuthenticationFailureHandler을 구현한 LoginFail를 인자로 넣습니다 

@Autowired
private LoginFail loginFail;

@Autowired
private LoginSuccess loginSuccess;

 

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable();

    http.authorizeRequests()
        .antMatchers("/admin/**").hasRole("ADMIN")
        .antMatchers("/user/**").hasAnyRole("ADMIN, USER")
        .anyRequest().permitAll()
    .and()
        .formLogin()
        .loginPage("/") // 인증 필요한 페이지 접근시 이동페이지
        .loginProcessingUrl("/login")
        .successHandler(loginSuccess)
        .failureHandler(loginFail)
    .and()
        .logout()
        .logoutSuccessUrl("/myPage")
    ;

}

 

 

 

 

@Component
public class LoginSuccess implements AuthenticationSuccessHandler {

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException {
		
		response.sendRedirect("/myPage");
	}
}

로그인 성공시 myPage로 이동하게 합니다

 

 

@Component
public class LoginFail implements AuthenticationFailureHandler {

	@Override
	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException exception) throws IOException, ServletException {
		if (exception instanceof BadCredentialsException
				|| exception instanceof InternalAuthenticationServiceException) {
			request.setAttribute("loginFailMsg", "아이디와 비밀번호를 확인해 주세요");
		}
		request.getRequestDispatcher("/WEB-INF/view/user/login.jsp").forward(request, response);
	}

}
  • BadCredentialsException는 비밀번호가 일치하지 않았을때
  • InternalAuthenticationServiceException는 아이디가 없을때

로그인을 실패하게되면 다시 로그인페이지를 보여주고 에러메세지를 출력하게 했습니다

login.jsp 상단에 에러메세지를 추가합니다

<c:if test="${!empty loginFailMsg }">
    <script type="text/javascript">
        const msg = "${loginFailMsg}";
        swal(msg);
    </script>
</c:if>

 

 


자동로그인을 사용하고 싶다면 rememberMe() 옵션을 사용하면 됩니다

rememberMe는 로그인 성공시 쿠키에 인증정보를 암호화해서 저장하고 세션이 만료되었을때 이 인증정보로 다시 로그인을 합니다

 

시큐리티 설정파일에 코드를 추가합니다

@Autowired
private LoginDetailService loginDetailService;

 

@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.csrf().disable();
		
		http.authorizeRequests()
			.antMatchers("/admin/**").hasRole("ADMIN")
			.antMatchers("/user/**").hasAnyRole("ADMIN, USER")
			.anyRequest().permitAll()
		.and()
			.formLogin()
			.loginPage("/") // 인증 필요한 페이지 접근시 이동페이지
			.loginProcessingUrl("/login")
			.successHandler(loginSuccess)
			.failureHandler(loginFail)
		.and()
			.logout()
			.logoutSuccessUrl("/myPage")
		.and()
			.rememberMe()
			.key("rememberKey")
			.rememberMeCookieName("rememberMeCookieName")
			.rememberMeParameter("remember-me")
			.tokenValiditySeconds(60 * 60 * 24 * 7)
			.userDetailsService(loginDetailService)
		;
		
	}
  • key("rememberKey") 쿠키값을 암호화할때 사용되는 키
  • rememberMeParameter("remember-me")  로그인페이지의 체크박스 name과 일치해야합니다 이옵션을 쓰지 않으면 기본값은 "remember-me" 
  • rememberMeCookieName("rememberMeCookieName") 저장할 쿠키의 이름 기본값은 "remember-me" 
  • tokenValiditySeconds(60 * 60 * 24 * 7) 쿠키의 유지시간 - 일주일 
  • userDetailsService(loginDetailService) 유저정보를 받아올 UserDetailsService를 넣으면 되는데 위에서 구현한 loginDetailService를 사용

 

체크박스에 체크를 하고 로그인을 하면 설정한 이름으로 쿠키가 생성되고 서버를 재시작해도 로그인이 유지가됩니다

 

 

더 안전한 방법으론 인증정보를 db에 저장하는 방법이 있지만 다음번에 구현해보겠습니다